Class: WavefrontCli::Base
- Inherits:
-
Object
- Object
- WavefrontCli::Base
- Includes:
- Wavefront::Validators, Constants
- 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.
Direct Known Subclasses
Alert, ApiToken, CloudIntegration, Config, Dashboard, DerivedMetric, Event, ExternalLink, Integration, MaintenanceWindow, Message, Metric, Notificant, Proxy, Query, SavedSearch, Settings, Source, User, UserGroup, Webhook, Write
Constant Summary
Constants included from Constants
Constants::ALL_PAGE_SIZE, Constants::DEFAULT_CONFIG, Constants::DEFAULT_OPTS, Constants::HUMAN_TIME_FORMAT, Constants::HUMAN_TIME_FORMAT_MS, Constants::SEARCH_SPLIT
Instance Attribute Summary collapse
-
#klass ⇒ Object
Returns the value of attribute klass.
-
#klass_word ⇒ Object
Returns the value of attribute klass_word.
-
#options ⇒ Object
Returns the value of attribute options.
-
#wf ⇒ Object
Returns the value of attribute wf.
Instance Method Summary collapse
-
#_sdk_class ⇒ Object
Normally we map the class name to a similar one in the SDK.
-
#cannot_noop! ⇒ Object
Operations which do require multiple operations cannot be perormed as a no-op.
- #check_status(status) ⇒ Object
-
#conds_to_query(conds) ⇒ Array[Hash]
Turn a list of search conditions into an API query.
-
#dispatch ⇒ nil
Works out the user’s command by matching any options docopt has set to ‘true’ with any ‘do_’ method in the class.
-
#display(data, method) ⇒ Object
Display a Ruby object as JSON, YAML, or human-readable.
-
#display_api_error(status) ⇒ Object
Classes can provide methods which give the user information on a given error code.
- #display_no_api_response(data, method) ⇒ Object
- #do_delete ⇒ Object
- #do_describe ⇒ Object
- #do_import ⇒ Object
-
#do_list ⇒ Object
Below here are common methods.
- #do_search(cond = ) ⇒ Object
- #do_set ⇒ Object
- #do_undelete ⇒ Object
-
#extract_values(obj, key, aggr = []) ⇒ Array
A recursive function which fetches list of values from a nested hash.
- #failed_validation_message(input) ⇒ Object
-
#format_var ⇒ Symbol
To allow a user to default to different output formats for different object, we are able to define a format for each class.
-
#handle_error(method, code) ⇒ Object
This gives us a chance to catch different errors in WavefrontDisplay classes.
- #handle_response(resp, format, method) ⇒ Object
-
#hcl_fields ⇒ Object
rubocop:enable Metrics/AbcSize.
-
#import_to_create(raw) ⇒ Object
Most things will re-import with the POST method if you remove the ID.
- #import_update(raw) ⇒ Object
-
#initialize(options) ⇒ Base
constructor
A new instance of Base.
- #load_display_class ⇒ Object
-
#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.
-
#load_from_stdin ⇒ Object
Read STDIN and return a Ruby object, assuming that STDIN is valid JSON or YAML.
-
#matching_method(cond) ⇒ Hash
rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/MethodLength.
-
#mk_creds ⇒ Hash
Make a wavefront-sdk credentials object from standard options.
-
#mk_opts ⇒ Hash
Make a common wavefront-sdk options object from standard CLI options.
-
#no_api_response ⇒ Array[String]
Some subcommands don’t make an API call, so they don’t return a Wavefront::Response object.
-
#ok_exit(message) ⇒ Object
Print a message and exit 0.
-
#one_or_all ⇒ Object
Return a detailed description of one item, if an ID has been given, or all items if it has not.
- #options_and_exit ⇒ Object
-
#parseable_output(format, resp) ⇒ Object
rubocop:disable Metrics/AbcSize.
-
#range_hash ⇒ Object
If the user has specified –all, override any limit and offset values.
- #run ⇒ Object
-
#search_key ⇒ Object
The search URI pattern doesn’t always match the command name, or class name.
-
#smart_delete(object_type = klass_word) ⇒ Object
Some objects support soft deleting.
- #smart_delete_message(object_type) ⇒ Object
- #validate_id ⇒ Object
- #validate_input ⇒ Object
-
#validate_opts ⇒ Object
There are things we need to have.
- #validate_tags(key = :'<tag>') ⇒ Object
- #validator_exception ⇒ Object
-
#validator_method ⇒ Object
We normally validate with a predictable method name.
Constructor Details
#initialize(options) ⇒ Base
Returns a new instance of Base.
28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/wavefront-cli/base.rb', line 28 def initialize() @options = sdk_class = _sdk_class @klass_word = sdk_class.split('::').last.downcase validate_input if [:help] require File.join('wavefront-sdk', @klass_word) @klass = Object.const_get(sdk_class) send(:post_initialize, ) if respond_to?(:post_initialize) end |
Instance Attribute Details
#klass ⇒ Object
Returns the value of attribute klass.
23 24 25 |
# File 'lib/wavefront-cli/base.rb', line 23 def klass @klass end |
#klass_word ⇒ Object
Returns the value of attribute klass_word.
23 24 25 |
# File 'lib/wavefront-cli/base.rb', line 23 def klass_word @klass_word end |
#options ⇒ Object
Returns the value of attribute options.
23 24 25 |
# File 'lib/wavefront-cli/base.rb', line 23 def @options end |
#wf ⇒ Object
Returns the value of attribute wf.
23 24 25 |
# File 'lib/wavefront-cli/base.rb', line 23 def wf @wf end |
Instance Method Details
#_sdk_class ⇒ Object
Normally we map the class name to a similar one in the SDK. Overriding his method lets you map to something else.
45 46 47 |
# File 'lib/wavefront-cli/base.rb', line 45 def _sdk_class self.class.name.sub(/Cli/, '') end |
#cannot_noop! ⇒ Object
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
532 533 534 |
# File 'lib/wavefront-cli/base.rb', line 532 def cannot_noop! raise WavefrontCli::Exception::UnsupportedNoop if [:noop] end |
#check_status(status) ⇒ Object
255 256 257 |
# File 'lib/wavefront-cli/base.rb', line 255 def check_status(status) status.respond_to?(:result) && status.result == 'OK' end |
#conds_to_query(conds) ⇒ Array[Hash]
Turn a list of search conditions into an API query
470 471 472 473 474 475 |
# File 'lib/wavefront-cli/base.rb', line 470 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 |
#dispatch ⇒ nil
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.
rubocop:disable Metrics/AbcSize
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/wavefront-cli/base.rb', line 168 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| [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.
rubocop:disable Metrics/AbcSize
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/wavefront-cli/base.rb', line 212 def display(data, method) if no_api_response.include?(method) return display_no_api_response(data, method) end exit if [:noop] %i[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 display_api_error(data.status) end handle_response(data.response, format_var, method) end |
#display_api_error(status) ⇒ Object
Classes can provide methods which give the user information on a given error code. They are named #handle_errcode_xxx, and return a string.
238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/wavefront-cli/base.rb', line 238 def display_api_error(status) method = format('handle_errcode_%s', status.code).to_sym msg = if respond_to?(method) send(method, status) else status. || 'No further information' end abort format('ERROR: API code %s. %s.', status.code, msg.chomp('.')).fold(TW, 7) end |
#display_no_api_response(data, method) ⇒ Object
251 252 253 |
# File 'lib/wavefront-cli/base.rb', line 251 def display_no_api_response(data, method) handle_response(data, format_var, method) end |
#do_delete ⇒ Object
407 408 409 |
# File 'lib/wavefront-cli/base.rb', line 407 def do_delete wf.delete([:'<id>']) end |
#do_describe ⇒ Object
382 383 384 |
# File 'lib/wavefront-cli/base.rb', line 382 def do_describe wf.describe([:'<id>']) end |
#do_import ⇒ Object
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/wavefront-cli/base.rb', line 386 def do_import raw = load_file([:'<file>']) begin prepped = import_to_create(raw) rescue StandardError => e puts e if [:debug] raise WavefrontCli::Exception::UnparseableInput end if [:update] import_update(raw) else wf.create(prepped) end end |
#do_list ⇒ Object
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.
372 373 374 375 376 377 378 379 380 |
# File 'lib/wavefront-cli/base.rb', line 372 def do_list list = if [:all] wf.list(ALL_PAGE_SIZE, :all) else wf.list([:offset] || 0, [:limit] || 100) end respond_to?(:list_filter) ? list_filter(list) : list end |
#do_search(cond = ) ⇒ Object
436 437 438 439 440 441 |
# File 'lib/wavefront-cli/base.rb', line 436 def do_search(cond = [:'<condition>']) require 'wavefront-sdk/search' wfs = Wavefront::Search.new(mk_creds, mk_opts) query = conds_to_query(cond) wfs.search(search_key, query, range_hash) end |
#do_set ⇒ Object
430 431 432 433 434 |
# File 'lib/wavefront-cli/base.rb', line 430 def do_set cannot_noop! k, v = [:'<key=value>'].split('=', 2) wf.update([:'<id>'], k => v) end |
#do_undelete ⇒ Object
426 427 428 |
# File 'lib/wavefront-cli/base.rb', line 426 def do_undelete wf.undelete([:'<id>']) end |
#extract_values(obj, key, aggr = []) ⇒ Array
A recursive function which fetches list of values from a nested hash. Used by WavefrontCli::Dashboard#do_queries
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 |
# File 'lib/wavefront-cli/base.rb', line 543 def extract_values(obj, key, aggr = []) if obj.is_a?(Hash) obj.each_pair do |k, v| if k == key && !v.to_s.empty? aggr.<< v else extract_values(v, key, aggr) end end elsif obj.is_a?(Array) obj.each { |e| extract_values(e, key, aggr) } end aggr end |
#failed_validation_message(input) ⇒ Object
114 115 116 |
# File 'lib/wavefront-cli/base.rb', line 114 def (input) format("'%s' is not a valid %s ID.", input, klass_word) end |
#format_var ⇒ Symbol
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.
153 154 155 156 157 |
# File 'lib/wavefront-cli/base.rb', line 153 def format_var [:format].to_sym rescue NoMethodError :human end |
#handle_error(method, code) ⇒ Object
This gives us a chance to catch different errors in WavefrontDisplay classes. If nothing catches, them abort.
262 263 264 265 |
# File 'lib/wavefront-cli/base.rb', line 262 def handle_error(method, code) k = load_display_class k.new({}, ).run_error([method, code].join('_')) end |
#handle_response(resp, format, method) ⇒ Object
267 268 269 270 271 272 273 274 |
# File 'lib/wavefront-cli/base.rb', line 267 def handle_response(resp, format, method) if format == :human k = load_display_class k.new(resp, ).run(method) else parseable_output(format, resp) end end |
#hcl_fields ⇒ Object
rubocop:enable Metrics/AbcSize
291 292 293 |
# File 'lib/wavefront-cli/base.rb', line 291 def hcl_fields [] end |
#import_to_create(raw) ⇒ Object
Most things will re-import with the POST method if you remove the ID.
506 507 508 509 510 |
# File 'lib/wavefront-cli/base.rb', line 506 def import_to_create(raw) raw.each_with_object({}) do |(k, v), a| a[k.to_sym] = v unless k == 'id' end end |
#import_update(raw) ⇒ Object
403 404 405 |
# File 'lib/wavefront-cli/base.rb', line 403 def import_update(raw) wf.update(raw['id'], raw, false) end |
#load_display_class ⇒ Object
295 296 297 298 |
# File 'lib/wavefront-cli/base.rb', line 295 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. If path is ‘-’ then it will read STDIN.
rubocop:disable Metrics/AbcSize
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/wavefront-cli/base.rb', line 329 def load_file(path) return load_from_stdin if path == '-' file = Pathname.new(path) raise WavefrontCli::Exception::FileNotFound 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 WavefrontCli::Exception::UnsupportedFileFormat end end |
#load_from_stdin ⇒ Object
Read STDIN and return a Ruby object, assuming that STDIN is valid JSON or YAML. This is a dumb method, it does no buffering, so STDIN must be a single block of data. This appears to be a valid assumption for use-cases of this CLI.
355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/wavefront-cli/base.rb', line 355 def load_from_stdin raw = STDIN.read if raw.start_with?('---') YAML.safe_load(raw) else JSON.parse(raw) end rescue RuntimeError raise Wavefront::Exception::UnparseableInput end |
#matching_method(cond) ⇒ Hash
rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/MethodLength
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 |
# File 'lib/wavefront-cli/base.rb', line 482 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 |
#mk_creds ⇒ Hash
Make a wavefront-sdk credentials object from standard options.
123 124 125 126 127 |
# File 'lib/wavefront-cli/base.rb', line 123 def mk_creds { token: [:token], endpoint: [:endpoint], agent: "wavefront-cli-#{WF_CLI_VERSION}" } end |
#mk_opts ⇒ Hash
Make a common wavefront-sdk options object from standard CLI options. We force verbosity on for a noop, otherwise we get no output.
135 136 137 138 139 140 141 142 143 |
# File 'lib/wavefront-cli/base.rb', line 135 def mk_opts ret = { debug: [:debug], noop: [:noop] } ret[:verbose] = [:noop] ? true : [:verbose] ret.merge!() if respond_to?(:extra_options) ret end |
#no_api_response ⇒ Array[String]
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.
response
57 58 59 |
# File 'lib/wavefront-cli/base.rb', line 57 def no_api_response [] end |
#ok_exit(message) ⇒ Object
Print a message and exit 0
67 68 69 70 |
# File 'lib/wavefront-cli/base.rb', line 67 def ok_exit() puts exit 0 end |
#one_or_all ⇒ Object
Return a detailed description of one item, if an ID has been given, or all items if it has not.
515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'lib/wavefront-cli/base.rb', line 515 def one_or_all if [:'<id>'] resp = wf.describe([:'<id>']) data = [resp.response] else [:all] = true resp = do_list data = resp.response.items end [resp, data] end |
#options_and_exit ⇒ Object
61 62 63 |
# File 'lib/wavefront-cli/base.rb', line 61 def ok_exit() end |
#parseable_output(format, resp) ⇒ Object
rubocop:disable Metrics/AbcSize
277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/wavefront-cli/base.rb', line 277 def parseable_output(format, resp) [:class] = klass_word [:hcl_fields] = hcl_fields require_relative File.join('output', format.to_s) oclass = Object.const_get(format('WavefrontOutput::%s', format.to_s.capitalize)) oclass.new(resp, ).run rescue LoadError raise(WavefrontCli::Exception::UnsupportedOutput, format("The '%s' command does not support '%s' output.", [:class], format)) end |
#range_hash ⇒ Object
If the user has specified –all, override any limit and offset values
446 447 448 449 450 451 452 453 454 455 456 |
# File 'lib/wavefront-cli/base.rb', line 446 def range_hash if [:all] limit = :all offset = ALL_PAGE_SIZE else limit = [:limit] offset = [:offset] || [:cursor] end { limit: limit, offset: offset } end |
#run ⇒ Object
72 73 74 75 |
# File 'lib/wavefront-cli/base.rb', line 72 def run @wf = klass.new(mk_creds, mk_opts) dispatch end |
#search_key ⇒ Object
The search URI pattern doesn’t always match the command name, or class name. Override this method if this is the case.
461 462 463 |
# File 'lib/wavefront-cli/base.rb', line 461 def search_key klass_word end |
#smart_delete(object_type = klass_word) ⇒ Object
Some objects support soft deleting. To handle that, call this method from do_delete
414 415 416 417 418 |
# File 'lib/wavefront-cli/base.rb', line 414 def smart_delete(object_type = klass_word) cannot_noop! puts (object_type) wf.delete([:'<id>']) end |
#smart_delete_message(object_type) ⇒ Object
420 421 422 423 424 |
# File 'lib/wavefront-cli/base.rb', line 420 def (object_type) desc = wf.describe([:'<id>']) word = desc.ok? ? 'Soft' : 'Permanently' format("%s deleting %s '%s'", word, object_type, [:'<id>']) end |
#validate_id ⇒ Object
108 109 110 111 112 |
# File 'lib/wavefront-cli/base.rb', line 108 def validate_id send(validator_method, [:'<id>']) rescue validator_exception abort ([:'<id>']) end |
#validate_input ⇒ Object
91 92 93 94 95 |
# File 'lib/wavefront-cli/base.rb', line 91 def validate_input validate_id if [:'<id>'] if [:'<tag>'] send(:extra_validation) if respond_to?(:extra_validation) end |
#validate_opts ⇒ Object
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.
306 307 308 309 310 311 312 313 314 315 |
# File 'lib/wavefront-cli/base.rb', line 306 def validate_opts unless [:token] raise(WavefrontCli::Exception::CredentialError, 'Missing API token.') end return true if [:endpoint] raise(WavefrontCli::Exception::CredentialError, 'Missing API endpoint.') end |
#validate_tags(key = :'<tag>') ⇒ Object
97 98 99 100 101 102 103 104 105 106 |
# File 'lib/wavefront-cli/base.rb', line 97 def (key = :'<tag>') Array([key]).each do |t| begin send(:wf_tag?, t) rescue Wavefront::Exception::InvalidTag raise(WavefrontCli::Exception::InvalidInput, "'#{t}' is not a valid tag.") end end end |
#validator_exception ⇒ Object
85 86 87 88 89 |
# File 'lib/wavefront-cli/base.rb', line 85 def validator_exception Object.const_get( "Wavefront::Exception::Invalid#{klass_word.capitalize}Id" ) end |
#validator_method ⇒ Object
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.
81 82 83 |
# File 'lib/wavefront-cli/base.rb', line 81 def validator_method "wf_#{klass_word}_id?".to_sym end |