Class: WavefrontCli::Base

Inherits:
Object
  • Object
show all
Includes:
Wavefront::Validators
Defined in:
lib/wavefront-cli/base.rb

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Base

Returns a new instance of Base.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/wavefront-cli/base.rb', line 26

def initialize(options)
  @options = options
  sdk_class = self.class.name.sub(/Cli/, '')
  @klass_word = sdk_class.split('::').last.downcase
  validate_input

  if options.include?(:help) && options[:help]
    puts options
    exit 0
  end

  require File.join('wavefront-sdk', @klass_word)
  @klass = Object.const_get(sdk_class)

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

Instance Attribute Details

#klassObject

Returns the value of attribute klass.



22
23
24
# File 'lib/wavefront-cli/base.rb', line 22

def klass
  @klass
end

#klass_wordObject

Returns the value of attribute klass_word.



22
23
24
# File 'lib/wavefront-cli/base.rb', line 22

def klass_word
  @klass_word
end

#optionsObject

Returns the value of attribute options.



22
23
24
# File 'lib/wavefront-cli/base.rb', line 22

def options
  @options
end

#wfObject

Returns the value of attribute wf.



22
23
24
# File 'lib/wavefront-cli/base.rb', line 22

def wf
  @wf
end

Instance Method Details

#check_status(status) ⇒ Object



192
193
194
# File 'lib/wavefront-cli/base.rb', line 192

def check_status(status)
  status.respond_to?(:result) && status.result == 'OK'
end

#dispatchnil

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.

Returns:

Raises:



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/wavefront-cli/base.rb', line 129

def dispatch
  #
  # Take a list of do_ methods, remove the 'do_' from their name,
  # and break them into arrays of '_' separated words.
  #
  m_list = methods.select { |m| m.to_s.start_with?('do_') }.map do |m|
    m.to_s.split('_')[1..-1]
  end

  # Sort that array of arrays by length, longest first.  Then 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".
  #
  m_list.sort_by(&:length).reverse.each do |m|
    if m.reject { |w| options[w.to_sym] }.empty?
      method = (%w(do) + m).join('_')
      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) ⇒ Object

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.

Parameters:

  • an object returned by a Wavefront SDK method. This will contain a ‘response’ and ‘status’ structures.

  • the name of the method which produced this output. Used to find a suitable humanize method.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/wavefront-cli/base.rb', line 171

def display(data, method)
  exit if options[:noop]

  [:status, :response].each do |b|
    abort "no #{b} block in API response" unless data.respond_to?(b)
  end

  unless check_status(data.status)
    handle_error(method, data.status.code) if format_var == :human
    abort "API #{data.status.code}: #{data.status.message}."
  end

  resp = if data.response.respond_to?(:items)
           data.response.items
         else
           data.response
         end

  handle_response(resp, format_var, method)
end

#do_deleteObject



285
286
287
# File 'lib/wavefront-cli/base.rb', line 285

def do_delete
  wf.delete(options[:'<id>'])
end

#do_describeObject



268
269
270
# File 'lib/wavefront-cli/base.rb', line 268

def do_describe
  wf.describe(options[:'<id>'])
end

#do_importObject



272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/wavefront-cli/base.rb', line 272

def do_import
  raw = load_file(options[:'<file>'])

  begin
    prepped = import_to_create(raw)
  rescue => e
    puts e if options[:debug]
    raise 'could not parse input.'
  end

  wf.create(prepped)
end

#do_listObject

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.



264
265
266
# File 'lib/wavefront-cli/base.rb', line 264

def do_list
  wf.list(options[:offset] || 0, options[:limit] || 100)
end

#do_searchObject



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/wavefront-cli/base.rb', line 298

def do_search
  require 'wavefront-sdk/search'
  wfs = Wavefront::Search.new(mk_creds, mk_opts)

  query = options[:'<condition>'].each_with_object([]) do |c, aggr|
    key, value = c.split(/\W/, 2)
    q = { key: key, value: value }
    q[:matchingMethod] = 'EXACT' if c.start_with?("#{key}=")
    q[:matchingMethod] = 'STARTSWITH' if c.start_with?("#{key}^")
    aggr.<< q
  end

  wfs.search(klass_word, query, { limit: options[:limit],
                                  offset: options[:offset] || options[:cursor]})
end

#do_tag_addObject



318
319
320
# File 'lib/wavefront-cli/base.rb', line 318

def do_tag_add
  wf.tag_add(options[:'<id>'], options[:'<tag>'].first)
end

#do_tag_clearObject



330
331
332
# File 'lib/wavefront-cli/base.rb', line 330

def do_tag_clear
  wf.tag_set(options[:'<id>'], [])
end

#do_tag_deleteObject



322
323
324
# File 'lib/wavefront-cli/base.rb', line 322

def do_tag_delete
  wf.tag_delete(options[:'<id>'], options[:'<tag>'].first)
end

#do_tag_setObject



326
327
328
# File 'lib/wavefront-cli/base.rb', line 326

def do_tag_set
  wf.tag_set(options[:'<id>'], options[:'<tag>'])
end

#do_tagsObject



314
315
316
# File 'lib/wavefront-cli/base.rb', line 314

def do_tags
  wf.tags(options[:'<id>'])
end

#do_undeleteObject



289
290
291
# File 'lib/wavefront-cli/base.rb', line 289

def do_undelete
  wf.undelete(options[:'<id>'])
end

#do_updateObject



293
294
295
296
# File 'lib/wavefront-cli/base.rb', line 293

def do_update
  k, v = options[:'<key=value>'].split('=')
  wf.update(options[:'<id>'], k => v)
end

#format_varSymbol

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

Returns:

  • name of the option or config-file key which sets the default output format for this class



116
117
118
119
# File 'lib/wavefront-cli/base.rb', line 116

def format_var
  options[:format].to_sym
  # (self.class.name.split('::').last.downcase + 'format').to_sym
end

#handle_error(method, code) ⇒ Object

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



199
200
201
202
# File 'lib/wavefront-cli/base.rb', line 199

def handle_error(method, code)
  k = load_display_class
  k.new({}, options).run_error([method, code].join('_'))
end

#handle_response(resp, format, method) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/wavefront-cli/base.rb', line 204

def handle_response(resp, format, method)
  case format
  when :json
    puts resp.to_json
  when :yaml # We don't want the YAML keys to be symbols.
    puts JSON.parse(resp.to_json).to_yaml
  when :ruby
    p resp
  when :human
    k = load_display_class
    k.new(resp, options).run(method)
  else
    raise "Unknown output format '#{format}'."
  end
end

#import_to_create(raw) ⇒ Object

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



337
338
339
# File 'lib/wavefront-cli/base.rb', line 337

def import_to_create(raw)
  raw.delete_if { |k, _v| k == 'id' }
end

#load_display_classObject



220
221
222
223
# File 'lib/wavefront-cli/base.rb', line 220

def load_display_class
  require_relative File.join('display', klass_word)
  Object.const_get(klass.name.sub('Wavefront', 'WavefrontDisplay'))
end

#load_file(path) ⇒ Hash

Give it a path to a file (as a string) and it will return the contents of that file as a Ruby object. Automatically detects JSON and YAML. Raises an exception if it doesn’t look like either.

Parameters:

  • the file to load

Returns:

  • a Ruby object of the loaded file

Raises:

  • pass through any error loading or parsing the file



246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/wavefront-cli/base.rb', line 246

def load_file(path)
  file = Pathname.new(path)
  raise 'Import file does not exist.' unless file.exist?

  if file.extname == '.json'
    JSON.parse(IO.read(file))
  elsif file.extname == '.yaml' || file.extname == '.yml'
    YAML.safe_load(IO.read(file))
  else
    raise 'Unsupported file format.'
  end
end

#mk_credsHash

Make a wavefront-sdk credentials object from standard options.

Returns:

  • containing token and endpoint.



89
90
91
92
93
94
# File 'lib/wavefront-cli/base.rb', line 89

def mk_creds
  { token:    options[:token],
    endpoint: options[:endpoint],
    agent:    "wavefront-cli-#{WF_CLI_VERSION}"
  }
end

#mk_optsHash

Make a common wavefront-sdk options object from standard CLI options.

Returns:

  • containing debug, verbose, and noop.



101
102
103
104
105
106
# File 'lib/wavefront-cli/base.rb', line 101

def mk_opts
  { debug:   options[:debug],
    verbose: options[:verbose],
    noop:    options[:noop],
  }
end

#runObject



43
44
45
46
# File 'lib/wavefront-cli/base.rb', line 43

def run
  @wf = klass.new(mk_creds, mk_opts)
  dispatch
end

#validate_idObject



78
79
80
81
82
# File 'lib/wavefront-cli/base.rb', line 78

def validate_id
  send(validator_method, options[:'<id>'])
rescue validator_exception
  abort "'#{options[:'<id>']}' is not a valid #{klass_word} ID."
end

#validate_inputObject



62
63
64
65
66
# File 'lib/wavefront-cli/base.rb', line 62

def validate_input
  validate_id if options[:'<id>']
  validate_tags if options[:'<tag>']
  send(:extra_validation) if respond_to?(:extra_validation)
end

#validate_optsObject

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.



231
232
233
234
# File 'lib/wavefront-cli/base.rb', line 231

def validate_opts
  raise 'Please supply an API token.' unless options[:token]
  raise 'Please supply an API endpoint.' unless options[:endpoint]
end

#validate_tagsObject



68
69
70
71
72
73
74
75
76
# File 'lib/wavefront-cli/base.rb', line 68

def validate_tags
  Array(options[:'<tag>']).each do |t|
    begin
      send(:wf_tag?, t)
    rescue Wavefront::Exception::InvalidTag
      abort "'#{t}' is not a valid tag."
    end
  end
end

#validator_exceptionObject



56
57
58
59
60
# File 'lib/wavefront-cli/base.rb', line 56

def validator_exception
  Object.const_get(
    "Wavefront::Exception::Invalid#{klass_word.capitalize}Id"
  )
end

#validator_methodObject

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.



52
53
54
# File 'lib/wavefront-cli/base.rb', line 52

def validator_method
  "wf_#{klass_word}_id?".to_sym
end