wavefront-cli 2.8.0 → 2.9.0

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -0
  3. data/HISTORY.md +17 -0
  4. data/Rakefile +1 -1
  5. data/bin/wf +1 -1
  6. data/lib/wavefront-cli/base.rb +46 -18
  7. data/lib/wavefront-cli/base_write.rb +49 -33
  8. data/lib/wavefront-cli/commands/.rubocop.yml +7 -0
  9. data/lib/wavefront-cli/commands/alert.rb +0 -1
  10. data/lib/wavefront-cli/commands/base.rb +1 -1
  11. data/lib/wavefront-cli/commands/link.rb +5 -0
  12. data/lib/wavefront-cli/commands/window.rb +1 -0
  13. data/lib/wavefront-cli/commands/write.rb +1 -1
  14. data/lib/wavefront-cli/controller.rb +51 -21
  15. data/lib/wavefront-cli/derivedmetric.rb +4 -0
  16. data/lib/wavefront-cli/display/alert.rb +2 -0
  17. data/lib/wavefront-cli/display/base.rb +18 -11
  18. data/lib/wavefront-cli/display/metric.rb +13 -5
  19. data/lib/wavefront-cli/display/printer/long.rb +12 -8
  20. data/lib/wavefront-cli/display/printer/sparkline.rb +2 -2
  21. data/lib/wavefront-cli/display/printer/terse.rb +3 -1
  22. data/lib/wavefront-cli/display/query.rb +7 -3
  23. data/lib/wavefront-cli/display/write.rb +2 -0
  24. data/lib/wavefront-cli/event.rb +27 -16
  25. data/lib/wavefront-cli/exception.rb +8 -0
  26. data/lib/wavefront-cli/externallink.rb +30 -0
  27. data/lib/wavefront-cli/maintenancewindow.rb +2 -2
  28. data/lib/wavefront-cli/output/hcl/base.rb +21 -18
  29. data/lib/wavefront-cli/output/hcl/dashboard.rb +13 -38
  30. data/lib/wavefront-cli/output/hcl/notificant.rb +4 -2
  31. data/lib/wavefront-cli/output/hcl/stdlib/array.rb +22 -0
  32. data/lib/wavefront-cli/output/hcl/stdlib/string.rb +9 -0
  33. data/lib/wavefront-cli/output/wavefront/base.rb +3 -0
  34. data/lib/wavefront-cli/output/wavefront/query.rb +2 -2
  35. data/lib/wavefront-cli/query.rb +2 -0
  36. data/lib/wavefront-cli/report.rb +5 -2
  37. data/lib/wavefront-cli/{string.rb → stdlib/string.rb} +16 -12
  38. data/lib/wavefront-cli/version.rb +1 -1
  39. data/lib/wavefront-cli/write.rb +12 -4
  40. data/spec/spec_helper.rb +41 -24
  41. data/spec/wavefront-cli/commands/link_spec.rb +1 -1
  42. data/spec/wavefront-cli/derivedmetric_spec.rb +2 -2
  43. data/spec/wavefront-cli/display/base_spec.rb +2 -0
  44. data/spec/wavefront-cli/display/printer/long_spec.rb +1 -0
  45. data/spec/wavefront-cli/externallink_spec.rb +52 -4
  46. data/spec/wavefront-cli/output/hcl_spec.rb +3 -1
  47. data/spec/wavefront-cli/output/ruby_spec.rb +5 -5
  48. data/spec/wavefront-cli/output/wavefront/query_spec.rb +14 -9
  49. data/spec/wavefront-cli/output/wavefront_spec.rb +3 -1
  50. data/spec/wavefront-cli/query_spec.rb +2 -0
  51. data/spec/wavefront-cli/{string_spec.rb → stdlib/string_spec.rb} +3 -3
  52. data/wavefront-cli.gemspec +7 -7
  53. metadata +34 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 397350925432eb596a2268652590b0690ee115d0f351f4406e807eec465db61c
4
- data.tar.gz: e85b47160b148fc9067ea9d14b6265c8fa27004661a65a62dd4bdca67645c4e1
3
+ metadata.gz: ceaedf1118ace74decdfe51ad1e4c1c019e532b492c66fba499106c15f8ac021
4
+ data.tar.gz: 016ef2f6fdd75a958dc17b70375e74ac35b33acfe5d1296c75c68ce1bfbf1de9
5
5
  SHA512:
6
- metadata.gz: 0f9d17a9f4ceebbadc18e41813b6766b86c07f6a881a1b30814ab747e82782f67ef97d6a5f876d28719c8182b8b6e1ae14b514cfc649efe80f265bdefc623c0f
7
- data.tar.gz: 7071d861a7da1bad3322fac62601cc05ed773898b04833384d16178ce67d260861603d36ff669f36dff43d333e9805608d0c6e520513fdf179f9ae074286b93d
6
+ metadata.gz: 51931ddb036a749b9989c35999be1e276b2d1832f5fe7d04a02cb897bceeed7b960e627667e79dff7bd3240c4a3b22352d23a4baba2ca4feca86659ade6b0c8b
7
+ data.tar.gz: e6f40465692ae30d9d4fdd8ef68d6d6f21443127237ba14fdc726096af0bb8a3b7f2562e2c6342bbc84f24fd5bd25ec3a272b4b012827c2e612761068cc98ead
@@ -0,0 +1,11 @@
1
+ ---
2
+
3
+ Style/FormatStringToken:
4
+ Enabled: false
5
+
6
+ Metrics/ClassLength:
7
+ Enabled: false
8
+
9
+ Metrics/MethodLength:
10
+ Max: 13
11
+
data/HISTORY.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.9.0 (22/08/2018)
4
+ * Create external links with new `link create` sub-command.
5
+ * Fix bug which stopped you writing points without a `.wavefront`
6
+ configuration file.
7
+ * Improved error reporting.
8
+ * Bugfix on external link searching.
9
+ * Modify external link filters.
10
+ * Use 1.6.1 of [wavefront-sdk](https://github.com/snltd/wavefront-sdk).
11
+
12
+ ## 2.8.0 (08/08/2018)
13
+ * Add `wavefront` format to the `query` command. This outputs the
14
+ result of a raw or timeseries query in a format which can be fed
15
+ back into Wavefront via a proxy.
16
+ * Use 1.6.0 of [wavefront-sdk](https://github.com/snltd/wavefront-sdk).
17
+ * Restructure the way different output formats are handled in a
18
+ better, more flexible way.
19
+
3
20
  ## 2.7.0 (04/07/2018)
4
21
 
5
22
  * Add a `-i` option to the `report` command, to send delta metrics.
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rubocop/rake_task'
4
4
 
5
5
  task default: :test
6
6
 
7
- Rake::TestTask.new do |t|
7
+ Rake::TestTask.new(test: :rubocop) do |t|
8
8
  t.pattern = 'spec/wavefront-cli/**/*_spec.rb'
9
9
  t.warning = false
10
10
  end
data/bin/wf CHANGED
@@ -6,7 +6,7 @@ require_relative '../lib/wavefront-cli/controller'
6
6
 
7
7
  begin
8
8
  tw = IO.console.winsize.last
9
- rescue
9
+ rescue RuntimeError
10
10
  tw = 80
11
11
  end
12
12
 
@@ -84,7 +84,8 @@ module WavefrontCli
84
84
  begin
85
85
  send(:wf_tag?, t)
86
86
  rescue Wavefront::Exception::InvalidTag
87
- abort "'#{t}' is not a valid tag."
87
+ raise(WavefrontCli::Exception::InvalidInput,
88
+ "'#{t}' is not a valid tag.")
88
89
  end
89
90
  end
90
91
  end
@@ -135,9 +136,10 @@ module WavefrontCli
135
136
  # method, and displays whatever it returns.
136
137
  #
137
138
  # @return [nil]
138
- # @raise 'unsupported command', if the command does not match a
139
- # `do_` method.
139
+ # @raise WavefrontCli::Exception::UnhandledCommand if the
140
+ # command does not match a `do_` method.
140
141
  #
142
+ # rubocop:disable Metrics/AbcSize
141
143
  def dispatch
142
144
  #
143
145
  # Take a list of do_ methods, remove the 'do_' from their name,
@@ -166,6 +168,7 @@ module WavefrontCli
166
168
 
167
169
  raise WavefrontCli::Exception::UnhandledCommand
168
170
  end
171
+ # rubocop:enable Metrics/AbcSize
169
172
 
170
173
  # Display a Ruby object as JSON, YAML, or human-readable. We
171
174
  # provide a default method to format human-readable output, but
@@ -180,6 +183,7 @@ module WavefrontCli
180
183
  # @param method [String] the name of the method which produced
181
184
  # this output. Used to find a suitable humanize method.
182
185
  #
186
+ # rubocop:disable Metrics/AbcSize
183
187
  def display(data, method)
184
188
  if no_api_response.include?(method)
185
189
  return display_no_api_response(data, method)
@@ -198,6 +202,7 @@ module WavefrontCli
198
202
 
199
203
  handle_response(data.response, format_var, method)
200
204
  end
205
+ # rubocop:enable Metrics/AbcSize
201
206
 
202
207
  # @param status [Map] status object from SDK response
203
208
  # @return System exit
@@ -232,19 +237,20 @@ module WavefrontCli
232
237
  end
233
238
  end
234
239
 
240
+ # rubocop:disable Metrics/AbcSize
235
241
  def parseable_output(format, resp)
236
242
  options[:class] = klass_word
237
243
  options[:hcl_fields] = hcl_fields
238
244
  require_relative File.join('output', format.to_s)
239
245
  oclass = Object.const_get(format('WavefrontOutput::%s',
240
- format.to_s.capitalize))
246
+ format.to_s.capitalize))
241
247
  oclass.new(resp, options).run
242
248
  rescue LoadError
243
- raise WavefrontCli::Exception::UnsupportedOutput.new(
244
- format("The '%s' command does not support '%s' output.",
245
- options[:class], format)
246
- )
249
+ raise(WavefrontCli::Exception::UnsupportedOutput,
250
+ format("The '%s' command does not support '%s' output.",
251
+ options[:class], format))
247
252
  end
253
+ # rubocop:enable Metrics/AbcSize
248
254
 
249
255
  def hcl_fields
250
256
  []
@@ -262,8 +268,14 @@ module WavefrontCli
262
268
  # writer, for instance, uses a proxy and has no token.
263
269
  #
264
270
  def validate_opts
265
- raise 'Please supply an API token.' unless options[:token]
266
- raise 'Please supply an API endpoint.' unless options[:endpoint]
271
+ unless options[:token]
272
+ raise(WavefrontCli::Exception::CredentialError,
273
+ 'Missing API token.')
274
+ end
275
+
276
+ return true if options[:endpoint]
277
+ raise(WavefrontCli::Exception::CredentialError,
278
+ 'Missing API endpoint.')
267
279
  end
268
280
 
269
281
  # Give it a path to a file (as a string) and it will return the
@@ -273,23 +285,27 @@ module WavefrontCli
273
285
  #
274
286
  # @param path [String] the file to load
275
287
  # @return [Hash] a Ruby object of the loaded file
276
- # @raise 'Unsupported file format.' if the filetype is unknown.
288
+ # @raise WavefrontCli::Exception::UnsupportedFileFormat if the
289
+ # filetype is unknown.
277
290
  # @raise pass through any error loading or parsing the file
278
291
  #
292
+ # rubocop:disable Metrics/AbcSize
279
293
  def load_file(path)
280
294
  return load_from_stdin if path == '-'
281
295
 
282
296
  file = Pathname.new(path)
283
- raise 'Import file does not exist.' unless file.exist?
297
+
298
+ raise WavefrontCli::Exception::FileNotFound unless file.exist?
284
299
 
285
300
  if file.extname == '.json'
286
301
  JSON.parse(IO.read(file))
287
302
  elsif file.extname == '.yaml' || file.extname == '.yml'
288
303
  YAML.safe_load(IO.read(file))
289
304
  else
290
- raise 'Unsupported file format.'
305
+ raise WavefrontCli::Exception::UnsupportedFileFormat
291
306
  end
292
307
  end
308
+ # rubocop:enable Metrics/AbcSize
293
309
 
294
310
  # Read STDIN and return a Ruby object, assuming that STDIN is
295
311
  # valid JSON or YAML. This is a dumb method, it does no
@@ -297,7 +313,8 @@ module WavefrontCli
297
313
  # appears to be a valid assumption for use-cases of this CLI.
298
314
  #
299
315
  # @return [Object]
300
- # @raise 'cannot parse stdin' if it, well you know.
316
+ # @raise Wavefront::Exception::UnparseableInput if the input
317
+ # does not parse
301
318
  #
302
319
  def load_from_stdin
303
320
  raw = STDIN.read
@@ -307,8 +324,8 @@ module WavefrontCli
307
324
  else
308
325
  JSON.parse(raw)
309
326
  end
310
- rescue
311
- raise 'Cannot parse stdin.'
327
+ rescue RuntimeError
328
+ raise Wavefront::Exception::UnparseableInput
312
329
  end
313
330
 
314
331
  # Below here are common methods. Most are used by most classes,
@@ -331,7 +348,7 @@ module WavefrontCli
331
348
  prepped = import_to_create(raw)
332
349
  rescue StandardError => e
333
350
  puts e if options[:debug]
334
- raise 'could not parse input.'
351
+ raise WavefrontCli::Exception::UnparseableInput
335
352
  end
336
353
 
337
354
  wf.create(prepped)
@@ -348,6 +365,10 @@ module WavefrontCli
348
365
  def do_update
349
366
  k, v = options[:'<key=value>'].split('=', 2)
350
367
  wf.update(options[:'<id>'], k => v)
368
+ rescue NoMethodError
369
+ raise(WavefrontCli::Exception::UnsupportedOperation,
370
+ 'Updates require two API calls. We cannot do the second ' \
371
+ 'when -n is set.')
351
372
  end
352
373
 
353
374
  def do_search(cond = options[:'<condition>'])
@@ -356,11 +377,18 @@ module WavefrontCli
356
377
 
357
378
  query = conds_to_query(cond)
358
379
 
359
- wfs.search(klass_word, query, limit: options[:limit],
380
+ wfs.search(search_key, query, limit: options[:limit],
360
381
  offset: options[:offset] ||
361
382
  options[:cursor])
362
383
  end
363
384
 
385
+ # The search URI pattern doesn't always match the command name,
386
+ # or class name. Override this method if this is the case.
387
+ #
388
+ def search_key
389
+ klass_word
390
+ end
391
+
364
392
  # Turn a list of search conditions into an API query
365
393
  #
366
394
  def conds_to_query(conds)
@@ -9,6 +9,7 @@ module WavefrontCli
9
9
  attr_reader :fmt
10
10
  include Wavefront::Mixins
11
11
 
12
+ # rubocop:disable Metrics/AbcSize
12
13
  def do_point
13
14
  p = { path: options[:'<metric>'],
14
15
  value: options[:'<value>'].delete('\\').to_f,
@@ -18,9 +19,10 @@ module WavefrontCli
18
19
  p[:ts] = parse_time(options[:time]) if options[:time]
19
20
  send_point(p)
20
21
  end
22
+ # rubocop:enable Metrics/AbcSize
21
23
 
22
- def send_point(p)
23
- call_write(p)
24
+ def send_point(point)
25
+ call_write(point)
24
26
  rescue Wavefront::Exception::InvalidEndpoint
25
27
  abort "could not speak to proxy #{options[:proxy]}:#{options[:port]}."
26
28
  end
@@ -38,14 +40,22 @@ module WavefrontCli
38
40
  if file == '-'
39
41
  read_stdin
40
42
  else
41
- data = load_data(Pathname.new(file)).split("\n").map do |l|
42
- process_line(l)
43
- end
44
-
43
+ data = process_input_file(load_data(Pathname.new(file)).split("\n"))
45
44
  call_write(data)
46
45
  end
47
46
  end
48
47
 
48
+ def process_input_file(data)
49
+ data.each_with_object([]) do |l, a|
50
+ begin
51
+ a.<< process_line(l)
52
+ rescue WavefrontCli::Exception::UnparseableInput => e
53
+ puts "Bad input. #{e.message}."
54
+ next
55
+ end
56
+ end
57
+ end
58
+
49
59
  # A wrapper which lets us send normal points or deltas
50
60
  #
51
61
  def call_write(data)
@@ -80,10 +90,9 @@ module WavefrontCli
80
90
 
81
91
  # Find and return the source in a chunked line of input.
82
92
  #
83
- # param chunks [Array] a chunked line of input from #process_line
84
- # return [Float] the timestamp, if it is there, or the current
93
+ # @param chunks [Array] a chunked line of input from #process_line
94
+ # @return [Float] the timestamp, if it is there, or the current
85
95
  # UTC time if it is not.
86
- # raise TypeError if field does not exist
87
96
  #
88
97
  def extract_ts(chunks)
89
98
  ts = chunks[fmt.index('t')]
@@ -107,7 +116,7 @@ module WavefrontCli
107
116
  #
108
117
  def extract_path(chunks)
109
118
  m = chunks[fmt.index('m')]
110
- return options[:metric] ? [options[:metric], m].join('.') : m
119
+ options[:metric] ? [options[:metric], m].join('.') : m
111
120
  rescue TypeError
112
121
  return options[:metric] if options[:metric]
113
122
  raise
@@ -120,7 +129,7 @@ module WavefrontCli
120
129
  # value passed through by -H, or the local hostname.
121
130
  #
122
131
  def extract_source(chunks)
123
- return chunks[fmt.index('s')]
132
+ chunks[fmt.index('s')]
124
133
  rescue TypeError
125
134
  options[:source] || Socket.gethostname
126
135
  end
@@ -132,23 +141,32 @@ module WavefrontCli
132
141
  # what they define is always assumed to be point tags. This is
133
142
  # because you can have arbitrarily many of those for each point.
134
143
  #
135
- def process_line(l)
136
- return true if l.empty?
137
- chunks = l.split(/\s+/, fmt.length)
138
- raise 'wrong number of fields' unless enough_fields?(l)
144
+ # @raise WavefrontCli::Exception::UnparseableInput if the line
145
+ # doesn't look right
146
+ #
147
+ # rubocop:disable Metrics/AbcSize
148
+ # rubocop:disable Metrics/MethodLength
149
+ def process_line(line)
150
+ return true if line.empty?
151
+ chunks = line.split(/\s+/, fmt.length)
152
+ enough_fields?(line) # can raise exception
139
153
 
140
154
  begin
141
155
  point = { path: extract_path(chunks),
156
+ tags: line_tags(chunks),
142
157
  value: extract_value(chunks) }
143
- point[:ts] = extract_ts(chunks) if fmt.include?('t')
158
+
159
+ point[:ts] = extract_ts(chunks) if fmt.include?('t')
144
160
  point[:source] = extract_source(chunks) if fmt.include?('s')
145
- point[:tags] = line_tags(chunks)
146
161
  rescue TypeError
147
- raise "could not process #{l}"
162
+ raise(WavefrontCli::Exception::UnparseableInput,
163
+ "could not process #{line}")
148
164
  end
149
165
 
150
166
  point
151
167
  end
168
+ # rubocop:enable Metrics/MethodLength
169
+ # rubocop:enable Metrics/AbcSize
152
170
 
153
171
  # We can get tags from the file, from the -T option, or both.
154
172
  # Merge them, making the -T win if there is a collision.
@@ -190,7 +208,8 @@ module WavefrontCli
190
208
  return true
191
209
  end
192
210
 
193
- raise 'Invalid format string.'
211
+ raise(WavefrontCli::Exception::UnparseableInput,
212
+ 'Invalid format string.')
194
213
  end
195
214
 
196
215
  # Make sure we have the right number of columns, according to
@@ -201,16 +220,11 @@ module WavefrontCli
201
220
  # If the format string says we are expecting point tags, we
202
221
  # may have more columns than the length of the format string.
203
222
  #
204
- def enough_fields?(l)
205
- ncols = l.split.length
206
-
207
- if fmt.include?('T')
208
- return false unless ncols >= fmt.length
209
- else
210
- return false unless ncols == fmt.length
211
- end
212
-
213
- true
223
+ def enough_fields?(line)
224
+ ncols = line.split.length
225
+ return true if fmt.include?('T') && ncols >= fmt.length
226
+ return true if ncols == fmt.length
227
+ raise WavefrontCli::Exception::UnparseableInput, 'wrong number of fields'
214
228
  end
215
229
 
216
230
  # Although the SDK does value checking, we'll add another layer
@@ -218,9 +232,10 @@ module WavefrontCli
218
232
  # assume anything before 2000/01/01 or after a year from now is
219
233
  # wrong. Arbitrary, but there has to be a cut-off somewhere.
220
234
  #
221
- def valid_timestamp?(ts)
222
- (ts.is_a?(Integer) || ts.match(/^\d+$/)) &&
223
- ts.to_i > 946_684_800 && ts.to_i < (Time.now.to_i + 31_557_600)
235
+ def valid_timestamp?(timestamp)
236
+ (timestamp.is_a?(Integer) || timestamp.match(/^\d+$/)) &&
237
+ timestamp.to_i > 946_684_800 &&
238
+ timestamp.to_i < (Time.now.to_i + 31_557_600)
224
239
  end
225
240
 
226
241
  private
@@ -230,8 +245,9 @@ module WavefrontCli
230
245
  end
231
246
 
232
247
  def load_data(file)
233
- raise "Cannot open file '#{file}'." unless file.exist?
234
248
  IO.read(file)
249
+ rescue StandardError
250
+ raise WavefrontCli::Exception::FileNotFound
235
251
  end
236
252
  end
237
253
  end
@@ -0,0 +1,7 @@
1
+ ---
2
+
3
+ inherit_from: ../../../.rubocop.yml
4
+
5
+ # Some of these methods are long, but they're just lists
6
+ Metrics/MethodLength:
7
+ Enabled: false
@@ -3,7 +3,6 @@ require_relative 'base'
3
3
  # Define the Alert command
4
4
  #
5
5
  class WavefrontCommandAlert < WavefrontCommandBase
6
-
7
6
  def description
8
7
  'view and manage alerts'
9
8
  end
@@ -1,4 +1,4 @@
1
- require_relative '../string'
1
+ require_relative '../stdlib/string'
2
2
 
3
3
  CMN = '[-DnV] [-c file] [-P profile] [-E endpoint] [-t token]'.freeze
4
4
 
@@ -18,6 +18,8 @@ class WavefrontCommandLink < WavefrontCommandBase
18
18
  def _commands
19
19
  ["list #{CMN} [-l] [-f format] [-o offset] [-L limit]",
20
20
  "describe #{CMN} [-f format] <id>",
21
+ "create #{CMN} [-m regex] [-s regex] [-p str=regex...] <name> " \
22
+ '<description> <template>',
21
23
  "delete #{CMN} <id>",
22
24
  "import #{CMN} <file>",
23
25
  "update #{CMN} <key=value> <id>",
@@ -29,6 +31,9 @@ class WavefrontCommandLink < WavefrontCommandBase
29
31
  '-l, --long list external links in detail',
30
32
  '-o, --offset=n start from nth external link',
31
33
  '-L, --limit=COUNT number of external link to list',
34
+ '-m, --metric-regex=REGEX metric filter regular expression',
35
+ '-s, --source-regex=REGEX source filter regular expression',
36
+ '-p, --point-regex=REGEX point filter regular expression',
32
37
  '-f, --format=STRING output format']
33
38
  end
34
39
  end