wavefront-cli 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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