wavefront-cli 4.2.1 → 4.3.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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.rubocop.yml +1 -14
  4. data/.travis.yml +4 -4
  5. data/Gemfile +2 -0
  6. data/HISTORY.md +82 -60
  7. data/Rakefile +2 -0
  8. data/bin/wf +1 -0
  9. data/lib/wavefront-cli/alert.rb +13 -5
  10. data/lib/wavefront-cli/apitoken.rb +2 -0
  11. data/lib/wavefront-cli/base.rb +95 -47
  12. data/lib/wavefront-cli/cloudintegration.rb +6 -0
  13. data/lib/wavefront-cli/command_mixins/acl.rb +6 -1
  14. data/lib/wavefront-cli/command_mixins/tag.rb +2 -0
  15. data/lib/wavefront-cli/commands/.rubocop.yml +3 -4
  16. data/lib/wavefront-cli/commands/alert.rb +2 -1
  17. data/lib/wavefront-cli/commands/apitoken.rb +6 -0
  18. data/lib/wavefront-cli/commands/base.rb +30 -21
  19. data/lib/wavefront-cli/commands/cloudintegration.rb +2 -0
  20. data/lib/wavefront-cli/commands/config.rb +2 -0
  21. data/lib/wavefront-cli/commands/dashboard.rb +2 -0
  22. data/lib/wavefront-cli/commands/derivedmetric.rb +2 -0
  23. data/lib/wavefront-cli/commands/event.rb +3 -1
  24. data/lib/wavefront-cli/commands/integration.rb +2 -1
  25. data/lib/wavefront-cli/commands/link.rb +2 -0
  26. data/lib/wavefront-cli/commands/message.rb +2 -0
  27. data/lib/wavefront-cli/commands/metric.rb +2 -0
  28. data/lib/wavefront-cli/commands/notificant.rb +2 -0
  29. data/lib/wavefront-cli/commands/proxy.rb +2 -0
  30. data/lib/wavefront-cli/commands/query.rb +2 -0
  31. data/lib/wavefront-cli/commands/savedsearch.rb +2 -0
  32. data/lib/wavefront-cli/commands/serviceaccount.rb +58 -0
  33. data/lib/wavefront-cli/commands/settings.rb +2 -0
  34. data/lib/wavefront-cli/commands/source.rb +3 -1
  35. data/lib/wavefront-cli/commands/user.rb +4 -1
  36. data/lib/wavefront-cli/commands/usergroup.rb +3 -0
  37. data/lib/wavefront-cli/commands/webhook.rb +2 -0
  38. data/lib/wavefront-cli/commands/window.rb +2 -0
  39. data/lib/wavefront-cli/commands/write.rb +2 -0
  40. data/lib/wavefront-cli/config.rb +23 -12
  41. data/lib/wavefront-cli/constants.rb +10 -4
  42. data/lib/wavefront-cli/controller.rb +53 -22
  43. data/lib/wavefront-cli/dashboard.rb +3 -0
  44. data/lib/wavefront-cli/derivedmetric.rb +12 -11
  45. data/lib/wavefront-cli/display/alert.rb +7 -4
  46. data/lib/wavefront-cli/display/apitoken.rb +2 -0
  47. data/lib/wavefront-cli/display/base.rb +34 -8
  48. data/lib/wavefront-cli/display/cloudintegration.rb +4 -2
  49. data/lib/wavefront-cli/display/dashboard.rb +2 -0
  50. data/lib/wavefront-cli/display/derivedmetric.rb +2 -0
  51. data/lib/wavefront-cli/display/distribution.rb +2 -0
  52. data/lib/wavefront-cli/display/event.rb +2 -0
  53. data/lib/wavefront-cli/display/externallink.rb +2 -0
  54. data/lib/wavefront-cli/display/integration.rb +2 -0
  55. data/lib/wavefront-cli/display/maintenancewindow.rb +2 -0
  56. data/lib/wavefront-cli/display/message.rb +7 -3
  57. data/lib/wavefront-cli/display/metric.rb +3 -1
  58. data/lib/wavefront-cli/display/notificant.rb +3 -1
  59. data/lib/wavefront-cli/display/printer/long.rb +36 -11
  60. data/lib/wavefront-cli/display/printer/sparkline.rb +3 -0
  61. data/lib/wavefront-cli/display/printer/terse.rb +6 -1
  62. data/lib/wavefront-cli/display/proxy.rb +2 -0
  63. data/lib/wavefront-cli/display/query.rb +35 -24
  64. data/lib/wavefront-cli/display/savedsearch.rb +2 -0
  65. data/lib/wavefront-cli/display/serviceaccount.rb +60 -0
  66. data/lib/wavefront-cli/display/settings.rb +2 -0
  67. data/lib/wavefront-cli/display/source.rb +18 -4
  68. data/lib/wavefront-cli/display/user.rb +56 -10
  69. data/lib/wavefront-cli/display/usergroup.rb +25 -19
  70. data/lib/wavefront-cli/display/webhook.rb +2 -0
  71. data/lib/wavefront-cli/display/write.rb +6 -2
  72. data/lib/wavefront-cli/event.rb +83 -62
  73. data/lib/wavefront-cli/exception.rb +3 -0
  74. data/lib/wavefront-cli/externallink.rb +6 -4
  75. data/lib/wavefront-cli/integration.rb +2 -0
  76. data/lib/wavefront-cli/maintenancewindow.rb +28 -17
  77. data/lib/wavefront-cli/message.rb +4 -0
  78. data/lib/wavefront-cli/metric.rb +4 -1
  79. data/lib/wavefront-cli/notificant.rb +2 -0
  80. data/lib/wavefront-cli/opt_handler.rb +2 -0
  81. data/lib/wavefront-cli/output/base.rb +8 -3
  82. data/lib/wavefront-cli/output/csv.rb +2 -0
  83. data/lib/wavefront-cli/output/csv/base.rb +2 -0
  84. data/lib/wavefront-cli/output/csv/query.rb +14 -7
  85. data/lib/wavefront-cli/output/hcl.rb +2 -0
  86. data/lib/wavefront-cli/output/hcl/alert.rb +2 -0
  87. data/lib/wavefront-cli/output/hcl/base.rb +10 -4
  88. data/lib/wavefront-cli/output/hcl/dashboard.rb +17 -8
  89. data/lib/wavefront-cli/output/hcl/notificant.rb +2 -0
  90. data/lib/wavefront-cli/output/hcl/stdlib/array.rb +2 -0
  91. data/lib/wavefront-cli/output/hcl/stdlib/string.rb +2 -0
  92. data/lib/wavefront-cli/output/json.rb +2 -0
  93. data/lib/wavefront-cli/output/ruby.rb +2 -0
  94. data/lib/wavefront-cli/output/wavefront.rb +2 -0
  95. data/lib/wavefront-cli/output/wavefront/query.rb +7 -4
  96. data/lib/wavefront-cli/output/yaml.rb +2 -0
  97. data/lib/wavefront-cli/proxy.rb +15 -3
  98. data/lib/wavefront-cli/query.rb +14 -12
  99. data/lib/wavefront-cli/savedsearch.rb +2 -0
  100. data/lib/wavefront-cli/serviceaccount.rb +179 -0
  101. data/lib/wavefront-cli/settings.rb +2 -0
  102. data/lib/wavefront-cli/source.rb +2 -0
  103. data/lib/wavefront-cli/stdlib/array.rb +3 -0
  104. data/lib/wavefront-cli/stdlib/string.rb +17 -11
  105. data/lib/wavefront-cli/user.rb +10 -4
  106. data/lib/wavefront-cli/usergroup.rb +4 -2
  107. data/lib/wavefront-cli/version.rb +3 -1
  108. data/lib/wavefront-cli/webhook.rb +2 -0
  109. data/lib/wavefront-cli/write.rb +80 -46
  110. data/spec/.rubocop.yml +3 -16
  111. data/spec/constants.rb +21 -0
  112. data/spec/spec_helper.rb +8 -422
  113. data/spec/support/command_base.rb +82 -0
  114. data/spec/support/minitest_assertions.rb +262 -0
  115. data/spec/support/output_tester.rb +32 -0
  116. data/spec/support/supported_commands.rb +19 -0
  117. data/spec/test_mixins/acl.rb +169 -0
  118. data/spec/test_mixins/delete.rb +25 -0
  119. data/spec/test_mixins/deleteundelete.rb +106 -0
  120. data/spec/test_mixins/describe.rb +24 -0
  121. data/spec/test_mixins/dump.rb +43 -0
  122. data/spec/test_mixins/general.rb +11 -0
  123. data/spec/test_mixins/history.rb +35 -0
  124. data/spec/test_mixins/import.rb +65 -0
  125. data/spec/test_mixins/list.rb +29 -0
  126. data/spec/test_mixins/search.rb +98 -0
  127. data/spec/test_mixins/set.rb +47 -0
  128. data/spec/test_mixins/tag.rb +99 -0
  129. data/spec/wavefront-cli/alert_spec.rb +288 -111
  130. data/spec/wavefront-cli/apitoken_spec.rb +53 -24
  131. data/spec/wavefront-cli/base_spec.rb +9 -27
  132. data/spec/wavefront-cli/cloudintegration_spec.rb +65 -29
  133. data/spec/wavefront-cli/commands/alert_spec.rb +1 -0
  134. data/spec/wavefront-cli/commands/base_spec.rb +15 -13
  135. data/spec/wavefront-cli/commands/cloudintegration_spec.rb +1 -0
  136. data/spec/wavefront-cli/commands/config_spec.rb +3 -0
  137. data/spec/wavefront-cli/commands/dashboard_spec.rb +1 -0
  138. data/spec/wavefront-cli/commands/derivedmetric_spec.rb +1 -0
  139. data/spec/wavefront-cli/commands/event_spec.rb +1 -0
  140. data/spec/wavefront-cli/commands/link_spec.rb +1 -0
  141. data/spec/wavefront-cli/commands/message_spec.rb +1 -0
  142. data/spec/wavefront-cli/commands/metric_spec.rb +1 -0
  143. data/spec/wavefront-cli/commands/proxy_spec.rb +1 -0
  144. data/spec/wavefront-cli/commands/query_spec.rb +1 -0
  145. data/spec/wavefront-cli/commands/webhook_spec.rb +1 -0
  146. data/spec/wavefront-cli/commands/window_spec.rb +1 -0
  147. data/spec/wavefront-cli/commands/write_spec.rb +1 -0
  148. data/spec/wavefront-cli/config_spec.rb +25 -14
  149. data/spec/wavefront-cli/controller_spec.rb +13 -3
  150. data/spec/wavefront-cli/dashboard_spec.rb +125 -76
  151. data/spec/wavefront-cli/derivedmetric_spec.rb +83 -67
  152. data/spec/wavefront-cli/display/base_spec.rb +5 -7
  153. data/spec/wavefront-cli/display/printer/long_spec.rb +20 -16
  154. data/spec/wavefront-cli/display/printer/terse_spec.rb +5 -4
  155. data/spec/wavefront-cli/event_spec.rb +360 -18
  156. data/spec/wavefront-cli/externallink_spec.rb +92 -58
  157. data/spec/wavefront-cli/integration_spec.rb +129 -31
  158. data/spec/wavefront-cli/maintenancewindow_spec.rb +270 -32
  159. data/spec/wavefront-cli/message_spec.rb +73 -30
  160. data/spec/wavefront-cli/metric_spec.rb +60 -22
  161. data/spec/wavefront-cli/notificant_spec.rb +45 -32
  162. data/spec/wavefront-cli/opt_handler_spec.rb +4 -1
  163. data/spec/wavefront-cli/output/csv/query_spec.rb +21 -19
  164. data/spec/wavefront-cli/output/csv_spec.rb +5 -2
  165. data/spec/wavefront-cli/output/hcl_spec.rb +5 -2
  166. data/spec/wavefront-cli/output/helpers.rb +18 -0
  167. data/spec/wavefront-cli/output/json_spec.rb +3 -1
  168. data/spec/wavefront-cli/output/ruby_spec.rb +3 -1
  169. data/spec/wavefront-cli/output/wavefront/query_spec.rb +3 -1
  170. data/spec/wavefront-cli/output/wavefront_spec.rb +6 -4
  171. data/spec/wavefront-cli/output/yaml_spec.rb +3 -1
  172. data/spec/wavefront-cli/proxy_spec.rb +49 -27
  173. data/spec/wavefront-cli/query_spec.rb +174 -92
  174. data/spec/wavefront-cli/resources/responses/query.json +1 -0
  175. data/spec/wavefront-cli/resources/updates/alert.json +15 -0
  176. data/spec/wavefront-cli/resources/updates/dashboard.json +1 -0
  177. data/spec/wavefront-cli/savedsearch_spec.rb +35 -18
  178. data/spec/wavefront-cli/serviceaccount_spec.rb +399 -0
  179. data/spec/wavefront-cli/settings_spec.rb +42 -11
  180. data/spec/wavefront-cli/source_spec.rb +120 -23
  181. data/spec/wavefront-cli/stdlib/array_spec.rb +2 -1
  182. data/spec/wavefront-cli/stdlib/string_spec.rb +9 -6
  183. data/spec/wavefront-cli/user_spec.rb +278 -108
  184. data/spec/wavefront-cli/usergroup_spec.rb +152 -102
  185. data/spec/wavefront-cli/webhook_spec.rb +30 -15
  186. data/spec/wavefront-cli/write_spec.rb +25 -1
  187. data/wavefront-cli.gemspec +5 -3
  188. metadata +65 -21
  189. data/spec/wavefront-cli/commands/spec_helper.rb +0 -3
  190. data/spec/wavefront-cli/display/spec_helper.rb +0 -3
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'wavefront-sdk/support/mixins'
2
4
  require_relative 'base'
3
5
 
@@ -10,15 +12,15 @@ module WavefrontCli
10
12
  class Write < Base
11
13
  attr_reader :fmt
12
14
  include Wavefront::Mixins
13
- SPLIT_PATTERN = /\s(?=(?:[^"]|"[^"]*")*$)/
15
+ SPLIT_PATTERN = /\s(?=(?:[^"]|"[^"]*")*$)/.freeze
14
16
 
15
17
  # rubocop:disable Metrics/AbcSize
16
18
  def do_point
17
- p = { path: options[:'<metric>'],
18
- value: options[:'<value>'].delete('\\').to_f }
19
-
20
19
  tags = tags_to_hash(options[:tag])
21
20
 
21
+ p = { path: options[:'<metric>'],
22
+ value: options[:'<value>'].delete('\\').to_f }
23
+
22
24
  p[:tags] = tags unless tags.empty?
23
25
  p[:source] = options[:host] if options[:host]
24
26
  p[:ts] = parse_time(options[:time]) if options[:time]
@@ -32,17 +34,19 @@ module WavefrontCli
32
34
  process_input(options[:'<file>'])
33
35
  end
34
36
 
35
- # rubocop:disable Metrics/AbcSize
36
37
  def do_distribution
37
- p = { path: options[:'<metric>'],
38
- interval: options[:interval] || 'M',
39
- value: mk_dist }
38
+ send_point(make_distribution_point(tags_to_hash(options[:tag])))
39
+ end
40
40
 
41
- tags = tags_to_hash(options[:tag])
42
- p[:tags] = tags unless tags.empty?
43
- p[:source] = options[:host] if options[:host]
44
- p[:ts] = parse_time(options[:time]) if options[:time]
45
- send_point(p)
41
+ # rubocop:disable Metrics/AbcSize
42
+ def make_distribution_point(tags)
43
+ { path: options[:'<metric>'],
44
+ interval: options[:interval] || 'M',
45
+ tags: tags,
46
+ value: mk_dist }.tap do |p|
47
+ p[:source] = options[:host] if options[:host]
48
+ p[:ts] = parse_time(options[:time]) if options[:time]
49
+ end
46
50
  end
47
51
  # rubocop:enable Metrics/AbcSize
48
52
 
@@ -65,24 +69,26 @@ module WavefrontCli
65
69
  #
66
70
  def _sdk_class
67
71
  return 'Wavefront::Distribution' if distribution?
72
+
68
73
  'Wavefront::Write'
69
74
  end
70
75
 
71
76
  def distribution?
72
77
  return true if options[:distribution]
78
+
73
79
  options[:infileformat]&.include?('d')
74
80
  end
75
81
 
76
82
  def mk_creds
77
- { proxy: options[:proxy],
78
- port: options[:port] || default_port,
79
- socket: options[:socket],
83
+ { proxy: options[:proxy],
84
+ port: options[:port] || default_port,
85
+ socket: options[:socket],
80
86
  endpoint: options[:endpoint],
81
- token: options[:token] }
87
+ token: options[:token] }
82
88
  end
83
89
 
84
90
  def default_port
85
- distribution? ? 40000 : 2878
91
+ distribution? ? 40_000 : 2878
86
92
  end
87
93
 
88
94
  def validate_opts
@@ -90,10 +96,12 @@ module WavefrontCli
90
96
 
91
97
  if options[:using] == 'unix'
92
98
  return true if options[:socket]
99
+
93
100
  raise(WavefrontCli::Exception::CredentialError, 'No socket path.')
94
101
  end
95
102
 
96
103
  return true if options[:proxy]
104
+
97
105
  raise(WavefrontCli::Exception::CredentialError, 'No proxy address.')
98
106
  end
99
107
 
@@ -115,8 +123,7 @@ module WavefrontCli
115
123
  def send_point(point)
116
124
  call_write(point)
117
125
  rescue Wavefront::Exception::InvalidEndpoint
118
- abort format("Could not connect to proxy '%s:%s'.",
119
- options[:proxy], options[:port])
126
+ abort format("Could not connect to proxy '%<proxy>s:%<port>s'.", options)
120
127
  end
121
128
 
122
129
  # Read the input, from a file or from STDIN, and turn each line
@@ -237,6 +244,7 @@ module WavefrontCli
237
244
  options[:metric] ? [options[:metric], m].join('.') : m
238
245
  rescue TypeError
239
246
  return options[:metric] if options[:metric]
247
+
240
248
  raise
241
249
  end
242
250
 
@@ -264,14 +272,16 @@ module WavefrontCli
264
272
  # @raise WavefrontCli::Exception::UnparseableInput if the line
265
273
  # doesn't look right
266
274
  #
267
- # rubocop:disable Metrics/AbcSize
268
275
  # rubocop:disable Metrics/CyclomaticComplexity
276
+ # rubocop:disable Metrics/MethodLength
277
+ # rubocop:disable Metrics/AbcSize
269
278
  def process_line(line)
270
279
  return true if line.empty?
280
+
271
281
  chunks = line.split(SPLIT_PATTERN, fmt.length)
272
282
  enough_fields?(line) # can raise exception
273
283
 
274
- point = { path: extract_path(chunks),
284
+ point = { path: extract_path(chunks),
275
285
  value: extract_value(chunks) }
276
286
 
277
287
  tags = line_tags(chunks)
@@ -283,8 +293,9 @@ module WavefrontCli
283
293
  p[:interval] = options[:interval] || 'm' if fmt.include?('d')
284
294
  end
285
295
  end
286
- # rubocop:enable Metrics/CyclomaticComplexity
287
296
  # rubocop:enable Metrics/AbcSize
297
+ # rubocop:enable Metrics/MethodLength
298
+ # rubocop:enable Metrics/CyclomaticComplexity
288
299
 
289
300
  # We can get tags from the file, from the -T option, or both.
290
301
  # Merge them, making the -T win if there is a collision.
@@ -321,29 +332,49 @@ module WavefrontCli
321
332
  #
322
333
  # @param fmt [String] format of input file
323
334
  #
324
- # rubocop:disable Metrics/PerceivedComplexity
325
- # rubocop:disable Metrics/CyclomaticComplexity
326
- # rubocop:disable Metrics/AbcSize
327
335
  def valid_format?(fmt)
328
- err = if fmt.include?('v') && fmt.include?('d')
329
- "'v' and 'd' are mutually exclusive"
330
- elsif !fmt.include?('v') && !fmt.include?('d')
331
- "format string must include 'v' or 'd'"
332
- elsif !fmt.match(/^[dmstTv]+$/)
333
- 'unsupported field in format string'
334
- elsif fmt != fmt.squeeze
335
- 'repeated field in format string'
336
- elsif fmt.include?('T') && !fmt.end_with?('T')
337
- "if used, 'T' must come at end of format string"
338
- end
339
-
340
- return true if err.nil?
341
-
342
- raise(WavefrontCli::Exception::UnparseableInput, err)
343
- end
344
- # rubocop:enable Metrics/PerceivedComplexity
345
- # rubocop:enable Metrics/CyclomaticComplexity
346
- # rubocop:enable Metrics/AbcSize
336
+ format_string_has_v_or_d?(fmt)
337
+ format_string_does_not_have_v_and_d?(fmt)
338
+ format_string_is_all_valid_chars?(fmt)
339
+ format_string_has_unique_chars?(fmt)
340
+ format_string_has_big_t_only_at_the_end?(fmt)
341
+ end
342
+
343
+ def format_string_has_v_or_d?(fmt)
344
+ return true if fmt.include?('v') || fmt.include?('d')
345
+
346
+ raise(WavefrontCli::Exception::UnparseableInput,
347
+ "format string must include 'v' or 'd'")
348
+ end
349
+
350
+ def format_string_does_not_have_v_and_d?(fmt)
351
+ return true unless fmt.include?('v') && fmt.include?('d')
352
+
353
+ raise(WavefrontCli::Exception::UnparseableInput,
354
+ "'v' and 'd' are mutually exclusive")
355
+ end
356
+
357
+ def format_string_is_all_valid_chars?(fmt)
358
+ return true if fmt =~ /^[dmstTv]+$/
359
+
360
+ raise(WavefrontCli::Exception::UnparseableInput,
361
+ 'unsupported field in format string')
362
+ end
363
+
364
+ def format_string_has_unique_chars?(fmt)
365
+ return true if fmt.chars.sort == fmt.chars.uniq.sort
366
+
367
+ raise(WavefrontCli::Exception::UnparseableInput,
368
+ 'repeated field in format string')
369
+ end
370
+
371
+ def format_string_has_big_t_only_at_the_end?(fmt)
372
+ return true unless fmt.include?('T')
373
+ return true if fmt.end_with?('T')
374
+
375
+ raise(WavefrontCli::Exception::UnparseableInput,
376
+ "if used, 'T' must come at end of format string")
377
+ end
347
378
 
348
379
  # Make sure we have the right number of columns, according to
349
380
  # the format string. We want to take every precaution we can to
@@ -359,8 +390,11 @@ module WavefrontCli
359
390
  ncols = line.split(SPLIT_PATTERN).length
360
391
  return true if fmt.include?('T') && ncols >= fmt.length
361
392
  return true if ncols == fmt.length
393
+
362
394
  raise(WavefrontCli::Exception::UnparseableInput,
363
- format('Expected %s fields, got %s', fmt.length, ncols))
395
+ format('Expected %<expected>s fields, got %<got>s',
396
+ expected: fmt.length,
397
+ got: ncols))
364
398
  end
365
399
 
366
400
  # Although the SDK does value checking, we'll add another layer
data/spec/.rubocop.yml CHANGED
@@ -1,22 +1,9 @@
1
- AllCops:
2
- TargetRubyVersion: 2.2
3
-
4
- Style/FormatStringToken:
5
- Enabled: false
6
-
7
- # Allow long blocks of tests
8
- #
9
- Metrics/BlockLength:
10
- Max: 120
11
-
1
+ ---
12
2
  Metrics/MethodLength:
13
- Max: 100
3
+ Max: 30
14
4
 
15
5
  Metrics/AbcSize:
16
6
  Max: 45
17
7
 
18
8
  Metrics/ClassLength:
19
- Max: 300
20
-
21
- Metrics/CyclomaticComplexity:
22
- Max: 10
9
+ Max: 250
data/spec/constants.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'inifile'
5
+
6
+ # Constants for testing
7
+
8
+ ROOT = Pathname.new(__dir__).parent
9
+
10
+ CMD = 'wf'
11
+ TW = 80
12
+
13
+ ENDPOINT = 'metrics.wavefront.com'
14
+ TOKEN = '0123456789-ABCDEF'
15
+ RES_DIR = ROOT + 'spec' + 'wavefront-cli' + 'resources'
16
+ CF = RES_DIR + 'wavefront.conf'
17
+ CF_VAL = IniFile.load(CF)
18
+ JSON_POST_HEADERS = {
19
+ 'Content-Type': 'application/json', Accept: 'application/json'
20
+ }.freeze
21
+ TEE_ZERO = Time.now.freeze
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require 'webmock/minitest'
3
5
  require 'spy/integration'
@@ -8,47 +10,22 @@ require 'minitest/spec'
8
10
  require 'pathname'
9
11
  require_relative '../lib/wavefront-cli/controller'
10
12
 
11
- def all_commands
12
- cmd_dir = ROOT + 'lib' + 'wavefront-cli' + 'commands'
13
-
14
- files = cmd_dir.children.select do |f|
15
- f.extname == '.rb' && f.basename.to_s != 'base.rb'
16
- end
17
-
18
- files.each.with_object([]) { |c, a| a.<< c.basename.to_s.chomp('.rb') }
19
- end
20
-
21
13
  unless defined?(CMD)
22
14
  ROOT = Pathname.new(__FILE__).dirname.parent
23
- CMD = 'wavefront'.freeze
24
- ENDPOINT = 'metrics.wavefront.com'.freeze
25
- TOKEN = '0123456789-ABCDEF'.freeze
15
+ CMD = 'wavefront'
16
+ ENDPOINT = 'metrics.wavefront.com'
17
+ TOKEN = '0123456789-ABCDEF'
26
18
  RES_DIR = Pathname.new(__FILE__).dirname + 'wavefront-cli' + 'resources'
27
19
  CF = RES_DIR + 'wavefront.conf'
28
20
  CF_VAL = IniFile.load(CF)
29
21
  JSON_POST_HEADERS = {
30
22
  'Content-Type': 'application/json', Accept: 'application/json'
31
23
  }.freeze
32
- CMDS = all_commands.freeze
33
- BAD_TAG = '*BAD_TAG*'.freeze
24
+ BAD_TAG = '*BAD_TAG*'
34
25
  TW = 80
35
26
  HOME_CONFIG = Pathname.new(ENV['HOME']) + '.wavefront'
36
27
  end
37
28
 
38
- # Return an array of CLI permutations and the values to which they relate
39
- #
40
- def permutations
41
- [["-t #{TOKEN} -E #{ENDPOINT}", { t: TOKEN, e: ENDPOINT }],
42
- ["-c #{CF}", { t: CF_VAL['default']['token'],
43
- e: CF_VAL['default']['endpoint'] }],
44
- ["-c #{CF} -P other", { t: CF_VAL['other']['token'],
45
- e: CF_VAL['other']['endpoint'] }],
46
- ["-c #{CF} -P other -t #{TOKEN}", { t: TOKEN,
47
- e: CF_VAL['other']['endpoint'] }],
48
- ["-c #{CF} -E #{ENDPOINT}", { t: CF_VAL['default']['token'],
49
- e: ENDPOINT }]]
50
- end
51
-
52
29
  # Object returned by cmd_to_call. Has just enough methods to satisfy
53
30
  # the SDK
54
31
  #
@@ -70,356 +47,6 @@ end
70
47
 
71
48
  CANNED_RESPONSE = DummyResponse.new
72
49
 
73
- # Match a command to the final API call it should produce, applying
74
- # options in as many combinations as possible, and ensuring the
75
- # requisite display methods are called.
76
- #
77
- # @param cmd [String] command line args to supply to the Wavefront
78
- # @param call [Hash]
79
- # @param spies [Array[Hash]] array of spies to set up, for stubbing
80
- # methods in any class. Hash has keys :class, :method, :return.
81
- # rubocop:disable Metrics/AbcSize
82
- # rubocop:disable Metrics/PerceivedComplexity
83
- def cmd_to_call(word, args, call, sdk_class = nil, spies = [])
84
- headers = { 'Accept': /.*/,
85
- 'Accept-Encoding': /.*/,
86
- 'Authorization': 'Bearer 0123456789-ABCDEF',
87
- 'User-Agent': "wavefront-cli-#{WF_CLI_VERSION}" }
88
-
89
- sdk_class ||= Object.const_get("WavefrontCli::#{word.capitalize}")
90
-
91
- headers.merge!(call[:headers]) if call.key?(:headers)
92
- method = call[:method] || :get
93
- fmts = call[:formats] ? ['-f json', '-f yaml', '-f human', ''] : ['']
94
-
95
- permutations.each do |opts, vals|
96
- describe "with #{word} #{args}" do
97
- fmts.each do |fmt|
98
- cmd = "#{word} #{args} #{opts} #{fmt}"
99
-
100
- uri = if call[:regex]
101
- Regexp.new(call[:path])
102
- else
103
- 'https://' + vals[:e] + call[:path]
104
- end
105
-
106
- h = headers.dup
107
- h[:Authorization] = "Bearer #{vals[:t]}"
108
-
109
- it "runs #{cmd} and makes the correct API call" do
110
- if call.key?(:body)
111
- stub_request(method, uri).with(headers: h, body: call[:body])
112
- .to_return(body: {}.to_json,
113
- status: 200)
114
- else
115
- stub_request(method, uri).with(headers: h)
116
- .to_return(body: {}.to_json,
117
- status: 200)
118
- end
119
-
120
- require "wavefront-sdk/#{sdk_class.name.split('::').last.downcase}"
121
- spies.each do |spy|
122
- Spy.on_instance_method(
123
- Object.const_get(spy[:class]), spy[:method]
124
- ).and_return(spy[:return])
125
- end
126
-
127
- Spy.on_instance_method(
128
- Object.const_get('Wavefront::ApiCaller'), :respond
129
- ).and_return(CANNED_RESPONSE)
130
-
131
- d = Spy.on_instance_method(sdk_class, :display)
132
- WavefrontCliController.new(cmd.split)
133
- assert d.has_been_called?
134
- assert_requested(method, uri, headers: h)
135
- WebMock.reset!
136
- end
137
- end
138
- end
139
- end
140
- end
141
- # rubocop:enable Metrics/PerceivedComplexity
142
- # rubocop:enable Metrics/AbcSize
143
-
144
- # Test no-ops
145
- #
146
- # @param output [Array] [URI, params]
147
- #
148
- def cmd_noop(word, cmd, output, sdk_class = nil)
149
- cmd = [word] + cmd.split + ['--noop'] + ["-t #{TOKEN} -E #{ENDPOINT}"]
150
-
151
- exit_code = output == :impossible ? 1 : 0
152
-
153
- it "runs #{cmd.join(' ')} and gets output without an API call" do
154
- out, err = capture_io do
155
- sdk_class ||= Object.const_get("WavefrontCli::#{word.capitalize}")
156
- require "wavefront-sdk/#{sdk_class.name.split('::').last.downcase}"
157
- begin
158
- WavefrontCliController.new(cmd).run
159
- rescue SystemExit => e
160
- assert_equal(exit_code, e.status)
161
- end
162
- end
163
-
164
- if output == :impossible
165
- assert_equal('Multiple API call operations cannot be performed ' \
166
- "as no-ops.\n", err)
167
- assert_empty(out)
168
- else
169
- out = out.split("\n")
170
- refute_empty(out)
171
- assert_equal("SDK INFO: uri: #{output.first}", out.first)
172
- if output.size > 1
173
- assert_equal("SDK INFO: params: #{output.last}", out.last)
174
- end
175
- assert_empty(err)
176
- end
177
- end
178
- end
179
-
180
- # Wrapper to standard noop tests
181
- #
182
- def noop_tests(cmd, id, nodelete = false, pth = nil, sdk_class = nil)
183
- pth ||= cmd
184
- cmd_noop(cmd, 'list',
185
- ["GET https://metrics.wavefront.com/api/v2/#{pth}",
186
- offset: 0, limit: 100], sdk_class)
187
- cmd_noop(cmd, "describe #{id}",
188
- ["GET https://metrics.wavefront.com/api/v2/#{pth}/#{id}"],
189
- sdk_class)
190
-
191
- if nodelete == :skip
192
- elsif nodelete
193
- cmd_noop(cmd, "delete #{id}", :impossible, sdk_class)
194
- else
195
- cmd_noop(cmd, "delete #{id}",
196
- ["DELETE https://metrics.wavefront.com/api/v2/#{pth}/#{id}"],
197
- sdk_class)
198
- end
199
- end
200
-
201
- # Run a command we expect to fail, returning stdout and stderr
202
- #
203
- def fail_command(cmd)
204
- capture_io do
205
- begin
206
- WavefrontCliController.new(cmd.split).run
207
- rescue SystemExit => e
208
- assert_equal(1, e.status)
209
- end
210
- end
211
- end
212
-
213
- def invalid_something(cmd, subcmds, thing)
214
- subcmds.each do |sc|
215
- it "fails '#{sc}' because of an invalid #{thing}" do
216
- _out, err = fail_command("#{cmd} #{sc}")
217
- assert_match(/^'.*' is not a valid #{thing}.\n$/, err)
218
- end
219
- end
220
- end
221
-
222
- def invalid_tags(cmd, subcmds)
223
- subcmds.each do |sc|
224
- it "fails '#{sc}' because of an invalid tag" do
225
- _out, err = fail_command("#{cmd} #{sc}")
226
- assert_equal(err, "Invalid input. '#{BAD_TAG}' is not a valid tag.\n")
227
- end
228
- end
229
- end
230
-
231
- def invalid_ids(cmd, subcmds)
232
- subcmds.each do |sc|
233
- it "fails '#{sc}' on invalid input" do
234
- _out, err = fail_command("#{cmd} #{sc}")
235
- assert_match(/^'.+' is not a valid \w/, err)
236
- end
237
- end
238
- end
239
-
240
- # Without a token, you should get an error. If you don't supply an
241
- # endpoint, it will default to 'metrics.wavefront.com'. The
242
- # behaviour is different now, depending on whether you have
243
- # ~/.wavefront or not.
244
- #
245
- def missing_creds(cmd, subcmds)
246
- describe 'commands with missing credentials' do
247
- subcmds.each do |subcmd|
248
- it "'#{subcmd}' tells the user config file not found" do
249
- _out, err = fail_command("#{cmd} #{subcmd} -c /f")
250
- assert_equal("Configuration file '/f' not found.\n", err)
251
- end
252
-
253
- # I've generally got a config file on my dev box, but Travis
254
- # won't have one. Therefore this test probably won't get run
255
- # locally, but it will get run in CI, which is what counts.
256
- #
257
- next if HOME_CONFIG.exist?
258
-
259
- it "'#{subcmd}' errors and tells the user to generate config" do
260
- out, _err = fail_command("#{cmd} #{subcmd}")
261
- assert_match(/Please run 'wf config setup'/, out)
262
- end
263
- end
264
- end
265
- end
266
-
267
- def search_tests(word, id, klass = nil, pth = nil)
268
- pth ||= word
269
- cmd_to_call(word,
270
- "search id=#{id}",
271
- { method: :post, path: "/api/v2/search/#{pth}",
272
- body: { limit: 10,
273
- offset: 0,
274
- query: [{ key: 'id',
275
- value: id,
276
- matchingMethod: 'EXACT',
277
- negated: false }],
278
- sort: { field: 'id', ascending: true } },
279
- headers: JSON_POST_HEADERS },
280
- klass)
281
-
282
- cmd_to_call(word,
283
- "search id=#{id} thing!^word",
284
- { method: :post, path: "/api/v2/search/#{pth}",
285
- body: { limit: 10,
286
- offset: 0,
287
- query: [{ key: 'id',
288
- value: id,
289
- matchingMethod: 'EXACT',
290
- negated: false },
291
- { key: 'thing',
292
- value: 'word',
293
- matchingMethod: 'STARTSWITH',
294
- negated: true }],
295
- sort: { field: 'id', ascending: true } },
296
- headers: JSON_POST_HEADERS },
297
- klass)
298
-
299
- cmd_to_call(word,
300
- 'search id!~avoid',
301
- { method: :post, path: "/api/v2/search/#{pth}",
302
- body: { limit: 10,
303
- offset: 0,
304
- query: [{ key: 'id',
305
- value: 'avoid',
306
- matchingMethod: 'CONTAINS',
307
- negated: true }],
308
- sort: { field: 'id', ascending: true } },
309
- headers: JSON_POST_HEADERS },
310
- klass)
311
- end
312
-
313
- # Generic list tests, needed by most commands
314
- #
315
- def list_tests(cmd, pth = nil, klass = nil)
316
- pth ||= cmd
317
- cmd_to_call(cmd, 'list', { path: "/api/v2/#{pth}?limit=100&offset=0" },
318
- klass)
319
- cmd_to_call(cmd, 'list -L 50',
320
- { path: "/api/v2/#{pth}?limit=50&offset=0" },
321
- klass)
322
- cmd_to_call(cmd, 'list -L 20 -o 8',
323
- { path: "/api/v2/#{pth}?limit=20&offset=8" }, klass)
324
- cmd_to_call(cmd, 'list -o 60',
325
- { path: "/api/v2/#{pth}?limit=100&offset=60" },
326
- klass)
327
- end
328
-
329
- def tag_tests(cmd, id, bad_id, pth = nil, klass = nil)
330
- pth ||= cmd
331
- cmd_to_call(cmd, "tags #{id}", { path: "/api/v2/#{pth}/#{id}/tag" },
332
- klass)
333
- cmd_to_call(cmd, "tag set #{id} mytag",
334
- { method: :post,
335
- path: "/api/v2/#{pth}/#{id}/tag",
336
- body: %w[mytag].to_json,
337
- headers: JSON_POST_HEADERS }, klass)
338
- cmd_to_call(cmd, "tag set #{id} mytag1 mytag2",
339
- { method: :post,
340
- path: "/api/v2/#{pth}/#{id}/tag",
341
- body: %w[mytag1 mytag2].to_json,
342
- headers: JSON_POST_HEADERS }, klass)
343
- cmd_to_call(cmd, "tag add #{id} mytag",
344
- { method: :put, path: "/api/v2/#{pth}/#{id}/tag/mytag" },
345
- klass)
346
- cmd_to_call(cmd, "tag delete #{id} mytag",
347
- { method: :delete, path: "/api/v2/#{pth}/#{id}/tag/mytag" },
348
- klass)
349
- cmd_to_call(cmd, "tag clear #{id}", { method: :post,
350
- path: "/api/v2/#{pth}/#{id}/tag",
351
- body: [].to_json,
352
- headers: JSON_POST_HEADERS }, klass)
353
- invalid_ids(cmd, ["tags #{bad_id}",
354
- "tag clear #{bad_id}",
355
- "tag add #{bad_id} mytag",
356
- "tag delete #{bad_id} mytag"])
357
- invalid_tags(cmd, ["tag add #{id} #{BAD_TAG}",
358
- "tag delete #{id} #{BAD_TAG}"])
359
- end
360
-
361
- def acl_tests(cmd, id, bad_id, pth = nil, klass = nil)
362
- gid1 = '2659191e-aad4-4302-a94e-9667e1517127'
363
- pth ||= cmd
364
-
365
- cmd_to_call(cmd, "acls #{id}", { path: "/api/v2/#{pth}/acl?id=#{id}" },
366
- klass)
367
-
368
- spies = [{ class: "WavefrontCli::#{cmd.capitalize}",
369
- method: :do_acls,
370
- return: true }]
371
-
372
- cmd_to_call(cmd, "acl clear #{id}",
373
- { method: :put,
374
- path: "/api/v2/#{pth}/acl/set",
375
- body: [{ entityId: id,
376
- viewAcl: [],
377
- modifyAcl: %w[abcd-1234] }].to_json,
378
- headers: JSON_POST_HEADERS }, klass, [
379
- { class: "WavefrontCli::#{cmd.capitalize}",
380
- method: :everyone_id,
381
- return: 'abcd-1234' },
382
- { class: "WavefrontCli::#{cmd.capitalize}",
383
- method: :do_acls,
384
- return: true }
385
- ])
386
-
387
- cmd_to_call(cmd, "acl grant view on #{id} to testuser1 testuser2",
388
- { method: :post,
389
- path: "/api/v2/#{pth}/acl/add",
390
- body: [{ entityId: id,
391
- viewAcl: %w[testuser1 testuser2],
392
- modifyAcl: [] }].to_json,
393
- headers: JSON_POST_HEADERS }, klass, spies)
394
- cmd_to_call(cmd, "acl revoke view on #{id} from testuser1",
395
- { method: :post,
396
- path: "/api/v2/#{pth}/acl/remove",
397
- body: [{ entityId: id,
398
- viewAcl: %w[testuser1],
399
- modifyAcl: [] }].to_json,
400
- headers: JSON_POST_HEADERS }, klass, spies)
401
-
402
- cmd_to_call(cmd, "acl grant modify on #{id} to testuser1",
403
- { method: :post,
404
- path: "/api/v2/#{pth}/acl/add",
405
- body: [{ entityId: id,
406
- viewAcl: [],
407
- modifyAcl: %w[testuser1] }].to_json,
408
- headers: JSON_POST_HEADERS }, klass, spies)
409
- cmd_to_call(cmd, "acl revoke modify on #{id} from testuser1",
410
- { method: :post,
411
- path: "/api/v2/#{pth}/acl/remove",
412
- body: [{ entityId: id,
413
- viewAcl: [],
414
- modifyAcl: %w[testuser1] }].to_json,
415
- headers: JSON_POST_HEADERS }, klass, spies)
416
-
417
- invalid_ids(cmd, ["acls #{bad_id}",
418
- "acl clear #{bad_id}",
419
- "acl grant modify on #{bad_id} to testuser1",
420
- "acl revoke view on #{bad_id} from #{gid1}"])
421
- end
422
-
423
50
  # Unit tests
424
51
  #
425
52
  class CliMethodTest < MiniTest::Test
@@ -438,48 +65,6 @@ class CliMethodTest < MiniTest::Test
438
65
  end
439
66
  end
440
67
 
441
- # Load in a canned query response
442
- #
443
- def load_query_response
444
- JSON.parse(IO.read(RES_DIR + 'sample_query_response.json'),
445
- symbolize_names: true)
446
- end
447
-
448
- def load_raw_query_response
449
- JSON.parse(IO.read(RES_DIR + 'sample_raw_query_response.json'),
450
- symbolize_names: true)
451
- end
452
-
453
- # We keep a bunch of Wavefront API responses as text files alongside
454
- # canned responses in various formats. This class groups helpers for
455
- # doing that.
456
- #
457
- class OutputTester
458
- # @param file [String] filename to load
459
- # @param only_items [Bool] true for the items hash, false for the
460
- # whole loadedobject
461
- # @return [Object] canned raw responses used to test outputs
462
- #
463
- def load_input(file, only_items = true)
464
- ret = JSON.parse(IO.read(RES_DIR + 'display' + file),
465
- symbolize_names: true)
466
- only_items ? ret[:items] : ret
467
- end
468
-
469
- # @param file [String] file to load
470
- # @return [String]
471
- #
472
- def load_expected(file)
473
- IO.read(RES_DIR + 'display' + file)
474
- end
475
-
476
- def in_and_out(input, expected, only_items = true)
477
- [load_input(input, only_items), load_expected(expected)]
478
- end
479
- end
480
-
481
- OUTPUT_TESTER = OutputTester.new
482
-
483
68
  # stdlib extensions
484
69
  #
485
70
  class Hash
@@ -507,7 +92,8 @@ def command_output(word, method, klass = nil, infile = nil)
507
92
  infile ||= "#{word}-list.json"
508
93
  json = IO.read(RES_DIR + 'responses' + infile)
509
94
  resp = Wavefront::Response.new(json, 200)
510
- klass ||= Object.const_get(format('WavefrontCli::%s', word.capitalize))
95
+ klass ||= Object.const_get(format('WavefrontCli::%<class_word>s',
96
+ class_word: word.capitalize))
511
97
  klass = klass.new(format: :human)
512
98
 
513
99
  capture_io { klass.display(resp, method) }