wavefront-cli 4.2.1 → 4.3.0

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