wavefront-cli 2.8.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
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