class WavefrontCli::Base

Parent of all the CLI classes. This class uses metaprogramming techniques to try to make adding new CLI commands and sub-commands as simple as possible.

To define a subcommand 'cmd', you only need add it to the `docopt` description in the relevant section, and create a method 'do_cmd'. The WavefrontCli::Base::dispatch() method will find it, and call it. If your subcommand has multiple words, like 'delete tag', your do method would be called `do_delete_tag`. The `do_` methods are able to access the Wavefront SDK object as `wf`, and all docopt options as `options`.

Attributes

klass[RW]
klass_word[RW]
options[RW]
wf[RW]

Public Class Methods

new(options) click to toggle source
# File lib/wavefront-cli/base.rb, line 30
def initialize(options)
  @options = options
  sdk_class = _sdk_class
  @klass_word = sdk_class.split('::').last.downcase
  validate_input

  options_and_exit if options[:help]

  require_sdk_class
  @klass = Object.const_get(sdk_class)

  send(:post_initialize, options) if respond_to?(:post_initialize)
end

Public Instance Methods

_sdk_class() click to toggle source

Normally we map the class name to a similar one in the SDK. Overriding this method lets you map to something else.

# File lib/wavefront-cli/base.rb, line 51
def _sdk_class
  self.class.name.sub(/Cli/, '')
end
cannot_noop!() click to toggle source

Operations which do require multiple operations cannot be perormed as a no-op. Drop in a call to this method for those things. The exception is caught in controller.rb

# File lib/wavefront-cli/base.rb, line 531
def cannot_noop!
  raise WavefrontCli::Exception::UnsupportedNoop if options[:noop]
end
check_response_blocks(data) click to toggle source
# File lib/wavefront-cli/base.rb, line 247
def check_response_blocks(data)
  %i[status response].each do |b|
    abort "no #{b} block in API response" unless data.respond_to?(b)
  end
end
check_status(status) click to toggle source
# File lib/wavefront-cli/base.rb, line 283
def check_status(status)
  status.respond_to?(:result) && status.result == 'OK'
end
cli_output_class(output_format) click to toggle source
# File lib/wavefront-cli/base.rb, line 313
def cli_output_class(output_format)
  require_relative File.join('output', output_format.to_s)
  Object.const_get(format('WavefrontOutput::%<class>s',
                          class: output_format.to_s.capitalize))
end
conds_to_query(conds) click to toggle source

Turn a list of search conditions into an API query

@param conds [Array] @return [Array]

# File lib/wavefront-cli/base.rb, line 480
def conds_to_query(conds)
  conds.map do |cond|
    key, value = cond.split(SEARCH_SPLIT, 2)
    { key: key, value: value }.merge(matching_method(cond))
  end
end
descriptive_name() click to toggle source
# File lib/wavefront-cli/base.rb, line 124
def descriptive_name
  klass_word
end
dispatch() click to toggle source

Works out the user's command by matching any options docopt has set to 'true' with any 'do_' method in the class. Then calls that method, and displays whatever it returns.

@return [nil] @raise WavefrontCli::Exception::UnhandledCommand if the

command does not match a `do_` method.
# File lib/wavefront-cli/base.rb, line 179
def dispatch
  # Look through each deconstructed method name and see if the
  # user supplied an option for each component.  Call the first
  # one that matches. The order will ensure we match
  # "do_delete_tags" before we match "do_delete".
  #
  method_word_list.reverse_each do |w_list|
    if w_list.reject { |w| options[w.to_sym] }.empty?
      method = name_of_do_method(w_list)
      return display(public_send(method), method)
    end
  end

  if respond_to?(:do_default)
    return display(public_send(:do_default), :do_default)
  end

  raise WavefrontCli::Exception::UnhandledCommand
end
display(data, method) click to toggle source

Display a Ruby object as JSON, YAML, or human-readable. We provide a default method to format human-readable output, but you can override it by creating your own `humanize_command_output` method control how its output is handled by setting the `response` instance variable.

@param data [WavefrontResponse] an object returned by a

Wavefront SDK method. This will contain 'response'
and 'status' structures.

@param method [String] the name of the method which produced

this output. Used to find a suitable humanize method.
# File lib/wavefront-cli/base.rb, line 225
def display(data, method)
  if no_api_response.include?(method)
    return display_no_api_response(data, method)
  end

  exit if options[:noop]

  ok_exit data if options[:format] == 'raw'

  check_response_blocks(data)
  warning_message(data.status)
  status_error_handler(data, method)
  handle_response(data.response, format_var, method)
end
display_api_error(status) click to toggle source

Classes can provide methods which give the user information on a given error code. They are named handle_errcode_xxx, and return a string. @param status [Map] status object from SDK response @return System exit

# File lib/wavefront-cli/base.rb, line 259
def display_api_error(status)
  method = format('handle_errcode_%<code>s', code: status.code).to_sym

  msg = if respond_to?(method)
          send(method, status)
        else
          status.message || 'No further information'
        end

  abort format('ERROR: API code %<code>s. %<message>s.',
               code: status.code,
               message: msg.chomp('.')).fold(TW, 7)
end
display_class() click to toggle source
# File lib/wavefront-cli/base.rb, line 333
def display_class
  klass.name.sub('Wavefront', 'WavefrontDisplay')
end
display_no_api_response(data, method) click to toggle source
# File lib/wavefront-cli/base.rb, line 279
def display_no_api_response(data, method)
  handle_response(data, format_var, method)
end
do_delete() click to toggle source
# File lib/wavefront-cli/base.rb, line 406
def do_delete
  wf.delete(options[:'<id>'])
end
do_describe() click to toggle source
# File lib/wavefront-cli/base.rb, line 368
def do_describe
  wf.describe(options[:'<id>'])
end
do_dump() click to toggle source
# File lib/wavefront-cli/base.rb, line 372
def do_dump
  cannot_noop!

  case options[:format]
  when 'yaml'
    ok_exit dump_yaml
  when 'json'
    ok_exit dump_json
  else
    abort format("Dump format must be 'json' or 'yaml'. " \
                 "(Tried '%<format>s')", options)
  end
end
do_import() click to toggle source
# File lib/wavefront-cli/base.rb, line 401
def do_import
  require_relative 'subcommands/import'
  WavefrontCli::Subcommand::Import.new(self, options).run!
end
do_list() click to toggle source

Below here are common methods. Most are used by most classes, but if they don't match a command described in the docopt text, the dispatcher will never call them. So, there's no harm inheriting unneeded things. Some classes override them.

# File lib/wavefront-cli/base.rb, line 358
def do_list
  list = if options[:all]
           wf.list(ALL_PAGE_SIZE, :all)
         else
           wf.list(options[:offset] || 0, options[:limit] || 100)
         end

  respond_to?(:list_filter) ? list_filter(list) : list
end
do_set() click to toggle source
# File lib/wavefront-cli/base.rb, line 432
def do_set
  cannot_noop!
  k, v = options[:'<key=value>'].split('=', 2)
  wf.update(options[:'<id>'], k => v)
end
do_undelete() click to toggle source
# File lib/wavefront-cli/base.rb, line 428
def do_undelete
  wf.undelete(options[:'<id>'])
end
dump_json() click to toggle source
# File lib/wavefront-cli/base.rb, line 390
def dump_json
  item_dump_call.to_json
end
dump_yaml() click to toggle source
# File lib/wavefront-cli/base.rb, line 386
def dump_yaml
  JSON.parse(item_dump_call.to_json).to_yaml
end
extract_values(obj, key, aggr = []) click to toggle source

A recursive function which fetches list of values from a nested hash. Used by WavefrontCli::Dashboard#do_queries @param obj [Object] the thing to search @param key [String, Symbol] the key to search for @param aggr [Array] values of matched keys @return [Array]

rubocop:disable Metrics/MethodLength

# File lib/wavefront-cli/base.rb, line 543
def extract_values(obj, key, aggr = [])
  case obj.class
  when Hash
    obj.each_pair do |k, v|
      if k == key && !v.to_s.empty?
        aggr.<< v
      else
        extract_values(v, key, aggr)
      end
    end
  when Array
    obj.each { |e| extract_values(e, key, aggr) }
  end

  aggr
end
failed_validation_message(input) click to toggle source
# File lib/wavefront-cli/base.rb, line 118
def failed_validation_message(input)
  format("'%<value>s' is not a valid %<thing>s ID.",
         value: input,
         thing: descriptive_name)
end
format_var() click to toggle source

To allow a user to default to different output formats for different object types, we are able to define a format for each class. instance, `alertformat` or `proxyformat`. This method returns such a symbol appropriate for the inheriting class.

@return [Symbol] name of the option or config-file key which

sets the default output format for this class
# File lib/wavefront-cli/base.rb, line 165
def format_var
  options[:format].to_sym
rescue NoMethodError
  :human
end
handle_error(method, code) click to toggle source

This gives us a chance to catch different errors in WavefrontDisplay classes. If nothing catches, them abort.

# File lib/wavefront-cli/base.rb, line 290
def handle_error(method, code)
  k = load_display_class
  k.new({}, options).run_error([method, code].join('_'))
end
handle_response(resp, format, method) click to toggle source
# File lib/wavefront-cli/base.rb, line 295
def handle_response(resp, format, method)
  if format == :human
    k = load_display_class
    k.new(resp, options).run(method)
  else
    parseable_output(format, resp)
  end
end
hcl_fields() click to toggle source
# File lib/wavefront-cli/base.rb, line 324
def hcl_fields
  []
end
import_to_create(raw) click to toggle source

Most things will re-import with the POST method if you remove the ID.

# File lib/wavefront-cli/base.rb, line 564
def import_to_create(raw)
  raw.each_with_object({}) do |(k, v), a|
    a[k.to_sym] = v unless k == :id
  end
rescue StandardError => e
  puts e if options[:debug]
  raise WavefrontCli::Exception::UnparseableInput
end
item_dump_call() click to toggle source

Broken out into its own method because 'users' does not use pagination

# File lib/wavefront-cli/base.rb, line 397
def item_dump_call
  wf.list(ALL_PAGE_SIZE, :all).response.items
end
load_display_class() click to toggle source
# File lib/wavefront-cli/base.rb, line 328
def load_display_class
  require_relative File.join('display', klass_word)
  Object.const_get(display_class)
end
matching_method(cond) click to toggle source

@param cond [String] a search condition, like “key=value” @return [Hash] of matchingMethod and negated

rubocop:disable Metrics/MethodLength

# File lib/wavefront-cli/base.rb, line 491
def matching_method(cond)
  case cond
  when /^\w+~/
    { matchingMethod: 'CONTAINS', negated: false }
  when /^\w+!~/
    { matchingMethod: 'CONTAINS', negated: true }
  when /^\w+=/
    { matchingMethod: 'EXACT', negated: false }
  when /^\w+!=/
    { matchingMethod: 'EXACT', negated: true }
  when /^\w+\^/
    { matchingMethod: 'STARTSWITH', negated: false }
  when /^\w+!\^/
    { matchingMethod: 'STARTSWITH', negated: true }
  else
    raise(WavefrontCli::Exception::UnparseableSearchPattern, cond)
  end
end
method_word_list() click to toggle source

Take a list of do_ methods, remove the 'do_' from their name, and break them into arrays of '_' separated words. The array is sorted by length, longest first.

# File lib/wavefront-cli/base.rb, line 207
def method_word_list
  do_methods = methods.select { |m| m.to_s.start_with?('do_') }
  do_methods.map { |m| m.to_s.split('_')[1..-1] }.sort_by(&:length)
end
mk_creds() click to toggle source

Make a wavefront-sdk credentials object from standard options.

@return [Hash] containing `token` and `endpoint`.

# File lib/wavefront-cli/base.rb, line 133
def mk_creds
  { token: options[:token],
    endpoint: options[:endpoint],
    agent: "wavefront-cli-#{WF_CLI_VERSION}" }
end
mk_opts() click to toggle source

Make a common wavefront-sdk options object from standard CLI options. We force verbosity on for a noop, otherwise we get no output.

@return [Hash] containing `debug`, `verbose`, and `noop`.

# File lib/wavefront-cli/base.rb, line 145
def mk_opts
  ret = { debug: options[:debug],
          noop: options[:noop] }

  ret[:verbose] = options[:noop] ? true : options[:verbose]
  ret[:raw_response] = true if options[:format] == 'raw'

  ret.merge!(extra_options) if respond_to?(:extra_options)
  ret
end
name_of_do_method(word_list) click to toggle source
# File lib/wavefront-cli/base.rb, line 199
def name_of_do_method(word_list)
  (%w[do] + word_list).join('_')
end
no_api_response() click to toggle source

Some subcommands don't make an API call, so they don't return a Wavefront::Response object. You can override this method with something which returns an array of methods like that. They will bypass the usual response checking.

@return [Array] methods which do not include an API response

# File lib/wavefront-cli/base.rb, line 63
def no_api_response
  []
end
ok_exit(message) click to toggle source

Print a message and exit 0

# File lib/wavefront-cli/base.rb, line 73
def ok_exit(message)
  puts message
  exit 0
end
one_or_all() click to toggle source

Return a detailed description of one item, if an ID has been given, or all items if it has not.

# File lib/wavefront-cli/base.rb, line 514
def one_or_all
  if options[:'<id>']
    resp = wf.describe(options[:'<id>'])
    data = [resp.response]
  else
    options[:all] = true
    resp = do_list
    data = resp.response.items
  end

  [resp, data]
end
options_and_exit() click to toggle source
# File lib/wavefront-cli/base.rb, line 67
def options_and_exit
  ok_exit(options)
end
parseable_output(output_format, resp) click to toggle source
# File lib/wavefront-cli/base.rb, line 304
def parseable_output(output_format, resp)
  options[:class] = klass_word
  options[:hcl_fields] = hcl_fields
  cli_output_class(output_format).new(resp, options).run
rescue LoadError
  raise(WavefrontCli::Exception::UnsupportedOutput,
        unsupported_format_message(output_format))
end
range_hash() click to toggle source

If the user has specified –all, override any limit and offset values

rubocop:disable Metrics/MethodLength

# File lib/wavefront-cli/base.rb, line 449
def range_hash
  offset_key = :offset

  if options[:all]
    limit  = :all
    offset = ALL_PAGE_SIZE
  elsif options[:cursor]
    offset_key = :cursor
    limit = options[:limit]
    offset = options[:cursor]
  else
    limit  = options[:limit]
    offset = options[:offset]
  end

  { limit: limit, offset_key => offset }
end
require_sdk_class() click to toggle source
# File lib/wavefront-cli/base.rb, line 44
def require_sdk_class
  require File.join('wavefront-sdk', @klass_word)
end
run() click to toggle source
# File lib/wavefront-cli/base.rb, line 78
def run
  @wf = klass.new(mk_creds, mk_opts)
  dispatch
end
search_key() click to toggle source

The search URI pattern doesn't always match the command name, or class name. Override this method if this is the case.

# File lib/wavefront-cli/base.rb, line 471
def search_key
  klass_word
end
smart_delete(object_type = klass_word) click to toggle source

Some objects support soft deleting. To handle that, call this method from do_delete

# File lib/wavefront-cli/base.rb, line 413
def smart_delete(object_type = klass_word)
  cannot_noop!
  puts smart_delete_message(object_type)
  wf.delete(options[:'<id>'])
end
smart_delete_message(object_type) click to toggle source
# File lib/wavefront-cli/base.rb, line 419
def smart_delete_message(object_type)
  desc = wf.describe(options[:'<id>'])
  word = desc.ok? ? 'Soft' : 'Permanently'
  format("%<soft_or_hard>s deleting %<object>s '%<id>s'",
         soft_or_hard: word,
         object: object_type,
         id: options[:'<id>'])
end
status_error_handler(data, method) click to toggle source
# File lib/wavefront-cli/base.rb, line 240
def status_error_handler(data, method)
  return if check_status(data.status)

  handle_error(method, data.status.code) if format_var == :human
  display_api_error(data.status)
end
unsupported_format_message(output_format) click to toggle source
# File lib/wavefront-cli/base.rb, line 319
def unsupported_format_message(output_format)
  format("The '%<command>s' command does not support '%<format>s' output.",
         command: options[:class], format: output_format)
end
validate_id() click to toggle source
# File lib/wavefront-cli/base.rb, line 112
def validate_id
  send(validator_method, options[:'<id>'])
rescue validator_exception
  abort failed_validation_message(options[:'<id>'])
end
validate_input() click to toggle source
# File lib/wavefront-cli/base.rb, line 97
def validate_input
  validate_id if options[:'<id>']
  validate_tags if options[:'<tag>']
  extra_validation if respond_to?(:extra_validation)
end
validate_opts() click to toggle source

There are things we need to have. If we don't have them, stop the user right now. Also, if we're in debug mode, print out a hash of options, which can be very useful when doing actual debugging. Some classes may have to override this method. The writer, for instance, uses a proxy and has no token.

# File lib/wavefront-cli/base.rb, line 343
def validate_opts
  unless options[:token]
    raise(WavefrontCli::Exception::CredentialError, 'Missing API token.')
  end

  return true if options[:endpoint]

  raise(WavefrontCli::Exception::CredentialError, 'Missing API endpoint.')
end
validate_tags(key = :'<tag>') click to toggle source
# File lib/wavefront-cli/base.rb, line 103
def validate_tags(key = :'<tag>')
  Array(options[key]).each do |t|
    send(:wf_tag?, t)
  rescue Wavefront::Exception::InvalidTag
    raise(WavefrontCli::Exception::InvalidInput,
          "'#{t}' is not a valid tag.")
  end
end
validator_exception() click to toggle source
# File lib/wavefront-cli/base.rb, line 91
def validator_exception
  Object.const_get(
    "Wavefront::Exception::Invalid#{klass_word.capitalize}Id"
  )
end
validator_method() click to toggle source

We normally validate with a predictable method name. Alert IDs are validated with wf_alert_id? etc. If you need to change that, override this method.

# File lib/wavefront-cli/base.rb, line 87
def validator_method
  "wf_#{klass_word}_id?".to_sym
end
warning_message(status) click to toggle source
# File lib/wavefront-cli/base.rb, line 273
def warning_message(status)
  return unless status.code.between?(201, 299)

  puts format("API WARNING: '%<message>s'.", message: status.message)
end