module Onering module CLI module Report DEFAULT_CACHE_MAXAGE=600 def self.configure(global={}) @api = Onering::CLI.connect(global.merge({ :autoconnect => false })) @opts = ::Trollop::options do banner <<-EOS Generate a system report that can be saved or submitted to a Onering server Subcommands: Generate and output the system inventory report. get [fallback] Get a specific from the report, return [fallback] if not found. save Generate and save the system inventory report to Onering. Usage: onering [global] report [options] [subcommands] Options: EOS opt :id, "Override the autodetected Hardware ID for this node", :short => '-I', :type => :string opt :fields, "Set the named FIELD to equal VALUE in the format FIELD=VALUE. Can be specified multiple times", :short => '-o', :type => :string, :multi => true opt :save, "Save the report output to the configured Onering server" opt :timeout, "The maximum amount of time to wait for a report to be generated", :type => :integer, :default => 60 opt :plugin_timeout, "The maximum amount of time to wait for a report plugin to return data", :type => :integer, :default => 10 opt :local, "Do not attempt to contact a remote server for retrieving values not found locally", :type => :boolean, :default => false opt :cachefile, "Use the specified file as a local report cache", :type => :string, :short => '-F' opt :nocache, "Do not attempt to use a cache file for report generation", :type => :boolean, :default => false opt :maxage, "Maxmimum age (in seconds) of the cache before it is automatically regenerated", :type => :integer, :default => DEFAULT_CACHE_MAXAGE stop_on %w{get save} end # initialize report generator with user options @_reporter = Onering::Reporter.new({ :id => @opts[:id], :timeout => @opts[:timeout], :plugin_timeout => @opts[:plugin_timeout], :nocache => @opts[:nocache], :cachefile => @opts[:cachefile], :maxage => @opts[:maxage] }.compact) end def self.run(args) # saving, by default, should not use the cache (but should update it to keep it fresh) if @opts[:save] === true or args[0] == 'save' report = _report({ :cacheregen => true }) else report = _report() end # pull overrides from CLI arguments @opts[:fields].each do |field| key, value = field.split('=', 2) Onering::Logger.debug("Override value #{key} from command line argument", "Onering::CLI::Report") value = nil if ['null', '', '-'].include?(value.to_s.strip.chomp) report = report.set(key, value) end # save if specified if @opts[:save] === true _save(report) else sc = args.shift case (sc.downcase.to_sym rescue nil) # ----------------------------------------------------------------------------- when :save _save(report) return nil when :get Onering::Logger.fatal!("Expected at least 1 parameter, got #{args.length}", "Onering::CLI::Report") unless args.length >= 1 # this is kinda ugly # because we don't know which property might have an @-prefix, progressively # search through all of them. first non-null match wins parts = args[0].split('.') # create an array with every component of the path prefixed with the @-symbol, then with # the path as is. # # e.g.: onering report get metrics.disk.block # -> value exists in the inventory as properties.metrics.disk.@block, # but the user shouldn't need to know where that @-prefix is, so... # # Search for all of these, first non-nil value wins: # * properties.metrics.disk.block # * properties.@metrics.disk.block # * properties.metrics.@disk.block # * properties.metrics.disk.@block # * metrics.disk.block # candidates = [(['properties']+parts).join('.')] parts.each_index{|ix| candidates << (['properties']+(ix == 0 ? [] : parts[0..(ix-1)]) + ["@#{parts[ix]}"] + parts[ix+1..-1]).join('.') }.flatten() rv = nil # search for the key using science or something candidates.each do |c| rv = report.get(c) break unless rv.nil? end # if we're still nil by this point, use the fallback value rv = report.get(args[0], args[1]) if rv.nil? # attempt to get the value remotely if not found locally if rv.nil? and not @opts[:local] hid = Onering::Util.fact(:hardwareid) if not hid.nil? Onering::Logger.debug("Getting remote value #{args[0]} for asset #{hid}") @api.connect() return @api.assets.get_field(hid, args[0], args[1]) end end return rv end end return report end private def self._save(report) @api.connect() @api.assets.save(report['id']) do MultiJson.dump(report) end end def self._report(options={}) begin Onering::Logger.debug("Gathering local data for report", "Onering::CLI::Report") Onering::Logger.fatal("Reporter not configured. This is a bug", "Onering::CLI::Report") if @_reporter.nil? report = @_reporter.report(options).stringify_keys() # pull report overrides from the config file Onering::Config.get('reporter.fields',{}).each do |key, value| Onering::Logger.debug("Override value #{key} from config file", "Onering::CLI::Report") if value.is_a?(Hash) value.coalesce(key, nil, '.').each do |k,v| v = nil if ['null', '', '-'].include?(v.to_s.strip.chomp) report = report.set(k, v) end else value = nil if ['null', '', '-'].include?(value.to_s.strip.chomp) report = report.set(key, value) end end return report rescue Timeout::Error Onering::Logger.fatal!("Report took too long to generate, exiting...", "Onering::CLI::Report") end end end end end