wavefront-cli 8.4.1 → 8.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d96bb27765c031f9998dfa45406581de1852f0fbdc0abff4bcea45b679963299
4
- data.tar.gz: d40011ef4d521dd738fb594095366ee7e506bc35c9bad83e2ad421eeedab16bb
3
+ metadata.gz: 363b1be94331165aacf1538c797d07bdbefda71e801335bb4e35df97cb0bd950
4
+ data.tar.gz: 5563b27f93c679dde5947c220d80c37816ba6faf8844d08111cbf7dcc65a76c1
5
5
  SHA512:
6
- metadata.gz: f4777538a7b6f116f56c24382b9f3ea54ca78d5306009a3876857ff963f481561ff28e5c925cf0e62152e686caaa3c54e3abe90f48f16cb6e7cd6f87ede9f857
7
- data.tar.gz: 1bfab61e86f92ff9284207627615f07a701092afb6bd6f839471833db3610fb60c8e013c56b57a50329c67650e1a778c6581adb61804f4365675cb1838cb7c4f
6
+ metadata.gz: ce61f68ba09a6ba395143fa92abeadcdb0de127f8991fe61b979ae49379d026973c5d47a09d6d4f0b306006a15f95a717cac02160af0b727f2858ba66a9fddae
7
+ data.tar.gz: 111d9d52a0e0c3955164e1872198531b9e2bd61184dc430050029efdf8d19f9cd845885a7f1d5d5685d03dbf7de0845453d95f888e13eb0195b83fd25437116d
data/HISTORY.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 8.5.0 (2021-01-12)
4
+ * Allow attachment of roles and ingestion policies when creating service
5
+ accounts.
6
+ * Improved introspection of service accounts.
7
+ * Refactor `event` command. This improves test coverage, fixes handling
8
+ event names with numeric suffix, and fixes searching.
9
+
3
10
  ## 8.4.1 (2021-01-04)
4
11
  * Bugfix docopt error on `event` command.
5
12
 
@@ -10,15 +10,14 @@ class WavefrontCommandEvent < WavefrontCommandBase
10
10
  end
11
11
 
12
12
  def _commands
13
- ["list #{CMN} [-l] [-O fields] [-s start] [-e end] " \
14
- '[-L limit] [-o cursor]',
13
+ ["list #{CMN} [-l] [-O fields] [-s start] [-e end] [-L limit] [-o cursor]",
15
14
  "describe #{CMN} <id>",
16
15
  "create #{CMN} [-d description] [-s start] [-i | -e end] " \
17
16
  '[-S severity] [-T type] [-H host...] [-g tag...] [-N] <event>',
18
17
  "close #{CMN} [<id>]",
19
18
  "delete #{CMN} <id>",
20
19
  "set #{CMN} <key=value> <id>",
21
- "search #{CMN} [-o offset] [-L limit] [-l] [-O fields] <condition>...",
20
+ "search #{CMN} [-al] [-o cursor] [-L limit] [-O fields] <condition>...",
22
21
  "wrap #{CMN} [-C command] [-d description] [-S severity] [-T type] " \
23
22
  '[-H host...] [-g tag...] <event>',
24
23
  tag_commands,
@@ -28,6 +27,7 @@ class WavefrontCommandEvent < WavefrontCommandBase
28
27
  def _options
29
28
  [common_options,
30
29
  "-l, --long list #{things} in detail",
30
+ "-a, --all list all #{things}",
31
31
  "-o, --cursor=EVENT start listing from given #{thing}",
32
32
  '-O, --fields=F1,F2,... only show given fields',
33
33
  "-L, --limit=COUNT number of #{things} to list",
@@ -20,14 +20,15 @@ class WavefrontCommandServiceaccount < WavefrontCommandBase
20
20
  def _commands
21
21
  ["list #{CMN} [-l] [-O fields]",
22
22
  "describe #{CMN} <id>",
23
- "create #{CMN} [-I] [-d description] [-p permission...] [-g group...] " \
24
- '[-k usertoken...] <id>',
23
+ "create #{CMN} [-I] [-d description] [-p policy] [-r role...] " \
24
+ '[-g group...] [-k usertoken...] <id>',
25
25
  "activate #{CMN} <id>",
26
26
  "delete #{CMN} <account>...",
27
27
  "deactivate #{CMN} <id>",
28
28
  "dump #{CMN}",
29
29
  "groups #{CMN} <id>",
30
- "permissions #{CMN} <id>",
30
+ "roles #{CMN} <id>",
31
+ "ingestionpolicy #{CMN} <id>",
31
32
  "join #{CMN} <id> <group>...",
32
33
  "leave #{CMN} <id> <group>...",
33
34
  "grant #{CMN} <permission> to <id>",
@@ -52,7 +53,8 @@ class WavefrontCommandServiceaccount < WavefrontCommandBase
52
53
  "-U, --upsert import new or update existing #{thing}",
53
54
  "-I, --inactive create an inactive #{thing}",
54
55
  "-d, --desc=STRING description of #{thing}",
55
- "-p, --permission=STRING give #{thing} this permission",
56
+ "-r, --role=STRING give #{thing} this role",
57
+ "-p, --policy=STRING give #{thing} this ingestion policy",
56
58
  "-g, --group=STRING add #{thing} to this user group",
57
59
  '-N, --name=STRING name of token',
58
60
  '-k, --usertoken=STRING API token']
@@ -35,14 +35,22 @@ module WavefrontDisplay
35
35
  end
36
36
  end
37
37
 
38
+ def do_roles
39
+ if data[:roles].empty?
40
+ puts 'Account does not have any roles attached.'
41
+ else
42
+ data[:roles].each { |r| puts format('%<id>s (%<name>s)', r) }
43
+ end
44
+ end
45
+
38
46
  alias do_join do_groups
39
47
  alias do_leave do_groups
40
48
 
41
- def do_permissions
42
- if data[:groups].empty?
43
- puts 'Account does not have any Wavefront permissions.'
49
+ def do_ingestionpolicy
50
+ if data[:ingestionPolicy].empty?
51
+ puts 'Account does not have an ingestion policy attached.'
44
52
  else
45
- puts data[:groups]
53
+ puts format('%<id>s (%<name>s)', data[:ingestionPolicy])
46
54
  end
47
55
  end
48
56
 
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fileutils'
4
- require 'open3'
5
- require 'etc'
6
3
  require 'wavefront-sdk/support/mixins'
7
4
  require_relative 'base'
5
+ require_relative 'event_store'
8
6
  require_relative 'command_mixins/tag'
9
7
 
10
8
  module WavefrontCli
@@ -12,14 +10,13 @@ module WavefrontCli
12
10
  # CLI coverage for the v2 'event' API.
13
11
  #
14
12
  class Event < Base
15
- attr_reader :state_dir
13
+ attr_reader :state
16
14
 
17
15
  include Wavefront::Mixins
18
16
  include WavefrontCli::Mixin::Tag
19
17
 
20
- def post_initialize(_options)
21
- @state_dir = event_state_dir + (Etc.getlogin || 'notty')
22
- create_dir(state_dir)
18
+ def post_initialize(options)
19
+ @state = WavefrontCli::EventStore.new(options)
23
20
  end
24
21
 
25
22
  def do_list
@@ -32,38 +29,14 @@ module WavefrontCli
32
29
  t_start = parse_time(opts[:start], true)
33
30
  body = create_body(opts, t_start)
34
31
  resp = wf.create(body)
35
- create_state_file(resp.response[:id]) if state_file_needed?(opts)
36
- resp
37
- end
38
-
39
- def state_file_needed?(opts)
40
- !(opts[:nostate] || opts[:end] || opts[:instant])
41
- end
42
-
43
- # The user doesn't have to give us an event ID. If no event
44
- # name is given, we'll pop the last event off the stack. If an
45
- # event name is given and it doesn't look like a full WF event
46
- # name, we'll look for something on the stack. If it does look
47
- # like a real event, we'll make an API call straight away.
48
- #
49
- def do_close(id = nil)
50
- id ||= options[:'<id>']
51
- ev = local_event(id)
52
- ev_file = event_file(id)
53
-
54
- abort "No locally stored event matches '#{id}'." unless ev
55
-
56
- res = wf.close(ev)
57
- ev_file.unlink if ev_file&.exist? && res.status.code == 200
58
- res
59
- end
32
+ return if opts[:noop]
60
33
 
61
- def event_file(id)
62
- id =~ /^\d{13}:.+/ ? state_dir + id : nil
34
+ state.create!(resp.response[:id])
35
+ resp
63
36
  end
64
37
 
65
38
  def do_show
66
- events = local_event_list
39
+ events = state.list
67
40
 
68
41
  if events.size.zero?
69
42
  puts 'No open events.'
@@ -78,21 +51,54 @@ module WavefrontCli
78
51
  create_opts = options
79
52
  create_opts[:desc] ||= create_opts[:command]
80
53
  event_id = do_create(create_opts).response.id
81
- exit_code = run_wrapped_cmd(options[:command])
54
+ exit_code = state.run_wrapped_cmd(options[:command])
82
55
  do_close(event_id)
83
56
  puts "Command exited #{exit_code}."
84
57
  exit exit_code
85
58
  end
86
59
 
87
- # We can override the temp directory with the WF_EVENT_STATE_DIR. This is
88
- # primarily for testing.
60
+ # The user doesn't have to give us an event ID. If no event name is given,
61
+ # we'll pop the last event off the stack. If an event name is given and it
62
+ # doesn't look like a full WF event name, we'll look for something on the
63
+ # stack. If it does look like a real event, we'll make an API call
64
+ # straight away.
89
65
  #
90
- def event_state_dir
91
- if ENV['WF_EVENT_STATE_DIR']
92
- Pathname.new(ENV['WF_EVENT_STATE_DIR'])
93
- else
94
- EVENT_STATE_DIR
95
- end
66
+ def do_close(id = nil)
67
+ id ||= options[:'<id>']
68
+ ev = state.event(id)
69
+ ev_file = state.event_file(id)
70
+
71
+ abort "No locally stored event matches '#{id}'." unless ev
72
+
73
+ res = wf.close(ev)
74
+ ev_file.unlink if ev_file&.exist? && res.status.code == 200
75
+ res
76
+ end
77
+
78
+ # We have to override the normal validation methods because an event can
79
+ # be referred to by only a part of its name. This happens when the user
80
+ # refers to events on the local stack.
81
+ #
82
+ def validate_input
83
+ validate_id if options[:'<id>'] && !options[:close]
84
+ validate_tags if options[:'<tag>']
85
+ validate_tags(:evtag) if options[:evtag]
86
+ send(:extra_validation) if respond_to?(:extra_validation)
87
+ end
88
+
89
+ def list_args
90
+ [window_start,
91
+ window_end,
92
+ options[:limit] || 100,
93
+ options[:cursor] || nil]
94
+ end
95
+
96
+ def window_start
97
+ parse_time((options[:start] || Time.now - 600), true)
98
+ end
99
+
100
+ def window_end
101
+ parse_time((options[:end] || Time.now), true)
96
102
  end
97
103
 
98
104
  # return [Hash] body for #create() method
@@ -123,128 +129,5 @@ module WavefrontCli
123
129
  r[:type] = opts[:type] if opts[:type]
124
130
  end
125
131
  end
126
-
127
- # @return a local event from the stack directory
128
- #
129
- def local_event(id)
130
- if !id
131
- pop_event
132
- elsif id =~ /^\d{13}:.+/
133
- id
134
- else
135
- pop_event(id)
136
- end
137
- end
138
-
139
- def local_event_list
140
- events = state_dir.children
141
- abort 'No locally recorded events.' if events.empty?
142
-
143
- events
144
- rescue Errno::ENOENT
145
- raise(WavefrontCli::Exception::SystemError,
146
- 'There is no event state directory on this host.')
147
- end
148
-
149
- # Run a command, stream stderr and stdout to the screen (they
150
- # get combined -- could be an issue for someone somewhere) and
151
- # return the command's exit code
152
- #
153
- def run_wrapped_cmd(cmd)
154
- separator = '-' * (TW - 4)
155
-
156
- puts "Command output follows, on STDERR:\n#{separator}"
157
- ret = nil
158
-
159
- Open3.popen2e(cmd) do |_in, out, thr|
160
- # rubocop:disable Lint/AssignmentInCondition
161
- while l = out.gets do warn l end
162
- # rubocop:enable Lint/AssignmentInCondition
163
- ret = thr.value.exitstatus
164
- end
165
-
166
- puts separator
167
- ret
168
- end
169
-
170
- # Write a state file. We put the hosts bound to the event into the
171
- # file. These aren't currently used by anything in the CLI, but they
172
- # might be useful to someone, somewhere, someday.
173
- #
174
- def create_state_file(id)
175
- fname = state_dir + id
176
- File.open(fname, 'w') { |fh| fh.puts(event_file_data) }
177
- puts "Event state recorded at #{fname}."
178
- rescue StandardError
179
- puts 'NOTICE: event was created but state file was not.'
180
- end
181
-
182
- # Record event data in the state file. We don't currently use it, but it
183
- # might be useful to someone someday.
184
- #
185
- # @return [String]
186
- #
187
- def event_file_data
188
- { hosts: options[:host],
189
- description: options[:desc],
190
- severity: options[:severity],
191
- tags: options[:evtag] }.to_json
192
- end
193
-
194
- def create_dir(state_dir)
195
- FileUtils.mkdir_p(state_dir)
196
- raise unless state_dir.exist? && state_dir.directory? &&
197
- state_dir.writable?
198
- rescue StandardError
199
- raise(WavefrontCli::Exception::SystemError,
200
- "Cannot create writable system directory at '#{state_dir}'.")
201
- end
202
-
203
- def validate_input
204
- validate_id if options[:'<id>'] && !options[:close]
205
- validate_tags if options[:'<tag>']
206
- validate_tags(:evtag) if options[:evtag]
207
- send(:extra_validation) if respond_to?(:extra_validation)
208
- end
209
-
210
- # Get the last event this script created. If you supply a name, you
211
- # get the last event with that name. If not, you get the last event.
212
- # Chances are you'll only ever have one in-play at once.
213
- #
214
- # @param name [String] name of event
215
- # @eturn an array of [timestamp, event_name]
216
- #
217
- def pop_event(name = nil)
218
- return false unless state_dir.exist?
219
-
220
- list = local_events_with_name(name)
221
- return false if list.empty?
222
-
223
- ev_file = list.max
224
- File.unlink(ev_file)
225
- ev_file.basename.to_s
226
- end
227
-
228
- def local_events_with_name(name = nil)
229
- list = local_event_list
230
- return list unless name
231
-
232
- list.select { |f| f.basename.to_s.split(':').last == name }
233
- end
234
-
235
- def list_args
236
- [window_start,
237
- window_end,
238
- options[:limit] || 100,
239
- options[:cursor] || nil]
240
- end
241
-
242
- def window_start
243
- parse_time((options[:start] || Time.now - 600), true)
244
- end
245
-
246
- def window_end
247
- parse_time((options[:end] || Time.now), true)
248
- end
249
132
  end
250
133
  end
@@ -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
@@ -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.4.1'
3
+ WF_CLI_VERSION = '8.5.0'
@@ -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
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'pathname'
5
+ require 'spy'
6
+ require 'minitest/autorun'
7
+ require_relative '../../lib/wavefront-cli/event_store'
8
+
9
+ TEST_EVENT_STORE_DIR = Pathname.new(Dir.mktmpdir)
10
+
11
+ # Tests for event store class. This is tested well via the interface of the
12
+ # events CLI class.
13
+ #
14
+ class Test < MiniTest::Test
15
+ attr_reader :wf
16
+
17
+ include WavefrontCli::Constants
18
+
19
+ def before_setup
20
+ FileUtils.mkdir_p(TEST_EVENT_STORE_DIR)
21
+ end
22
+
23
+ def setup
24
+ @wf = WavefrontCli::EventStore.new({}, TEST_EVENT_STORE_DIR)
25
+ end
26
+
27
+ def teardown
28
+ FileUtils.rm_r(TEST_EVENT_STORE_DIR)
29
+ end
30
+
31
+ def test_state_file_needed?
32
+ wf1 = WavefrontCli::EventStore.new({}, TEST_EVENT_STORE_DIR)
33
+ assert wf1.state_file_needed?
34
+
35
+ wf2 = WavefrontCli::EventStore.new({ nostate: true }, TEST_EVENT_STORE_DIR)
36
+ refute wf2.state_file_needed?
37
+
38
+ wf3 = WavefrontCli::EventStore.new({ instant: true }, TEST_EVENT_STORE_DIR)
39
+ refute wf3.state_file_needed?
40
+
41
+ wf4 = WavefrontCli::EventStore.new({ start: Time.now - 20, end: Time.now },
42
+ TEST_EVENT_STORE_DIR)
43
+ refute wf4.state_file_needed?
44
+ end
45
+
46
+ def test_event_file
47
+ x = wf.event_file(id)
48
+ assert_instance_of(Pathname, x)
49
+ assert_equal(wf.dir, x.dirname)
50
+ assert_equal(id, x.basename.to_s)
51
+
52
+ assert_nil(wf.event_file('not_a_valid_id'))
53
+ end
54
+
55
+ def test_create_dir_ok
56
+ dir = TEST_EVENT_STORE_DIR + 'testdir'
57
+ refute dir.exist?
58
+ wf.create_dir(dir)
59
+ assert dir.exist?
60
+ dir.unlink
61
+ end
62
+
63
+ def test_list
64
+ setup_test_state_dir
65
+
66
+ x = wf.list
67
+ assert(x.all? { |e| e.is_a?(Pathname) })
68
+ assert_equal(4, x.size)
69
+
70
+ empty_test_state_dir
71
+ end
72
+
73
+ def test_list_empty_stack
74
+ out, err = capture_io { assert_raises(SystemExit) { wf.list } }
75
+ assert_empty(out)
76
+ assert_equal("No locally recorded events.\n", err)
77
+ end
78
+
79
+ def test_pop_event
80
+ setup_test_state_dir
81
+
82
+ assert (wf.dir + '1568133440530:ev3:0').exist?
83
+ assert_equal('1568133440530:ev3:0', wf.pop_event!)
84
+ refute (wf.dir + '1568133440530:ev3:0').exist?
85
+
86
+ empty_test_state_dir
87
+ end
88
+
89
+ def test_pop_event_named
90
+ setup_test_state_dir
91
+
92
+ assert (wf.dir + '1568133440515:ev1:1').exist?
93
+ assert_equal('1568133440515:ev1:1', wf.pop_event!('ev1'))
94
+ refute (wf.dir + '1568133440515:ev1:1').exist?
95
+
96
+ empty_test_state_dir
97
+ end
98
+
99
+ def test_event_specific
100
+ setup_test_state_dir
101
+
102
+ assert (wf.dir + '1568133440515:ev1:1').exist?
103
+ assert_equal('1568133440515:ev1:1', wf.event('1568133440515:ev1:1'))
104
+ assert (wf.dir + '1568133440515:ev1:1').exist?
105
+
106
+ empty_test_state_dir
107
+ end
108
+
109
+ def test_pop_event_empty_stack
110
+ out, err = capture_io { assert_raises(SystemExit) { wf.pop_event! } }
111
+ assert_empty(out)
112
+ assert_equal("No locally recorded events.\n", err)
113
+ end
114
+
115
+ def test_event_state_dir
116
+ ENV['WF_EVENT_STATE_DIR'] = nil
117
+ assert_equal(EVENT_STATE_DIR, wf.event_state_dir)
118
+
119
+ ENV['WF_EVENT_STATE_DIR'] = '/tmp/tester'
120
+ assert_equal(Pathname.new('/tmp/tester'), wf.event_state_dir)
121
+ ENV['WF_EVENT_STATE_DIR'] = nil
122
+
123
+ assert_equal(Pathname.new('/tmp/mydir'), wf.event_state_dir('/tmp/mydir'))
124
+ end
125
+
126
+ def test_create_dir_fail
127
+ spy = Spy.on(FileUtils, :mkdir_p).and_return(false)
128
+
129
+ assert_raises(WavefrontCli::Exception::SystemError) do
130
+ wf.create_dir(Pathname.new('/any/old/directory'))
131
+ end
132
+
133
+ assert spy.has_been_called?
134
+ spy.unhook
135
+ end
136
+
137
+ def test_event_file_data
138
+ wf = WavefrontCli::EventStore.new({ desc: 'test event' },
139
+ TEST_EVENT_STORE_DIR)
140
+ x = wf.event_file_data
141
+ assert_instance_of(String, x)
142
+ y = JSON.parse(x, symbolize_names: true)
143
+ assert_equal('test event', y[:description])
144
+ assert_equal(%i[hosts description severity tags], y.keys)
145
+ assert_nil(y[:tags])
146
+ end
147
+
148
+ def test_create
149
+ refute (wf.dir + id).exist?
150
+ out, err = capture_io { wf.create!(id) }
151
+ assert_match(/Event state recorded at .*1481553823153:testev:0./, out)
152
+ assert_empty(err)
153
+ assert (wf.dir + id).exist?
154
+ end
155
+
156
+ def test_create_with_nostate
157
+ wf1 = WavefrontCli::EventStore.new(nostate: true)
158
+ assert_nil wf1.create!(id)
159
+ end
160
+
161
+ private
162
+
163
+ def id
164
+ '1481553823153:testev:0'
165
+ end
166
+
167
+ def dummy_event_files
168
+ %w[1568133440510:ev1:0
169
+ 1568133440515:ev1:1
170
+ 1568133440520:ev2:0
171
+ 1568133440530:ev3:0]
172
+ end
173
+
174
+ def setup_test_state_dir
175
+ dummy_event_files.each do |f|
176
+ File.open(wf.dir + f, 'w') { |fh| fh.puts('dummy_data') }
177
+ end
178
+ end
179
+
180
+ def empty_test_state_dir
181
+ dummy_event_files.each do |f|
182
+ file = wf.dir + f
183
+ FileUtils.rm(file) if file.exist?
184
+ end
185
+ end
186
+ end
@@ -41,20 +41,36 @@ class ServiceAccountEndToEndTest < EndToEndTest
41
41
  assert_abort_on_missing_creds("groups #{id}")
42
42
  end
43
43
 
44
- def test_permissions
44
+ def test_roles
45
45
  quietly do
46
- assert_cmd_gets("permissions #{id}", "/api/v2/#{api_path}/#{id}")
46
+ assert_cmd_gets("roles #{id}", "/api/v2/#{api_path}/#{id}")
47
47
  end
48
48
 
49
- assert_invalid_id("permissions #{invalid_id}")
50
- assert_usage('permissions')
49
+ assert_invalid_id("roles #{invalid_id}")
50
+ assert_usage('roles')
51
51
 
52
52
  assert_noop(
53
- "permissions #{id}",
53
+ "roles #{id}",
54
54
  "uri: GET https://default.wavefront.com/api/v2/#{api_path}/#{id}"
55
55
  )
56
56
 
57
- assert_abort_on_missing_creds("permissions #{id}")
57
+ assert_abort_on_missing_creds("roles #{id}")
58
+ end
59
+
60
+ def test_ingestionpolicy
61
+ quietly do
62
+ assert_cmd_gets("ingestionpolicy #{id}", "/api/v2/#{api_path}/#{id}")
63
+ end
64
+
65
+ assert_invalid_id("ingestionpolicy #{invalid_id}")
66
+ assert_usage('ingestionpolicy')
67
+
68
+ assert_noop(
69
+ "ingestionpolicy #{id}",
70
+ "uri: GET https://default.wavefront.com/api/v2/#{api_path}/#{id}"
71
+ )
72
+
73
+ assert_abort_on_missing_creds("ingestionpolicy #{id}")
58
74
  end
59
75
 
60
76
  def test_activate
@@ -85,7 +101,7 @@ class ServiceAccountEndToEndTest < EndToEndTest
85
101
  '/api/v2/account/serviceaccount',
86
102
  identifier: id,
87
103
  active: true,
88
- groups: [],
104
+ roles: [],
89
105
  tokens: [],
90
106
  userGroups: [])
91
107
  end
@@ -95,8 +111,8 @@ class ServiceAccountEndToEndTest < EndToEndTest
95
111
  'uri: POST https://default.wavefront.com/api/v2/account/serviceaccount',
96
112
  'body: ' + { identifier: id,
97
113
  active: true,
98
- groups: [],
99
114
  tokens: [],
115
+ roles: [],
100
116
  userGroups: [] }.to_json
101
117
  )
102
118
 
@@ -111,43 +127,56 @@ class ServiceAccountEndToEndTest < EndToEndTest
111
127
  identifier: id,
112
128
  description: 'words',
113
129
  active: false,
114
- groups: [],
130
+ roles: [],
115
131
  tokens: [],
116
132
  userGroups: [])
117
133
  end
118
134
  end
119
135
 
120
- def test_create_account_in_usergroups
136
+ def test_create_account_with_usergroups
121
137
  quietly do
122
138
  assert_cmd_posts("create -g #{usergroups[0]} -g #{usergroups[1]} #{id}",
123
139
  '/api/v2/account/serviceaccount',
124
140
  identifier: id,
125
141
  active: true,
126
- groups: [],
127
142
  tokens: [],
143
+ roles: [],
128
144
  userGroups: usergroups)
129
145
  end
130
146
  end
131
147
 
148
+ def test_create_account_with_roles
149
+ quietly do
150
+ assert_cmd_posts("create -r #{roles[0]} -r #{roles[1]} #{id}",
151
+ '/api/v2/account/serviceaccount',
152
+ identifier: id,
153
+ active: true,
154
+ tokens: [],
155
+ roles: roles,
156
+ userGroups: [])
157
+ end
158
+ end
159
+
132
160
  def test_create_account_with_tokens
133
161
  quietly do
134
162
  assert_cmd_posts("create -k #{tokens[0]} -k #{tokens[1]} #{id}",
135
163
  '/api/v2/account/serviceaccount',
136
164
  identifier: id,
137
165
  active: true,
138
- groups: [],
139
166
  tokens: tokens,
167
+ roles: [],
140
168
  userGroups: [])
141
169
  end
142
170
  end
143
171
 
144
- def test_create_account_with_permissions
172
+ def test_create_account_with_ingestion_policy
145
173
  quietly do
146
- assert_cmd_posts("create -p #{permissions[0]} -p #{permissions[1]} #{id}",
174
+ assert_cmd_posts("create -p #{ingestion_policy} #{id}",
147
175
  '/api/v2/account/serviceaccount',
148
176
  identifier: id,
149
177
  active: true,
150
- groups: permissions,
178
+ ingestionPolicyId: ingestion_policy,
179
+ roles: [],
151
180
  tokens: [],
152
181
  userGroups: [])
153
182
  end
@@ -158,8 +187,8 @@ class ServiceAccountEndToEndTest < EndToEndTest
158
187
  "create -g abcdefg #{id}")
159
188
  end
160
189
 
161
- def test_create_invalid_permission
162
- assert_exits_with("'123456' is not a valid Wavefront permission.",
190
+ def test_create_invalid_ingestion_policy
191
+ assert_exits_with("'123456' is not a valid ingestion policy ID.",
163
192
  "create -p 123456 #{id}")
164
193
  end
165
194
 
@@ -358,8 +387,9 @@ class ServiceAccountEndToEndTest < EndToEndTest
358
387
  'service account'
359
388
  end
360
389
 
361
- def permissions
362
- %w[alerts_management events_management]
390
+ def roles
391
+ %w[07fc5cdd-0979-489e-8f70-325f39d15e55
392
+ 0a42adf6-e738-4c5d-9e53-fd10bd979a31]
363
393
  end
364
394
 
365
395
  def tokens
@@ -372,6 +402,10 @@ class ServiceAccountEndToEndTest < EndToEndTest
372
402
  abcdef12-1234-abcd-1234-abcdef012345]
373
403
  end
374
404
 
405
+ def ingestion_policy
406
+ 'test-policy-1607616352537'
407
+ end
408
+
375
409
  def import_fields
376
410
  %i[identifier description active tokens groups userGroups]
377
411
  end
@@ -26,7 +26,7 @@ Gem::Specification.new do |gem|
26
26
 
27
27
  gem.add_runtime_dependency 'docopt', '~> 0.6.0'
28
28
  gem.add_runtime_dependency 'inifile', '~> 3.0'
29
- gem.add_runtime_dependency 'wavefront-sdk', '~> 5.4', '>= 5.4.1'
29
+ gem.add_runtime_dependency 'wavefront-sdk', '~> 5.4', '>= 5.4.2'
30
30
 
31
31
  gem.add_development_dependency 'minitest', '~> 5.14'
32
32
  gem.add_development_dependency 'rake', '~> 13.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wavefront-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.4.1
4
+ version: 8.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Fisher
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-04 00:00:00.000000000 Z
11
+ date: 2021-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docopt
@@ -47,7 +47,7 @@ dependencies:
47
47
  version: '5.4'
48
48
  - - ">="
49
49
  - !ruby/object:Gem::Version
50
- version: 5.4.1
50
+ version: 5.4.2
51
51
  type: :runtime
52
52
  prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
@@ -57,7 +57,7 @@ dependencies:
57
57
  version: '5.4'
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 5.4.1
60
+ version: 5.4.2
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: minitest
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -232,6 +232,7 @@ files:
232
232
  - lib/wavefront-cli/display/webhook.rb
233
233
  - lib/wavefront-cli/display/write.rb
234
234
  - lib/wavefront-cli/event.rb
235
+ - lib/wavefront-cli/event_store.rb
235
236
  - lib/wavefront-cli/exception.rb
236
237
  - lib/wavefront-cli/exception_handler.rb
237
238
  - lib/wavefront-cli/externallink.rb
@@ -322,6 +323,7 @@ files:
322
323
  - spec/wavefront-cli/display/printer/long_spec.rb
323
324
  - spec/wavefront-cli/display/printer/terse_spec.rb
324
325
  - spec/wavefront-cli/event_spec.rb
326
+ - spec/wavefront-cli/event_store_spec.rb
325
327
  - spec/wavefront-cli/externallink_spec.rb
326
328
  - spec/wavefront-cli/integration_spec.rb
327
329
  - spec/wavefront-cli/maintenancewindow_spec.rb
@@ -461,6 +463,7 @@ test_files:
461
463
  - spec/wavefront-cli/display/printer/long_spec.rb
462
464
  - spec/wavefront-cli/display/printer/terse_spec.rb
463
465
  - spec/wavefront-cli/event_spec.rb
466
+ - spec/wavefront-cli/event_store_spec.rb
464
467
  - spec/wavefront-cli/externallink_spec.rb
465
468
  - spec/wavefront-cli/integration_spec.rb
466
469
  - spec/wavefront-cli/maintenancewindow_spec.rb