class OneviewSDK::Cli
command-line-interface for oneview-sdk When you install this gem, this cli should be available to you by running: `$ oneview-sdk-ruby`
Constants
- SUPPORTED_VARIANTS
Public Instance Methods
Check, import, or list OneView certs
# File lib/oneview-sdk/cli.rb, line 412 def cert(type, url = ENV['ONEVIEWSDK_URL']) case type.downcase when 'check' fail_nice 'Must specify a url' unless url puts "Checking certificate for '#{url}' ..." if OneviewSDK::SSLHelper.check_cert(url) puts 'Certificate is valid!' else fail_nice 'Certificate Validation Failed!' end when 'import' fail_nice 'Must specify a url' unless url puts "Importing certificate for '#{url}' into '#{OneviewSDK::SSLHelper::CERT_STORE}'..." OneviewSDK::SSLHelper.install_cert(url) when 'list' if File.file?(OneviewSDK::SSLHelper::CERT_STORE) puts File.read(OneviewSDK::SSLHelper::CERT_STORE) else puts 'No certs imported!' end else fail_nice "Invalid action '#{type}'. Valid actions are [check, import, list]" end rescue StandardError => e fail_nice e.message end
Open a Ruby console with a connection to OneView
# File lib/oneview-sdk/cli.rb, line 84 def console client_setup({}, true, true) puts "Console Connected to #{@client.url}" puts "HINT: The @client object is available to you\n\n" rescue puts "WARNING: Couldn't connect to #{@options['url'] || ENV['ONEVIEWSDK_URL']}\n\n" ensure require 'pry' Pry.config.prompt = proc { '> ' } Pry.plugins['stack_explorer'] && Pry.plugins['stack_explorer'].disable! Pry.plugins['byebug'] && Pry.plugins['byebug'].disable! Pry.start(OneviewSDK::Console.new(@client)) end
Create/Update resource defined in file
# File lib/oneview-sdk/cli.rb, line 351 def create_from_file(file_path) client_setup resource = OneviewSDK::Resource.from_file(@client, file_path) fail_nice 'Failed to determine resource type!' if resource.class == OneviewSDK::Resource existing_resource = resource.class.new(@client, resource.data) resource.data.delete('uri') if existing_resource.retrieve! if options['if_missing'] puts "Skipped: #{resource.class.name.split('::').last} '#{resource[:name]}' already exists.\n#{existing_resource[:uri]}" return end if existing_resource.like?(resource.data) puts "Skipped: #{resource.class.name.split('::').last} '#{resource[:name]}' is up to date.\n#{existing_resource[:uri]}" return end begin existing_resource.update(resource.data) puts "Updated Successfully!\n#{existing_resource[:uri]}" rescue StandardError => e fail_nice "Failed to update #{resource.class.name.split('::').last} '#{resource[:name]}': #{e}" end else begin resource.create puts "Created Successfully!\n#{resource[:uri]}" rescue StandardError => e fail_nice "Failed to create #{resource.class.name.split('::').last} '#{resource[:name]}': #{e}" end end rescue IncompleteResource => e fail_nice "Failed to create #{resource.class.name.split('::').last} '#{resource[:name]}': #{e}" rescue SystemCallError => e # File open errors fail_nice e end
Delete resource by name
# File lib/oneview-sdk/cli.rb, line 307 def delete(type, name) resource_class = parse_type(type) client_setup matches = resource_class.find_by(@client, name: name) fail_nice('Not Found', 2) if matches.empty? resource = matches.first return unless options['force'] || agree("Delete '#{name}'? [Y/N] ") begin resource.delete puts 'Deleted Successfully!' rescue StandardError => e fail_nice "Failed to delete #{resource.class.name.split('::').last} '#{name}': #{e}" end end
Delete resource defined in file
# File lib/oneview-sdk/cli.rb, line 328 def delete_from_file(file_path) client_setup resource = OneviewSDK::Resource.from_file(@client, file_path) fail_nice("#{resource.class.name.split('::').last} '#{resource[:name] || resource[:uri]}' Not Found", 2) unless resource.retrieve! return unless options['force'] || agree("Delete '#{resource[:name]}'? [Y/N] ") begin resource.delete puts 'Deleted Successfully!' rescue StandardError => e fail_nice "Failed to delete #{resource.class.name.split('::').last} '#{resource[:name]}': #{e}" end rescue IncompleteResource => e fail_nice "Failed to delete #{resource.class.name.split('::').last} '#{resource[:name]}': #{e}" rescue SystemCallError => e # File open errors fail_nice e end
Show environment variables for oneview-sdk-ruby
# File lib/oneview-sdk/cli.rb, line 115 def env data = {} OneviewSDK::ENV_VARS.each { |k| data[k] = ENV[k] } if @options['format'] == 'human' data.each do |key, value| value = "'#{value}'" if value && !%w[true false].include?(value) printf "%-#{data.keys.max_by(&:length).length}s = %s\n", key, value || 'nil' end else output(parse_hash(data, true)) end end
List names of resources (and optionally, specific attributes)
# File lib/oneview-sdk/cli.rb, line 148 def list(type) resource_class = parse_type(type) client_setup all = resource_class.get_all(@client) if options['attribute'] data = select_attributes_from_multiple(options['attribute'], all) output data, -2 # Shift left by 2 so things look right else # List names only by default names = [] all.each { |r| names.push(r['name']) } output names end end
Attempt authentication and return token
# File lib/oneview-sdk/cli.rb, line 130 def login client_setup puts "Login Successful! Token = #{@client.token}" end
Make REST call to the OneView API
# File lib/oneview-sdk/cli.rb, line 238 def rest(method, uri) log_level = @options['log_level'] == :warn ? :error : @options['log_level'].to_sym # Default to :error client_setup('log_level' => log_level) uri_copy = uri.dup uri_copy.prepend('/') unless uri_copy.start_with?('/') if @options['data'] begin data = { body: JSON.parse(@options['data']) } rescue JSON::ParserError => e fail_nice("Failed to parse data as JSON\n#{e.message}") end end data ||= {} response = @client.rest_api(method, uri_copy, data) if response.code.to_i.between?(200, 299) case @options['format'] when 'yaml' puts JSON.parse(response.body).to_yaml when 'json' puts JSON.pretty_generate(JSON.parse(response.body)) else # raw puts response.body end else body = JSON.pretty_generate(JSON.parse(response.body)) rescue response.body fail_nice("Request failed: #{response.inspect}\nHeaders: #{response.to_hash}\nBody: #{body}") end rescue OneviewSDK::InvalidRequest => e fail_nice(e.message) end
Subscribe to the OneView SCMB
# File lib/oneview-sdk/cli.rb, line 453 def scmb client_setup connection = OneviewSDK::SCMB.new_connection(@client) q = OneviewSDK::SCMB.new_queue(connection, @options['route']) puts 'Subscribing to OneView messages. To exit, press Ctrl + c' q.subscribe(block: true) do |_delivery_info, _properties, payload| data = JSON.parse(payload) rescue payload puts "\n#{'=' * 50}\n\nReceived message with payload:" @options['format'] == 'raw' ? puts(payload) : output(data) end end
Search for resource by key/value pair(s)
# File lib/oneview-sdk/cli.rb, line 206 def search(type) resource_class = parse_type(type) client_setup filter = parse_hash(options['filter']) matches = resource_class.find_by(@client, filter) if matches.empty? # Search with integers & booleans converted filter = parse_hash(options['filter'], true) matches = resource_class.find_by(@client, filter) unless filter == options['filter'] end if options['attribute'] data = select_attributes_from_multiple(options['attribute'], matches) output data, -2 # Shift left by 2 so things look right else # List names only by default names = [] matches.each { |m| names.push(m['name']) } output names end end
Show resource details
# File lib/oneview-sdk/cli.rb, line 176 def show(type, name) resource_class = parse_type(type) client_setup matches = resource_class.find_by(@client, name: name) fail_nice 'Not Found' if matches.empty? data = matches.first.data if options['attribute'] data = select_attributes(options['attribute'], data) end output data end
Save resource details to file
# File lib/oneview-sdk/cli.rb, line 398 def to_file(type, name) file = File.expand_path(options['path']) resource_class = parse_type(type) client_setup resource = resource_class.find_by(@client, name: name).first fail_nice "#{resource_class.name.split('::').last} '#{name}' not found" unless resource resource.to_file(file, options['format']) puts "Output to #{file}" rescue SystemCallError => e fail_nice "Failed to create file! (You may need to create the necessary directories). Message: #{e}" end
Update resource by name
# File lib/oneview-sdk/cli.rb, line 280 def update(type, name) resource_class = parse_type(type) client_setup fail_nice 'Must set the hash or json option' unless @options['hash'] || @options['json'] fail_nice 'Must set the hash OR json option. Not both' if @options['hash'] && @options['json'] begin data = @options['hash'] || JSON.parse(@options['json']) rescue JSON::ParserError => e fail_nice("Failed to parse json\n#{e.message}") end matches = resource_class.find_by(@client, name: name) fail_nice 'Not Found' if matches.empty? resource = matches.first begin resource.update(data) puts 'Updated Successfully!' rescue StandardError => e fail_nice "Failed to update #{resource.class.name.split('::').last} '#{name}': #{e}" end end
Print gem and OneView appliance versions
# File lib/oneview-sdk/cli.rb, line 100 def version puts "Gem Version: #{OneviewSDK::VERSION}" client_setup({ 'log_level' => :error }, true) puts "OneView appliance API version at '#{@client.url}' = #{@client.max_api_version}" rescue StandardError, SystemExit puts 'OneView appliance API version unknown' end
Private Instance Methods
# File lib/oneview-sdk/cli.rb, line 472 def client_setup(client_params = {}, quiet = false, throw_errors = false) client_params['ssl_enabled'] = true if @options['ssl_verify'] == true client_params['ssl_enabled'] = false if @options['ssl_verify'] == false client_params['url'] ||= @options['url'] if @options['url'] client_params['log_level'] ||= @options['log_level'].to_sym if @options['log_level'] client_params['api_version'] ||= @options['api_version'].to_i if @options['api_version'] client_params['api_version'] ||= ENV['ONEVIEWSDK_API_VERSION'].to_i if ENV['ONEVIEWSDK_API_VERSION'] @client = OneviewSDK::Client.new(client_params) rescue StandardError => e raise e if throw_errors fail_nice if quiet fail_nice "Failed to login to OneView appliance at '#{client_params['url']}'. Message: #{e}" end
# File lib/oneview-sdk/cli.rb, line 467 def fail_nice(msg = nil, exit_code = 1) puts "ERROR: #{msg}" if msg exit exit_code end
Print output in a given format.
# File lib/oneview-sdk/cli.rb, line 599 def output(data = {}, indent = 0) case @options['format'] when 'json' puts JSON.pretty_generate(data) when 'yaml' puts data.to_yaml else # rubocop:disable Metrics/BlockNesting if data.class == Hash || data.class <= OneviewSDK::Resource data.each do |k, v| if v.class == Hash || v.class == Array puts "#{' ' * indent}#{k.nil? ? 'nil' : k}:" output(v, indent + 2) else puts "#{' ' * indent}#{k.nil? ? 'nil' : k}: #{v.nil? ? 'nil' : v}" end end elsif data.class == Array data.each do |d| if d.class == Hash || d.class == Array output(d, indent + 2) else puts "#{' ' * indent}#{d.nil? ? 'nil' : d}" end end puts "\nTotal: #{data.size}" if indent < 1 else puts "#{' ' * indent}#{data.nil? ? 'nil' : data}" end # rubocop:enable Metrics/BlockNesting end end
Parse options hash from input. Handles chaining and keywords such as true/false & nil Returns new hash with proper nesting and formatting
# File lib/oneview-sdk/cli.rb, line 519 def parse_hash(hash, convert_types = false) new_hash = {} hash.each do |k, v| if convert_types v = v.to_i if v && v.match(/^\d+$/) v = true if v == 'true' v = false if v == 'false' v = nil if v == 'nil' end if k =~ /\./ sub_hash = new_hash split = k.split('.') split.each do |sub_key| if sub_key == split.last sub_hash[sub_key] = v else sub_hash[sub_key] ||= {} sub_hash = sub_hash[sub_key] end end new_hash[split.first] ||= {} else new_hash[k] = v end end new_hash end
Get resource class from given string
# File lib/oneview-sdk/cli.rb, line 487 def parse_type(type) api_ver = (@options['api_version'] || ENV['ONEVIEWSDK_API_VERSION'] || OneviewSDK.api_version).to_i unless OneviewSDK::SUPPORTED_API_VERSIONS.include?(api_ver) # Find and use the best available match for the desired API version (round down to nearest) valid_api_ver = OneviewSDK::SUPPORTED_API_VERSIONS.select { |x| x <= api_ver }.max || OneviewSDK::SUPPORTED_API_VERSIONS.min puts "WARNING: Module API version #{api_ver} is not supported. Using #{valid_api_ver}" api_ver = valid_api_ver end variant = @options['variant'] || ENV['ONEVIEWSDK_VARIANT'] variant ||= OneviewSDK::API300.variant if api_ver == 300 if variant && !SUPPORTED_VARIANTS.include?(variant) fail_nice "Variant '#{variant}' is not supported. Try one of #{SUPPORTED_VARIANTS}" end r = OneviewSDK.resource_named(type, api_ver, variant) # Try default API version as last resort r ||= OneviewSDK.resource_named(type, OneviewSDK.api_version, variant) unless api_ver == OneviewSDK.api_version return r if r && r.respond_to?(:find_by) valid_classes = [] api_module = OneviewSDK.const_get("API#{api_ver}") api_module = api_module.const_get(variant.to_s) unless api_ver.to_i == 200 api_module.constants.each do |c| klass = api_module.const_get(c) next unless klass.is_a?(Class) && klass.respond_to?(:find_by) valid_classes.push(klass.name.split('::').last) end vc = valid_classes.sort_by!(&:downcase).join("\n ") var = variant ? " (variant #{variant})" : '' fail_nice("Invalid resource type: '#{type}'. Valid options for API version #{api_ver}#{var} are:\n #{vc}") end
Select a subset of attributes from a given resource @param attributes [String, Array<Array<String>>] Comma-separated string or array of array of strings
The reason it's a nested array is to allow retrieval of nested keys. For example, the following 2 attribute params will return the same result: - [['key1'], ['key2', 'subKey3']] - 'key1,key2.subKey3'
@param data [Hash, OneviewSDK::Resource] @return [Hash] A Hash is returned. For example:
{ 'key1' => 'val1', 'key2' => { 'subKey3' => 'val2' } }
# File lib/oneview-sdk/cli.rb, line 556 def select_attributes(attributes, data = {}) attributes = attributes.split(',').map(&:strip).reject(&:empty?).map { |a| a.split('.') } if attributes.is_a?(String) r_data = data.is_a?(Hash) ? data : data.data temp = {} attributes.each do |attr| temp_level = temp attr = [attr] if attr.is_a?(String) attr.each_with_index do |a, index| # Safely retrieving and setting nested keys is not as easy, so loop to build a nested Hash structure for the result if index == attr.size - 1 # Use r_data.dig(*attr) if we ever drop support for Ruby < 2.3 temp_level[a] = [*attr].reduce(r_data) { |m, k| m && m[k] } rescue nil else temp_level[a] ||= {} temp_level = temp_level[a] end end end temp end
Select a subset of attributes from a given set of resources @param attributes [String, Array<Array<String>>] Comma-separated string or array of array of strings
The reason it's a nested array is to allow retrieval of nested keys. For example, the following 2 attribute params will return the same result: - [['key1'], ['key2', 'subKey3']] - 'key1,key2.subKey3'
@param data [Array<Hash>, Array<OneviewSDK::Resource>] @return [Array<Hash>] An Array of Hashes is returned. For example:
[ { 'resource_name1' => { 'key1' => 'val1', 'key2' => { 'subKey3' => 'val2' } } }, { 'resource_name2' => { 'key1' => 'val3', 'key2' => { 'subKey3' => 'val4' } } }, ]
# File lib/oneview-sdk/cli.rb, line 589 def select_attributes_from_multiple(attributes, data = []) attributes = attributes.split(',').map(&:strip).reject(&:empty?).map { |a| a.split('.') } if attributes.is_a?(String) result = [] data.each do |r| result.push(r['name'] => select_attributes(attributes, r)) end result end