wavefront-cli 8.2.0 → 8.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'etc'
4
+ require 'fileutils'
5
+ require 'open3'
6
+ require 'json'
7
+ require_relative 'constants'
8
+ require_relative 'exception'
9
+
10
+ module WavefrontCli
11
+ #
12
+ # Encapsulation of everything needed to manage the locally stored state of
13
+ # events opened by the CLI. This is our own addition, entirely separate from
14
+ # Wavefront's API.
15
+ #
16
+ # When the user creates an open-ended event (i.e. one that does not have and
17
+ # end time, and is not instantaneous) a state file is created in a local
18
+ # directory. (*)
19
+ #
20
+ # That directory is defined by the EVENT_STATE_DIR constant, but may be
21
+ # overriden with an option in the constructor. The tests do this.
22
+ #
23
+ # (*) The user may specifically request that no state file be created with
24
+ # the --nostate flag.
25
+ #
26
+ class EventStore
27
+ include WavefrontCli::Constants
28
+
29
+ attr_reader :dir, :options
30
+
31
+ # @param state_dir [Pathname] override the default dir for testing
32
+ #
33
+ def initialize(options, state_dir = nil)
34
+ @options = options
35
+ @dir = event_state_dir(state_dir) + (Etc.getlogin || 'notty')
36
+ create_dir(dir)
37
+ end
38
+
39
+ def state_file_needed?
40
+ !(options[:nostate] || options[:end] || options[:instant])
41
+ end
42
+
43
+ def event_file(id)
44
+ id =~ /^\d{13}:.+/ ? dir + id : nil
45
+ end
46
+
47
+ # We can override the temp directory with the WF_EVENT_STATE_DIR env var.
48
+ # This is primarily for testing, though someone may find a valid use for
49
+ # it.
50
+ #
51
+ def event_state_dir(state_dir = nil)
52
+ if ENV['WF_EVENT_STATE_DIR']
53
+ Pathname.new(ENV['WF_EVENT_STATE_DIR'])
54
+ elsif state_dir.nil?
55
+ EVENT_STATE_DIR
56
+ else
57
+ Pathname.new(state_dir)
58
+ end
59
+ end
60
+
61
+ # @param id [String,Nil] if this is falsey, returns the event on the top
62
+ # of the state stack, removing its state file. If it's an exact event
63
+ # ID, simply pass that ID back, NOT removing the state file. This is
64
+ # okay: the state file is cleaned up by WavefrontCli::Event when an
65
+ # event is closed. If it's a name but not an ID, return the ID of the
66
+ # most recent event with the given name.
67
+ # @return [String] the name of the most recent suitable event from the
68
+ # local stack directory.
69
+ #
70
+ def event(id)
71
+ if !id
72
+ pop_event!
73
+ elsif id =~ /^\d{13}:.+:\d+/
74
+ id
75
+ else
76
+ pop_event!(id)
77
+ end
78
+ end
79
+
80
+ # List events on the local stack
81
+ #
82
+ def list
83
+ events = dir.children
84
+ abort 'No locally recorded events.' if events.empty?
85
+
86
+ events
87
+ rescue Errno::ENOENT
88
+ raise(WavefrontCli::Exception::SystemError,
89
+ 'There is no event state directory on this host.')
90
+ end
91
+
92
+ # Run a command, stream stderr and stdout to the screen (they
93
+ # get combined -- could be an issue for someone somewhere) and
94
+ # return the command's exit code
95
+ #
96
+ def run_wrapped_cmd(cmd)
97
+ separator = '-' * (TW - 4)
98
+
99
+ puts "Command output follows, on STDERR:\n#{separator}"
100
+ ret = nil
101
+
102
+ Open3.popen2e(cmd) do |_in, out, thr|
103
+ # rubocop:disable Lint/AssignmentInCondition
104
+ while l = out.gets do warn l end
105
+ # rubocop:enable Lint/AssignmentInCondition
106
+ ret = thr.value.exitstatus
107
+ end
108
+
109
+ puts separator
110
+ ret
111
+ end
112
+
113
+ # Write a state file. We put the hosts bound to the event into the file.
114
+ # These aren't currently used by anything in the CLI, but they might be
115
+ # useful to someone, somewhere, someday.
116
+ # @return [Nil]
117
+ #
118
+ def create!(id)
119
+ return unless state_file_needed?
120
+
121
+ fname = dir + id
122
+ File.open(fname, 'w') { |fh| fh.puts(event_file_data) }
123
+ puts "Event state recorded at #{fname}."
124
+ rescue StandardError
125
+ puts 'NOTICE: event was created but state file was not.'
126
+ end
127
+
128
+ # Record event data in the state file. We don't currently use it, but it
129
+ # might be useful to someone someday.
130
+ # @return [String]
131
+ #
132
+ def event_file_data
133
+ { hosts: options[:host],
134
+ description: options[:desc],
135
+ severity: options[:severity],
136
+ tags: options[:evtag] }.to_json
137
+ end
138
+
139
+ def create_dir(state_dir)
140
+ FileUtils.mkdir_p(state_dir)
141
+ raise unless state_dir.exist? &&
142
+ state_dir.directory? &&
143
+ state_dir.writable?
144
+ rescue StandardError
145
+ raise(WavefrontCli::Exception::SystemError,
146
+ "Cannot create writable system directory at '#{state_dir}'.")
147
+ end
148
+
149
+ # Get the last event this script created. If you supply a name, you get
150
+ # the last event with that name. If not, you get the last event. Note the
151
+ # '!': this method (potentially) has side effects.
152
+ # @param name [String] name of event. This is the middle part of the real
153
+ # event name: the only part supplied by the user.
154
+ # @return [Array[timestamp, event_name]]
155
+ #
156
+ def pop_event!(name = nil)
157
+ return false unless dir.exist?
158
+
159
+ list = local_events_with_name(name)
160
+ return false if list.empty?
161
+
162
+ ev_file = list.max
163
+ File.unlink(ev_file)
164
+ ev_file.basename.to_s
165
+ end
166
+
167
+ # Event names are of the form `1609860826095:name:0`
168
+ # @param name [String] the user-specified (middle) portion of an event ID
169
+ # @return [Array[String]] list of matching events
170
+ #
171
+ def local_events_with_name(name = nil)
172
+ return list unless name
173
+
174
+ list.select { |f| f.basename.to_s.split(':')[1] == name }
175
+ end
176
+ end
177
+ end
@@ -32,6 +32,10 @@ module WavefrontCli
32
32
  version_info(raw).sort_by { |p| Gem::Version.new(p[:version]) }.reverse
33
33
  end
34
34
 
35
+ def do_shutdown
36
+ wf.shutdown(options[:'<id>'])
37
+ end
38
+
35
39
  def version_info(raw)
36
40
  raw.response.items.map do |i|
37
41
  { id: i.id, version: i.version, name: i.name }
@@ -23,7 +23,8 @@ module WavefrontCli
23
23
  end
24
24
 
25
25
  alias do_groups do_describe
26
- alias do_permissions do_describe
26
+ alias do_ingestionpolicy do_describe
27
+ alias do_roles do_describe
27
28
 
28
29
  def do_create
29
30
  wf_user_id?(options[:'<id>'])
@@ -95,7 +96,7 @@ module WavefrontCli
95
96
  def extra_validation
96
97
  validate_groups
97
98
  validate_tokens
98
- validate_perms
99
+ validate_ingestion_policy
99
100
  end
100
101
 
101
102
  def validator_exception
@@ -157,15 +158,18 @@ module WavefrontCli
157
158
  !options[:inactive]
158
159
  end
159
160
 
161
+ # rubocop:disable Metrics/AbcSize
160
162
  def user_body
161
163
  { identifier: options[:'<id>'],
162
164
  active: active_account?,
163
- groups: options[:permission],
165
+ ingestionPolicyId: options[:policy],
164
166
  tokens: options[:usertoken],
165
- userGroups: options[:group] }.tap do |b|
167
+ roles: options[:role],
168
+ userGroups: options[:group] }.compact.tap do |b|
166
169
  b[:description] = options[:desc] if options[:desc]
167
170
  end
168
171
  end
172
+ # rubocop:enable Metrics/AbcSize
169
173
 
170
174
  def item_dump_call
171
175
  wf.list.response
@@ -175,12 +179,18 @@ module WavefrontCli
175
179
  options[:group].each { |g| wf_usergroup_id?(g) }
176
180
  end
177
181
 
182
+ def validate_roles
183
+ options[:role].each { |r| wf_role_id?(r) }
184
+ end
185
+
178
186
  def validate_tokens
179
187
  options[:usertoken].each { |t| wf_apitoken_id?(t) }
180
188
  end
181
189
 
182
- def validate_perms
183
- options[:permission].each { |p| wf_permission?(p) }
190
+ def validate_ingestion_policy
191
+ return true unless options[:policy]
192
+
193
+ wf_ingestionpolicy_id?(options[:policy])
184
194
  end
185
195
 
186
196
  def descriptive_name
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- WF_CLI_VERSION = '8.2.0'
3
+ WF_CLI_VERSION = '8.5.0'
@@ -22,7 +22,6 @@ unless defined?(CMD)
22
22
  'Content-Type': 'application/json', Accept: 'application/json'
23
23
  }.freeze
24
24
  BAD_TAG = '*BAD_TAG*'
25
- TW = 80
26
25
  HOME_CONFIG = Pathname.new(ENV['HOME']) + '.wavefront'
27
26
  end
28
27
 
@@ -22,6 +22,18 @@ class WavefrontCliHelpTest < MiniTest::Test
22
22
  assert_match(/^ \w+ --help$/, e.message)
23
23
  end
24
24
 
25
+ def test_commands_no_args
26
+ SupportedCommands.new.all.each do |cmd|
27
+ _test_command_no_args(cmd)
28
+ end
29
+ end
30
+
31
+ def _test_command_no_args(cmd)
32
+ capture_io { WavefrontCliController.new([cmd]) }
33
+ rescue SystemExit => e
34
+ assert e.message.end_with?("wf #{cmd} --help")
35
+ end
36
+
25
37
  def test_version
26
38
  capture_io { WavefrontCliController.new(%w[--version]) }
27
39
  rescue SystemExit => e
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'tmpdir'
5
5
  require_relative '../support/command_base'
6
+ require_relative '../test_mixins/tag'
6
7
  require_relative '../../lib/wavefront-cli/event'
7
8
  require 'wavefront-sdk/support/mixins'
8
9
 
@@ -15,12 +16,11 @@ class EventEndToEndTest < EndToEndTest
15
16
  attr_reader :test_state_dir
16
17
 
17
18
  include Wavefront::Mixins
18
- include WavefrontCliTest::Describe
19
- include WavefrontCliTest::Delete
20
- # Ones above work, ones below don't
19
+ # include WavefrontCliTest::Describe
20
+ # include WavefrontCliTest::Delete
21
21
  # include WavefrontCliTest::Search
22
- # include WavefrontCliTest::Set
23
- # include WavefrontCliTest::Tags
22
+ # #include WavefrontCliTest::Set
23
+ # include WavefrontCliTest::Tag
24
24
 
25
25
  def before_setup
26
26
  @test_state_dir = Pathname.new(Dir.mktmpdir)
@@ -31,11 +31,6 @@ class EventEndToEndTest < EndToEndTest
31
31
  FileUtils.rm_r(test_state_dir)
32
32
  end
33
33
 
34
- def cmd_instance
35
- cmd_class.new(event_state_dir: TEST_EVENT_DIR)
36
- puts cmd_class
37
- end
38
-
39
34
  def test_list_no_options
40
35
  str = '/api/v2/event\?' \
41
36
  'earliestStartTimeEpochMillis=\d{13}+&' \
@@ -65,6 +60,22 @@ class EventEndToEndTest < EndToEndTest
65
60
  end
66
61
  end
67
62
 
63
+ def test_show_with_no_local_events
64
+ assert_exits_with('No locally recorded events.', 'show')
65
+ end
66
+
67
+ def test_show
68
+ setup_test_state_dir
69
+
70
+ out, err = capture_io do
71
+ assert_raises(SystemExit) { wf.new("event show -c #{CF}".split) }
72
+ end
73
+
74
+ assert_empty(err)
75
+ assert_equal("1568133440530:ev3:0\n1568133440520:ev2:0\n" \
76
+ "1568133440515:ev1:1\n1568133440510:ev1:0\n", out)
77
+ end
78
+
68
79
  def test_create
69
80
  mock_id = "#{start_time}:#{event_name}:1"
70
81
  state_file = state_dir + mock_id
@@ -100,8 +111,8 @@ class EventEndToEndTest < EndToEndTest
100
111
  refute state_file.exist?
101
112
 
102
113
  out, err = capture_io do
103
- assert_cmd_posts('create -d reason -H host1 -H host2 -g ' \
104
- "mytag #{event_name}",
114
+ assert_cmd_posts('create -d reason -H host1 -H host2 -g mytag ' \
115
+ "#{event_name}",
105
116
  '/api/v2/event',
106
117
  { name: event_name,
107
118
  startTime: a_ms_timestamp,
@@ -148,15 +159,6 @@ class EventEndToEndTest < EndToEndTest
148
159
  assert_match(/\ntags tag1\n tag2\n/, out)
149
160
  end
150
161
 
151
- def test_close_named_event
152
- quietly do
153
- assert_cmd_posts('close 1568133440520:ev2:0',
154
- '/api/v2/event/1568133440520:ev2:0/close')
155
- end
156
-
157
- assert_abort_on_missing_creds("close #{id}")
158
- end
159
-
160
162
  def test_close_with_no_local_events
161
163
  quietly { assert_cmd_posts("close #{id}", "/api/v2/event/#{id}/close") }
162
164
  assert_exits_with('No locally recorded events.', 'close')
@@ -212,26 +214,47 @@ class EventEndToEndTest < EndToEndTest
212
214
  end
213
215
  end
214
216
 
215
- def test_show
216
- setup_test_state_dir
217
+ def test_window_start
218
+ wfse = WavefrontCli::Event.new(start: wall_time[0], end: wall_time[1])
219
+ assert_kind_of(Numeric, wfse.window_start)
220
+ assert_equal(epoch_ms_time[0], wfse.window_start)
221
+ end
217
222
 
218
- out, err = capture_io do
219
- assert_raises(SystemExit) { wf.new("event show -c #{CF}".split) }
220
- end
223
+ def test_window_end
224
+ wfse = WavefrontCli::Event.new(start: wall_time[0], end: wall_time[1])
225
+ assert_kind_of(Numeric, wfse.window_end)
226
+ assert_equal(epoch_ms_time[1], wfse.window_end)
227
+ end
221
228
 
222
- assert_empty(err)
223
- assert_equal("1568133440530:ev3:0\n1568133440520:ev2:0\n" \
224
- "1568133440515:ev1:1\n1568133440510:ev1:0\n", out)
229
+ def test_list_args_defaults
230
+ wfe = WavefrontCli::Event.new({})
231
+ x = wfe.list_args
232
+ assert_instance_of(Array, x)
233
+ assert_equal(4, x.size)
234
+ assert_in_delta(((Time.now - 600).to_i * 1000), x[0], 1000)
235
+ assert_in_delta((Time.now.to_i * 1000), x[1], 1000)
236
+ assert_equal(100, x[2])
237
+ assert_nil(x[3])
238
+ end
239
+
240
+ def test_list_args_options
241
+ wfse = WavefrontCli::Event.new(limit: 55,
242
+ start: wall_time[0],
243
+ cursor: id,
244
+ end: wall_time[1])
245
+ x = wfse.list_args
246
+ assert_instance_of(Array, x)
247
+ assert_equal(4, x.size)
248
+ assert_equal(epoch_ms_time[0], x[0])
249
+ assert_equal(epoch_ms_time[1], x[1])
250
+ assert_equal(55, x[2])
251
+ assert_equal(id, x[3])
225
252
  end
226
253
 
227
254
  private
228
255
 
229
256
  def id
230
- '1481553823153:testev'
231
- end
232
-
233
- def event_name
234
- 'test_event'
257
+ '1481553823153:testev:0'
235
258
  end
236
259
 
237
260
  def invalid_id
@@ -242,18 +265,24 @@ class EventEndToEndTest < EndToEndTest
242
265
  'event'
243
266
  end
244
267
 
268
+ def event_name
269
+ 'test_event'
270
+ end
271
+
245
272
  def start_time
246
273
  1_481_553_823_153
247
274
  end
248
275
 
249
- def import_fields
250
- %i[method title creatorId triggers template]
276
+ def epoch_ms_time
277
+ wall_time.map { |t| (t.to_i * 1000) }
251
278
  end
252
279
 
253
280
  def state_dir
254
281
  test_state_dir + (Etc.getlogin || 'notty')
255
282
  end
256
283
 
284
+ # Puts some test events in the state directory
285
+ #
257
286
  def setup_test_state_dir
258
287
  FileUtils.mkdir_p(state_dir)
259
288
 
@@ -285,79 +314,10 @@ class EventEndToEndTest < EndToEndTest
285
314
  "https://#{perm[:endpoint]}/api/v2/event/#{mock_id}/close")
286
315
  .with(body: 'null')
287
316
  end
288
- end
289
-
290
- # Unit tests for class methods
291
- #
292
- class EventMethodTests < Minitest::Test
293
- attr_reader :wf, :wfse
294
-
295
- def setup
296
- @wf = WavefrontCli::Event.new({})
297
- @wfse = WavefrontCli::Event.new(start: wall_time[0],
298
- end: wall_time[1],
299
- limit: 55,
300
- cursor: '1481553823153:testev')
301
- end
302
-
303
- def test_create_dir_ok
304
- base = Pathname.new(Dir.mktmpdir)
305
- dir = base + 'testdir'
306
- refute dir.exist?
307
- wf.create_dir(dir)
308
- assert dir.exist?
309
- dir.unlink
310
- base.unlink
311
- end
312
-
313
- def test_create_dir_fail
314
- spy = Spy.on(FileUtils, :mkdir_p).and_return(false)
315
-
316
- assert_raises(WavefrontCli::Exception::SystemError) do
317
- wf.create_dir(Pathname.new('/any/old/directory'))
318
- end
319
-
320
- assert spy.has_been_called?
321
- spy.unhook
322
- end
323
-
324
- def test_list_args_defaults
325
- x = wf.list_args
326
- assert_instance_of(Array, x)
327
- assert_equal(4, x.size)
328
- assert_in_delta(((Time.now - 600).to_i * 1000), x[0], 1000)
329
- assert_in_delta((Time.now.to_i * 1000), x[1], 1000)
330
- assert_equal(100, x[2])
331
- assert_nil(x[3])
332
- end
333
-
334
- def test_list_args_options
335
- x = wfse.list_args
336
- assert_instance_of(Array, x)
337
- assert_equal(4, x.size)
338
- assert_equal(epoch_ms_time[0], x[0])
339
- assert_equal(epoch_ms_time[1], x[1])
340
- assert_equal(55, x[2])
341
- assert_equal('1481553823153:testev', x[3])
342
- end
343
-
344
- def test_window_start
345
- assert_kind_of(Numeric, wf.window_start)
346
- assert_equal(epoch_ms_time[0], wfse.window_start)
347
- end
348
-
349
- def test_window_end
350
- assert_kind_of(Numeric, wf.window_end)
351
- assert_equal(epoch_ms_time[1], wfse.window_end)
352
- end
353
317
 
354
- private
355
-
356
- def wall_time
357
- [Time.at(1_568_112_000), Time.at(1_568_112_999)]
358
- end
359
-
360
- def epoch_ms_time
361
- wall_time.map { |t| (t.to_i * 1000) }
318
+ # Event searching uses a cursor, not an offset
319
+ #
320
+ def cannot_handle_offsets
321
+ true
362
322
  end
363
323
  end