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.



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

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.



24
25
26
# File 'lib/wavefront-cli/base.rb', line 24

def klass
  @klass
end

#klass_wordObject

Returns the value of attribute klass_word.



24
25
26
# File 'lib/wavefront-cli/base.rb', line 24

def klass_word
  @klass_word
end

#optionsObject

Returns the value of attribute options.



24
25
26
# File 'lib/wavefront-cli/base.rb', line 24

def options
  @options
end

#wfObject

Returns the value of attribute wf.



24
25
26
# File 'lib/wavefront-cli/base.rb', line 24

def wf
  @wf
end

Instance Method Details

#check_status(status) ⇒ Object



187
188
189
# File 'lib/wavefront-cli/base.rb', line 187

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.



126
127
128
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
# File 'lib/wavefront-cli/base.rb', line 126

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:

  • data (WavefrontResponse)

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

  • method (String)

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



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/wavefront-cli/base.rb', line 168

def display(data, method)
  [: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



280
281
282
# File 'lib/wavefront-cli/base.rb', line 280

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

#do_describeObject



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

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

#do_importObject



267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/wavefront-cli/base.rb', line 267

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.



259
260
261
# File 'lib/wavefront-cli/base.rb', line 259

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

#do_tag_addObject



297
298
299
# File 'lib/wavefront-cli/base.rb', line 297

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

#do_tag_clearObject



309
310
311
# File 'lib/wavefront-cli/base.rb', line 309

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

#do_tag_deleteObject



301
302
303
# File 'lib/wavefront-cli/base.rb', line 301

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

#do_tag_setObject



305
306
307
# File 'lib/wavefront-cli/base.rb', line 305

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

#do_tagsObject



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

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

#do_undeleteObject



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

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

#do_updateObject



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

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 define a format for each class. For instance, alertformat or agentformat. This method returns such a string appropriate for the inheriting class.

Returns:

  • (Symbol)

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



113
114
115
116
# File 'lib/wavefront-cli/base.rb', line 113

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.



194
195
196
197
# File 'lib/wavefront-cli/base.rb', line 194

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

#handle_response(resp, format, method) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/wavefront-cli/base.rb', line 199

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.



316
317
318
# File 'lib/wavefront-cli/base.rb', line 316

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

#load_display_classObject



215
216
217
218
# File 'lib/wavefront-cli/base.rb', line 215

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:

  • path (String)

    the file to load

Returns:

  • (Hash)

    a Ruby object of the loaded file

Raises:

  • pass through any error loading or parsing the file



241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/wavefront-cli/base.rb', line 241

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:

  • (Hash)

    containing token and endpoint.



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

def mk_creds
  { token: options[:token], endpoint: options[:endpoint] }
end

#mk_optsHash

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

Returns:

  • (Hash)

    containing debug, verbose, and noop.



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

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

#runObject



45
46
47
48
# File 'lib/wavefront-cli/base.rb', line 45

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

#validate_idObject



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

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

#validate_inputObject



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

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.



226
227
228
229
# File 'lib/wavefront-cli/base.rb', line 226

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

#validate_tagsObject



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

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



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

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.



54
55
56
# File 'lib/wavefront-cli/base.rb', line 54

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