wavefront-cli 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +20 -0
  3. data/.gitignore +4 -0
  4. data/.travis.yml +16 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +65 -0
  7. data/README.md +221 -0
  8. data/Rakefile +18 -0
  9. data/bin/wavefront +14 -0
  10. data/lib/wavefront-cli/alert.rb +60 -0
  11. data/lib/wavefront-cli/base.rb +320 -0
  12. data/lib/wavefront-cli/cloudintegration.rb +12 -0
  13. data/lib/wavefront-cli/commands/alert.rb +38 -0
  14. data/lib/wavefront-cli/commands/base.rb +105 -0
  15. data/lib/wavefront-cli/commands/dashboard.rb +29 -0
  16. data/lib/wavefront-cli/commands/event.rb +44 -0
  17. data/lib/wavefront-cli/commands/integration.rb +33 -0
  18. data/lib/wavefront-cli/commands/link.rb +34 -0
  19. data/lib/wavefront-cli/commands/message.rb +23 -0
  20. data/lib/wavefront-cli/commands/metric.rb +20 -0
  21. data/lib/wavefront-cli/commands/proxy.rb +25 -0
  22. data/lib/wavefront-cli/commands/query.rb +32 -0
  23. data/lib/wavefront-cli/commands/savedsearch.rb +32 -0
  24. data/lib/wavefront-cli/commands/source.rb +27 -0
  25. data/lib/wavefront-cli/commands/user.rb +24 -0
  26. data/lib/wavefront-cli/commands/webhook.rb +25 -0
  27. data/lib/wavefront-cli/commands/window.rb +33 -0
  28. data/lib/wavefront-cli/commands/write.rb +35 -0
  29. data/lib/wavefront-cli/constants.rb +17 -0
  30. data/lib/wavefront-cli/controller.rb +134 -0
  31. data/lib/wavefront-cli/dashboard.rb +27 -0
  32. data/lib/wavefront-cli/display/alert.rb +44 -0
  33. data/lib/wavefront-cli/display/base.rb +304 -0
  34. data/lib/wavefront-cli/display/cloudintegration.rb +18 -0
  35. data/lib/wavefront-cli/display/dashboard.rb +21 -0
  36. data/lib/wavefront-cli/display/event.rb +19 -0
  37. data/lib/wavefront-cli/display/externallink.rb +13 -0
  38. data/lib/wavefront-cli/display/maintenancewindow.rb +19 -0
  39. data/lib/wavefront-cli/display/message.rb +8 -0
  40. data/lib/wavefront-cli/display/metric.rb +22 -0
  41. data/lib/wavefront-cli/display/proxy.rb +13 -0
  42. data/lib/wavefront-cli/display/query.rb +69 -0
  43. data/lib/wavefront-cli/display/savedsearch.rb +17 -0
  44. data/lib/wavefront-cli/display/source.rb +26 -0
  45. data/lib/wavefront-cli/display/user.rb +16 -0
  46. data/lib/wavefront-cli/display/webhook.rb +24 -0
  47. data/lib/wavefront-cli/display/write.rb +19 -0
  48. data/lib/wavefront-cli/event.rb +162 -0
  49. data/lib/wavefront-cli/exception.rb +5 -0
  50. data/lib/wavefront-cli/externallink.rb +16 -0
  51. data/lib/wavefront-cli/maintenancewindow.rb +16 -0
  52. data/lib/wavefront-cli/message.rb +19 -0
  53. data/lib/wavefront-cli/metric.rb +24 -0
  54. data/lib/wavefront-cli/opt_handler.rb +62 -0
  55. data/lib/wavefront-cli/proxy.rb +22 -0
  56. data/lib/wavefront-cli/query.rb +74 -0
  57. data/lib/wavefront-cli/savedsearch.rb +24 -0
  58. data/lib/wavefront-cli/source.rb +20 -0
  59. data/lib/wavefront-cli/user.rb +25 -0
  60. data/lib/wavefront-cli/version.rb +1 -0
  61. data/lib/wavefront-cli/webhook.rb +8 -0
  62. data/lib/wavefront-cli/write.rb +244 -0
  63. data/spec/spec_helper.rb +197 -0
  64. data/spec/wavefront-cli/alert_spec.rb +44 -0
  65. data/spec/wavefront-cli/base_spec.rb +47 -0
  66. data/spec/wavefront-cli/cli_help_spec.rb +47 -0
  67. data/spec/wavefront-cli/cloudintegration_spec.rb +24 -0
  68. data/spec/wavefront-cli/dashboard_spec.rb +37 -0
  69. data/spec/wavefront-cli/event_spec.rb +19 -0
  70. data/spec/wavefront-cli/externallink_spec.rb +18 -0
  71. data/spec/wavefront-cli/maintanancewindow_spec.rb +19 -0
  72. data/spec/wavefront-cli/message_spec.rb +28 -0
  73. data/spec/wavefront-cli/metric_spec.rb +22 -0
  74. data/spec/wavefront-cli/proxy_spec.rb +26 -0
  75. data/spec/wavefront-cli/query_spec.rb +63 -0
  76. data/spec/wavefront-cli/resources/conf.yaml +10 -0
  77. data/spec/wavefront-cli/savedsearch_spec.rb +18 -0
  78. data/spec/wavefront-cli/source_spec.rb +18 -0
  79. data/spec/wavefront-cli/user_spec.rb +31 -0
  80. data/spec/wavefront-cli/webhook_spec.rb +17 -0
  81. data/wavefront-cli.gemspec +36 -0
  82. metadata +279 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cd9a4217e92b817721a7437e19ec3733bd06cd3c
4
+ data.tar.gz: '019e98583f888391f948e7f82266ff2cdb902a3b'
5
+ SHA512:
6
+ metadata.gz: fb04299e0ad36f4f4cc6c587483574859a93794a317e9c54a127b1821894dad3d25a34eade8ad81ce290aedc8b8df24d44b9853d062433d8e3a6c7d1343bb8f4
7
+ data.tar.gz: 926c982c2fccb55b043c8780662ab37e4f12481c91f24a3fc51d81c8e9a0226a4172522d46bef8db1d18769a7b5fb521c3d1332a22ce0f868a887c87bd5d2386
data/.codeclimate.yml ADDED
@@ -0,0 +1,20 @@
1
+ ---
2
+ engines:
3
+ duplication:
4
+ enabled: true
5
+ checks:
6
+ Similar code:
7
+ enabled: false
8
+ config:
9
+ languages:
10
+ - ruby
11
+ fixme:
12
+ enabled: true
13
+ rubocop:
14
+ enabled: true
15
+ ratings:
16
+ paths:
17
+ - "**.rb"
18
+ exclude_paths:
19
+ - spec/
20
+ - lib/_wavefront
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .yardoc
2
+ /doc
3
+ .*swp
4
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 2.2.7
5
+ - 2.3.4
6
+ - 2.4.1
7
+ before_install: gem install bundler --no-rdoc --no-ri
8
+ deploy:
9
+ provider: rubygems
10
+ api_key:
11
+ secure: dfmL5JwBn+u3cUmyAaDsApDa7ljGajGNz3GDcKd2J8FOt7+a758/lmL8EQ34sDT1ZFotrxn/y1RbgXlaDxAE1XDfrZbjckmx7a6wa2sqR3kBraJ2tx7CiXodbw3Z8XZf9WLb0kYGmlLtI73GNcuunMt/9f1cobqWISRLHw6b7amlO7GW2ZBZgzRS+N8TSS2dicIvKMo5HoMYU+uWLM4zDFBPnGNcMiWxh8ysLzJoKqA9kbBUyCVEZ03MlV7G71ObvWCLasKnZ3W5U+K1NbgU7mgMYfl9KIcA4y9hQ9hUCijk40SmT7ffy3P2gq8zblC/4x5Eefpau9X/bdLwXoRCIzqk05t4f45wstj2auHGK0HJwOYRtx8apdaLSgyJ5lQpGcbCRu40WR9mDkaM8m9n3u2o6GJmftCg3AN1QtsourmQB84x67LEbHzValMaokrbCol4XeWqlC+dCNLPixemQRBvcNfI3V9C6RqVGfjpoGlSTI+RkQqwm01PcxpeqIVfdMd1wnfUuAOywUO6UpvtK9TZaxg0NnVElXpPseQbtzulLwZ7R5Y3A4Ss8Z7w43c1KHxTkg54FWUOp065ItjAc4lmyORXq/2+F7sMvRN6dtCLaXTUlkYuU3cjFLIPlLGFYgqq4T4xQa+e5NEK1XW7nghv+IRfKfyVeZsB0WpY+uc=
12
+ gem: wavefront-cli
13
+ on:
14
+ tags: true
15
+ repo: snltd/wavefront-cli
16
+ ruby: 2.3.4
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,65 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ wavefront-cli (0.0.2)
5
+ docopt (= 0.5.0)
6
+ wavefront-sdk (~> 0.1.5)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.5.1)
12
+ public_suffix (~> 2.0, >= 2.0.2)
13
+ ast (2.3.0)
14
+ crack (0.4.3)
15
+ safe_yaml (~> 1.0.0)
16
+ docopt (0.5.0)
17
+ faraday (0.12.1)
18
+ multipart-post (>= 1.2, < 3)
19
+ hashdiff (0.3.2)
20
+ inifile (3.0.0)
21
+ map (6.6.0)
22
+ minitest (5.8.5)
23
+ multipart-post (2.0.0)
24
+ parser (2.4.0.0)
25
+ ast (~> 2.2)
26
+ powerpack (0.1.1)
27
+ public_suffix (2.0.5)
28
+ rainbow (2.2.1)
29
+ rake (12.0.0)
30
+ rubocop (0.47.1)
31
+ parser (>= 2.3.3.1, < 3.0)
32
+ powerpack (~> 0.1)
33
+ rainbow (>= 1.99.1, < 3.0)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (~> 1.0, >= 1.0.1)
36
+ ruby-progressbar (1.8.1)
37
+ safe_yaml (1.0.4)
38
+ spy (0.4.5)
39
+ unicode-display_width (1.1.3)
40
+ wavefront-sdk (0.1.5)
41
+ addressable (~> 2.4)
42
+ faraday (>= 0.12.1, < 0.13)
43
+ inifile (>= 3.0.0)
44
+ map (~> 6.6.0)
45
+ webmock (2.3.2)
46
+ addressable (>= 2.3.6)
47
+ crack (>= 0.3.2)
48
+ hashdiff
49
+ yard (0.9.5)
50
+
51
+ PLATFORMS
52
+ ruby
53
+
54
+ DEPENDENCIES
55
+ bundler (~> 1.3)
56
+ minitest (~> 5.8, >= 5.8.0)
57
+ rake (~> 12.0)
58
+ rubocop (~> 0.47.0)
59
+ spy (~> 0.4.0)
60
+ wavefront-cli!
61
+ webmock (~> 2.3, >= 2.3.2)
62
+ yard (~> 0.9.5)
63
+
64
+ BUNDLED WITH
65
+ 1.14.6
data/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # Wavefront CLI [![Build Status](https://travis-ci.org/snltd/wavefront-cli.svg?branch=master)](https://travis-ci.org/snltd/wavefront-cli) [![Code Climate](https://codeclimate.com/github/snltd/wavefront-cli/badges/gpa.svg)](https://codeclimate.com/github/snltd/wavefront-cli) [![Issue Count](https://codeclimate.com/github/snltd/wavefront-cli/badges/issue_count.svg)](https://codeclimate.com/github/snltd/wavefront-cli) [![Known Vulnerabilities](https://snyk.io/test/github/snltd/wavefront-cli/badge.svg)](https://snyk.io/test/github/snltd/wavefront-cli)
2
+
3
+
4
+ This package provides a command-line interface to Wavefront's API. Each API path
5
+ is covered by a different command keyword.
6
+
7
+ It is built on [the Wavefront Ruby
8
+ SDK](https://github.com/snltd/wavefront-sdk) and requires Ruby >= 2.2.
9
+
10
+ ```
11
+ $ wavefront --help
12
+ Wavefront CLI
13
+
14
+ Usage:
15
+ wavefront [options] command [options]
16
+ wavefront --version
17
+ wavefront --help
18
+
19
+ Commands:
20
+ alert view and manage alerts
21
+ integration view and manage cloud integrations
22
+ dashboard view and manage dashboards
23
+ event view, manage, open, and close events
24
+ link view and manage external links
25
+ message view and mark as read user messages
26
+ metric view metric details
27
+ proxy view and manage Wavefront proxies
28
+ query run timeseries queries
29
+ savedsearch view and manage saved searches
30
+ source view and manage source tags and descriptions
31
+ user view and manage Wavefront users
32
+ window view and manage maintenance windows
33
+ webhook view and manage webhooks
34
+ write send data points to a Wavefront proxy
35
+
36
+ Use 'wavefront <command> --help' for further information.
37
+ ```
38
+
39
+ ## General Rules
40
+
41
+ ### Credentials and the Config File
42
+
43
+ You can pass in your Wavefront API and token with command-line
44
+ options `-E` and `-t`; with the environment variables
45
+ `WAVEFRONT_ENDPOINT` and `WAVEFRONT_TOKEN`,
46
+ or by putting them in a configuration file at `${HOME}/.wavefront`. This is an
47
+ ini-style file, with a section for each Wavefront account you wish to use. (None
48
+ of the tokens shown here are real, of course!)
49
+
50
+ ```
51
+ [default]
52
+ token = 106ba476-e3bd-c14c-4a3d-391cd4c11def
53
+ endpoint = metrics.wavefront.com
54
+ proxy = wavefront.localnet
55
+ format = human
56
+
57
+ [company]
58
+ token = 9ac40b15-f47f-a168-a5d3-271ab5bad617
59
+ endpoint = company.wavefront.com
60
+ format = yaml
61
+ ```
62
+
63
+ You can override the config file location with `-c`, and select a profile with
64
+ `-P`. If you don't supply `-P`, the `default` profile is used.
65
+
66
+ ### Listing Things
67
+
68
+ Most commands have a `list` subcommand, which will produce brief
69
+ "one thing per line" output. The unique ID of the "thing" is in the first
70
+ column.
71
+
72
+ ```
73
+ $ wavefront proxy list
74
+ 457d6cf3-5171-45e0-8d31-5c980be889ea test agent
75
+ 917102d1-a10e-997b-ba63-95058f98d4fb Agent on wavefront-2017-03-13-02
76
+ 926dfb4c-23c6-4fb9-8c8d-833625ab8f6f Agent on shark-wavefront
77
+ ```
78
+
79
+ You can get more verbose listings with the `-l` flag.
80
+
81
+ ### Describing Things
82
+
83
+ Most commands have a `describe` subcommand which will tell you more about the
84
+ object.
85
+
86
+ ```
87
+ $ wavefront proxy describe 917102d1-a10e-497b-ba63-95058f98d4fb
88
+ name Agent on wavefront-2017-03-13-02
89
+ id 917102d1-a10e-497b-ba63-95058f98d4fb
90
+ version 4.7
91
+ customerId sysdef
92
+ inTrash false
93
+ lastCheckInTime 2017-06-06 14:47:20
94
+ hostname wavefront-2017-03-13-02
95
+ timeDrift -751
96
+ bytesLeftForBuffer 1536094720
97
+ bytesPerMinuteForBuffer 280109
98
+ localQueueSize 0
99
+ sshAgent false
100
+ ephemeral false
101
+ deleted false
102
+ ```
103
+
104
+ Most timestamps come back from the API as epoch seconds or epoch milliseconds.
105
+ The CLI, in its human-readable descriptions, will convert those to
106
+ `YYYY-MM-DD HH:mm:ss` when it `describe`s something.
107
+
108
+ ### Formats, Importing, and Exporting
109
+
110
+ Most commands and sub-commands support the `-f` option. This takes one of
111
+ `json`, `yaml`, `human` and `raw`, and tells the CLI to present the information
112
+ it fetches from the Wavefront API in that format. (`raw` is the raw Ruby
113
+ representation, which, for instance, you could paste into `irb`.)
114
+
115
+ Human output can be selective. As well as the time formatting mentioned above,
116
+ human-readable listings and desctiptions may omit data which is not likely to be
117
+ useful, or which is extremely hard to present in a readable way.
118
+
119
+ If you `describe` an object like a dashboard, user, webhook etc as `json` or
120
+ `yaml`, and send the output to a file, you can re-import that data. The format of the file to be imported is automatically detected.
121
+
122
+ ```
123
+ $ wavefront user list
124
+ slackboy@gmail.com
125
+ sysdef.limited@gmail.com
126
+ $ wavefront user describe -f json sysdef.limited@gmail.com > user.json
127
+ $ cat user.json
128
+ {"identifier":"sysdef.limited@gmail.com","customer":"sysdef","groups":["agent_management"]}
129
+ $ wavefront user delete sysdef.limited@gmail.com
130
+ Deleted user 'sysdef.limited@gmail.com'.
131
+ $ wavefront user list
132
+ slackboy@gmail.com
133
+ $ wavefront user import user.json
134
+ Imported user.
135
+ identifier sysdef.limited@gmail.com
136
+ customer sysdef
137
+ groups agent_management
138
+ $ wavefront user list
139
+ slackboy@gmail.com
140
+ sysdef.limited@gmail.com
141
+ ```
142
+
143
+ You could, of course, modify certain aspects of the exported data before
144
+ re-importing.
145
+
146
+ ### Time Windows
147
+
148
+ Commands which operate on a time window, such as `query` or `event`
149
+ will expect that window to be defined with `-s` and `-e` (or
150
+ `--start` and `--end`). Times can be in seconds since the epoch, or
151
+ any format which [Ruby's `strptime`
152
+ method](https://ruby-doc.org/stdlib-2.3.1/libdoc/date/rdoc/DateTime.html#method-c-strptime)
153
+ method can parse unaided. For instance:
154
+
155
+ ```
156
+ $ wavefront --start 12:15 --end 12:20 ...
157
+ ```
158
+
159
+ will define a window between 12:15 and 12:20pm today. If you ran
160
+ that in the morning, the time would be invalid, and you would get a
161
+ 400 error from Wavefront, so something of the form
162
+ `2016-04-17T12:25:00` would remove all ambiguity.
163
+
164
+ There is no need to include a timezone in your time: the `wavefront`
165
+ CLI will automatically use your local timezone when it parses the
166
+ string.
167
+
168
+ The following options are valid in almost all contexts.
169
+
170
+ ```
171
+ -c, --config=FILE path to configuration file [default: ~/.wavefront]
172
+ -P, --profile=NAME profile in configuration file [default: default]
173
+ -D, --debug enable debug mode
174
+ -V, --verbose enable verbose mode
175
+ -h, --help show help for command
176
+ ```
177
+
178
+ Debug mode will show you combined options, and debug output from
179
+ `faraday`. It also shows the full stack trace should a command
180
+ fail. This output can be very verbose.
181
+
182
+ ## Writing Points
183
+
184
+ Writing a single point is simple:
185
+
186
+ ```
187
+ $ wavefront write point cli.example 10
188
+ ```
189
+
190
+ and you can add point tags, if you like.
191
+
192
+ ```
193
+ $ wavefront write point cli.example 9.4 -E wavefront -T proxy=wavefront \
194
+ -T from=README
195
+ ```
196
+
197
+ or force a timestamp:
198
+
199
+ ```
200
+ $ wavefront write point -t 16:53:14 cli.example 8
201
+ ```
202
+
203
+ More usefully, you can write from a file. Your file must contain multiple
204
+ columns: metric name (`m`), metric value (`v`), timestamp(`t`), and point tags
205
+ (`T`). `v` is mandatory, `m` can be filled in with the `-m` flag, `t` can be
206
+ filled in with the current timestamp, and `T` is optional, but if used, must be
207
+ last. You then tell the CLI what order your fields are in.
208
+
209
+ ```
210
+ $ cat datafile
211
+ 1496767813 dev.cli.test 12.1
212
+ 1496767813 dev.cli.test 10.0
213
+ 1496767813 dev.cli.test 14.5
214
+ $ wavefront write file -F tmv datafile
215
+ ```
216
+
217
+ If you set the file to `-`, you can read from standard in:
218
+
219
+ ```
220
+ $ while true; do echo $RANDOM; sleep 1; done | wavefront write file -m cli.demo -Fv -
221
+ ```
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'yard'
2
+ require 'rake/testtask'
3
+ require 'rubocop/rake_task'
4
+
5
+ task default: :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.pattern = 'spec/wavefront-cli/*_spec.rb'
9
+ t.warning = false
10
+ end
11
+
12
+ RuboCop::RakeTask.new(:rubocop) do |t|
13
+ t.options = ['--display-cop-names']
14
+ end
15
+
16
+ YARD::Rake::YardocTask.new do |t|
17
+ t.files = ['lib/wavefront-cli/*rb']
18
+ end
data/bin/wavefront ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ require 'io/console'
5
+ require_relative '../lib/wavefront-cli/controller'
6
+
7
+ begin
8
+ TW = IO.console.winsize.last
9
+ rescue
10
+ TW = 80
11
+ end
12
+
13
+ CMD = Pathname.new(__FILE__).basename
14
+ WavefrontCliController.new(ARGV)
@@ -0,0 +1,60 @@
1
+ require_relative './base'
2
+
3
+ module WavefrontCli
4
+ #
5
+ # CLI coverage for the v2 'alert' API.
6
+ #
7
+ class Alert < WavefrontCli::Base
8
+ def do_describe
9
+ wf.describe(options[:'<id>'], options[:version])
10
+ end
11
+
12
+ def do_snooze
13
+ wf.snooze(options[:'<id>'], options[:time])
14
+ end
15
+
16
+ def do_unsnooze
17
+ wf.unsnooze(options[:'<id>'])
18
+ end
19
+
20
+ def do_delete
21
+ print (if wf.describe(options[:'<id>']).status.code == 200
22
+ 'Soft'
23
+ else
24
+ 'Permanently'
25
+ end)
26
+
27
+ puts " deleting alert '#{options[:'<id>']}'."
28
+ wf.delete(options[:'<id>'])
29
+ end
30
+
31
+ def do_summary
32
+ wf.summary
33
+ end
34
+
35
+ def do_history
36
+ wf.history(options[:'<id>'], options[:offset], options[:limit])
37
+ end
38
+
39
+ # Take a previously exported alert, and construct a hash which
40
+ # create() can use to re-create it.
41
+ #
42
+ # @param raw [Hash] Ruby hash of imported data
43
+ #
44
+ def import_to_create(raw)
45
+ ret = %w(name condition minutes target severity displayExpression
46
+ additionalInformation).each_with_object({}) do |k, aggr|
47
+ aggr[k.to_sym] = raw[k]
48
+ end
49
+
50
+ if raw.key?('resolveAfterMinutes')
51
+ ret[:resolveMinutes] = raw['resolveAfterMinutes']
52
+ end
53
+
54
+ if raw.key?('customerTagsWithCounts')
55
+ ret[:sharedTags] = raw['customerTagsWithCounts'].keys
56
+ end
57
+ ret
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,320 @@
1
+
2
+ require 'yaml'
3
+ require 'json'
4
+ require 'wavefront-sdk/validators'
5
+ # require_relative './constants'
6
+ require_relative './exception'
7
+
8
+ module WavefrontCli
9
+ #
10
+ # Parent of all the CLI classes. This class uses metaprogramming
11
+ # techniques to try to make adding new CLI commands and
12
+ # sub-commands as simple as possible.
13
+ #
14
+ # To define a subcommand 'cmd', you only need add it to the
15
+ # `docopt` description in the relevant section, and create a
16
+ # method 'do_cmd'. The WavefrontCli::Base::dispatch() method will
17
+ # find it, and call it. If your subcommand has multiple words,
18
+ # like 'delete tag', your do method would be called
19
+ # `do_delete_tag`. The `do_` methods are able to access the
20
+ # Wavefront SDK object as `wf`, and all docopt options as
21
+ # `options`.
22
+ #
23
+ class Base
24
+ attr_accessor :wf, :options, :klass, :klass_word
25
+
26
+ include Wavefront::Validators
27
+
28
+ def initialize(options)
29
+ @options = options
30
+ sdk_class = self.class.name.sub(/Cli/, '')
31
+ @klass_word = sdk_class.split('::').last.downcase
32
+ validate_input
33
+
34
+ if options.include?(:help) && options[:help]
35
+ puts options
36
+ exit 0
37
+ end
38
+
39
+ require File.join('wavefront-sdk', @klass_word)
40
+ @klass = Object.const_get(sdk_class)
41
+
42
+ send(:post_initialize, options) if respond_to?(:post_initialize)
43
+ end
44
+
45
+ def run
46
+ @wf = klass.new(mk_creds, mk_opts)
47
+ dispatch
48
+ end
49
+
50
+ # We normally validate with a predictable method name. Alert IDs are
51
+ # validated with #wf_alert_id? etc. If you need to change that, override
52
+ # this method.
53
+ #
54
+ def validator_method
55
+ "wf_#{klass_word}_id?".to_sym
56
+ end
57
+
58
+ def validator_exception
59
+ Object.const_get(
60
+ "Wavefront::Exception::Invalid#{klass_word.capitalize}Id"
61
+ )
62
+ end
63
+
64
+ def validate_input
65
+ validate_id if options[:'<id>']
66
+ validate_tags if options[:'<tag>']
67
+ send(:extra_validation) if respond_to?(:extra_validation)
68
+ end
69
+
70
+ def validate_tags
71
+ Array(options[:'<tag>']).each do |t|
72
+ begin
73
+ send(:wf_tag?, t)
74
+ rescue Wavefront::Exception::InvalidTag
75
+ abort "'#{t}' is not a valid tag."
76
+ end
77
+ end
78
+ end
79
+
80
+ def validate_id
81
+ send(validator_method, options[:'<id>'])
82
+ rescue validator_exception
83
+ abort "'#{options[:'<id>']}' is not a valid #{klass_word} ID."
84
+ end
85
+
86
+ # Make a wavefront-sdk credentials object from standard
87
+ # options.
88
+ #
89
+ # @return [Hash] containing `token` and `endpoint`.
90
+ #
91
+ def mk_creds
92
+ { token: options[:token], endpoint: options[:endpoint] }
93
+ end
94
+
95
+ # Make a common wavefront-sdk options object from standard CLI
96
+ # options.
97
+ #
98
+ # @return [Hash] containing `debug`, `verbose`, and `noop`.
99
+ #
100
+ def mk_opts
101
+ { debug: options[:debug], verbose: options[:verbose],
102
+ noop: options[:noop] }
103
+ end
104
+
105
+ # To allow a user to default to different output formats for
106
+ # different object, we define a format for each class. For
107
+ # instance, `alertformat` or `agentformat`. This method returns
108
+ # such a string appropriate for the inheriting class.
109
+ #
110
+ # @return [Symbol] name of the option or config-file key which
111
+ # sets the default output format for this class
112
+ #
113
+ def format_var
114
+ options[:format].to_sym
115
+ # (self.class.name.split('::').last.downcase + 'format').to_sym
116
+ end
117
+
118
+ # Works out the user's command by matching any options docopt has
119
+ # set to 'true' with any 'do_' method in the class. Then calls that
120
+ # method, and displays whatever it returns.
121
+ #
122
+ # @return [nil]
123
+ # @raise 'unsupported command', if the command does not match a
124
+ # `do_` method.
125
+ #
126
+ def dispatch
127
+ #
128
+ # Take a list of do_ methods, remove the 'do_' from their name,
129
+ # and break them into arrays of '_' separated words.
130
+ #
131
+ m_list = methods.select { |m| m.to_s.start_with?('do_') }.map do |m|
132
+ m.to_s.split('_')[1..-1]
133
+ end
134
+
135
+ # Sort that array of arrays by length, longest first. Then look
136
+ # through each deconstructed method name and see if the user
137
+ # supplied an option for each component. Call the first one that
138
+ # matches. The order will ensure we match "do_delete_tags" before
139
+ # we match "do_delete".
140
+ #
141
+ m_list.sort_by(&:length).reverse.each do |m|
142
+ if m.reject { |w| options[w.to_sym] }.empty?
143
+ method = (%w(do) + m).join('_')
144
+ return display(public_send(method), method)
145
+ end
146
+ end
147
+
148
+ if respond_to?(:do_default)
149
+ return display(public_send(:do_default), :do_default)
150
+ end
151
+
152
+ raise WavefrontCli::Exception::UnhandledCommand
153
+ end
154
+
155
+ # Display a Ruby object as JSON, YAML, or human-readable. We
156
+ # provide a default method to format human-readable output, but
157
+ # you can override it by creating your own
158
+ # `humanize_command_output` method
159
+ # control how its output is handled by setting the `response`
160
+ # instance variable.
161
+ #
162
+ # @param data [WavefrontResponse] an object returned by a
163
+ # Wavefront SDK method. This will contain a 'response'
164
+ # and 'status' structures.
165
+ # @param method [String] the name of the method which produced
166
+ # this output. Used to find a suitable humanize method.
167
+ #
168
+ def display(data, method)
169
+ [:status, :response].each do |b|
170
+ abort "no #{b} block in API response" unless data.respond_to?(b)
171
+ end
172
+
173
+ unless check_status(data.status)
174
+ handle_error(method, data.status.code) if format_var == :human
175
+ abort "API #{data.status.code}: #{data.status.message}."
176
+ end
177
+
178
+ resp = if data.response.respond_to?(:items)
179
+ data.response.items
180
+ else
181
+ data.response
182
+ end
183
+
184
+ handle_response(resp, format_var, method)
185
+ end
186
+
187
+ def check_status(status)
188
+ status.respond_to?(:result) && status.result == 'OK'
189
+ end
190
+
191
+ # This gives us a chance to catch different errors in
192
+ # WavefrontDisplay classes. If nothing catches, them abort.
193
+ #
194
+ def handle_error(method, code)
195
+ k = load_display_class
196
+ k.new({}, options).run_error([method, code].join('_'))
197
+ end
198
+
199
+ def handle_response(resp, format, method)
200
+ case format
201
+ when :json
202
+ puts resp.to_json
203
+ when :yaml # We don't want the YAML keys to be symbols.
204
+ puts JSON.parse(resp.to_json).to_yaml
205
+ when :ruby
206
+ p resp
207
+ when :human
208
+ k = load_display_class
209
+ k.new(resp, options).run(method)
210
+ else
211
+ raise "Unknown output format '#{format}'."
212
+ end
213
+ end
214
+
215
+ def load_display_class
216
+ require_relative File.join('display', klass_word)
217
+ Object.const_get(klass.name.sub('Wavefront', 'WavefrontDisplay'))
218
+ end
219
+
220
+ # There are things we need to have. If we don't have them, stop
221
+ # the user right now. Also, if we're in debug mode, print out a
222
+ # hash of options, which can be very useful when doing actual
223
+ # debugging. Some classes may have to override this method. The
224
+ # writer, for instance, uses a proxy and has no token.
225
+ #
226
+ def validate_opts
227
+ raise 'Please supply an API token.' unless options[:token]
228
+ raise 'Please supply an API endpoint.' unless options[:endpoint]
229
+ end
230
+
231
+ # Give it a path to a file (as a string) and it will return the
232
+ # contents of that file as a Ruby object. Automatically detects
233
+ # JSON and YAML. Raises an exception if it doesn't look like
234
+ # either.
235
+ #
236
+ # @param path [String] the file to load
237
+ # @return [Hash] a Ruby object of the loaded file
238
+ # @raise 'Unsupported file format.' if the filetype is unknown.
239
+ # @raise pass through any error loading or parsing the file
240
+ #
241
+ def load_file(path)
242
+ file = Pathname.new(path)
243
+ raise 'Import file does not exist.' unless file.exist?
244
+
245
+ if file.extname == '.json'
246
+ JSON.parse(IO.read(file))
247
+ elsif file.extname == '.yaml' || file.extname == '.yml'
248
+ YAML.safe_load(IO.read(file))
249
+ else
250
+ raise 'Unsupported file format.'
251
+ end
252
+ end
253
+
254
+ # Below here are common methods. Most are used by most classes,
255
+ # but if they don't match a command described in the docopt
256
+ # text, the dispatcher will never call them. So, there's no
257
+ # harm inheriting unneeded things. Some classes override them.
258
+ #
259
+ def do_list
260
+ wf.list(options[:offset] || 0, options[:limit] || 100)
261
+ end
262
+
263
+ def do_describe
264
+ wf.describe(options[:'<id>'])
265
+ end
266
+
267
+ def do_import
268
+ raw = load_file(options[:'<file>'])
269
+
270
+ begin
271
+ prepped = import_to_create(raw)
272
+ rescue => e
273
+ puts e if options[:debug]
274
+ raise 'could not parse input.'
275
+ end
276
+
277
+ wf.create(prepped)
278
+ end
279
+
280
+ def do_delete
281
+ wf.delete(options[:'<id>'])
282
+ end
283
+
284
+ def do_undelete
285
+ wf.undelete(options[:'<id>'])
286
+ end
287
+
288
+ def do_update
289
+ k, v = options[:'<key=value>'].split('=')
290
+ wf.update(options[:'<id>'], k => v)
291
+ end
292
+
293
+ def do_tags
294
+ wf.tags(options[:'<id>'])
295
+ end
296
+
297
+ def do_tag_add
298
+ wf.tag_add(options[:'<id>'], options[:'<tag>'].first)
299
+ end
300
+
301
+ def do_tag_delete
302
+ wf.tag_delete(options[:'<id>'], options[:'<tag>'].first)
303
+ end
304
+
305
+ def do_tag_set
306
+ wf.tag_set(options[:'<id>'], options[:'<tag>'])
307
+ end
308
+
309
+ def do_tag_clear
310
+ wf.tag_set(options[:'<id>'], [])
311
+ end
312
+
313
+ # Most things will re-import with the POST method if you remove
314
+ # the ID.
315
+ #
316
+ def import_to_create(raw)
317
+ raw.delete_if { |k, _v| k == 'id' }
318
+ end
319
+ end
320
+ end