wavefront-client 3.5.3 → 3.5.4

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -2
  3. data/README-cli.md +84 -60
  4. data/bin/wavefront +84 -69
  5. data/lib/wavefront/alerting.rb +14 -3
  6. data/lib/wavefront/batch_writer.rb +7 -0
  7. data/lib/wavefront/cli.rb +15 -17
  8. data/lib/wavefront/cli/alerts.rb +15 -10
  9. data/lib/wavefront/cli/batch_write.rb +12 -3
  10. data/lib/wavefront/cli/events.rb +19 -3
  11. data/lib/wavefront/cli/sources.rb +22 -12
  12. data/lib/wavefront/cli/ts.rb +9 -1
  13. data/lib/wavefront/cli/write.rb +7 -0
  14. data/lib/wavefront/client.rb +9 -5
  15. data/lib/wavefront/client/version.rb +1 -1
  16. data/lib/wavefront/constants.rb +20 -0
  17. data/lib/wavefront/events.rb +26 -4
  18. data/lib/wavefront/metadata.rb +23 -6
  19. data/lib/wavefront/opt_handler.rb +61 -0
  20. data/spec/cli_spec.rb +584 -0
  21. data/spec/spec_helper.rb +42 -0
  22. data/spec/wavefront/alerting_spec.rb +8 -9
  23. data/spec/wavefront/batch_writer_spec.rb +1 -1
  24. data/spec/wavefront/cli/alerts_spec.rb +5 -4
  25. data/spec/wavefront/cli/batch_write_spec.rb +4 -2
  26. data/spec/wavefront/cli/events_spec.rb +3 -2
  27. data/spec/wavefront/cli/sources_spec.rb +3 -2
  28. data/spec/wavefront/cli/write_spec.rb +4 -2
  29. data/spec/wavefront/cli_spec.rb +11 -43
  30. data/spec/wavefront/client_spec.rb +2 -2
  31. data/spec/wavefront/events_spec.rb +1 -1
  32. data/spec/wavefront/metadata_spec.rb +1 -1
  33. data/spec/wavefront/mixins_spec.rb +1 -1
  34. data/spec/wavefront/opt_handler_spec.rb +89 -0
  35. data/spec/wavefront/resources/conf.yaml +10 -0
  36. data/spec/wavefront/response_spec.rb +3 -3
  37. data/spec/wavefront/validators_spec.rb +1 -1
  38. data/spec/wavefront/writer_spec.rb +1 -1
  39. metadata +9 -3
  40. data/.ruby-version +0 -1
@@ -16,6 +16,6 @@ See the License for the specific language governing permissions and
16
16
 
17
17
  module Wavefront
18
18
  class Client
19
- VERSION = "3.5.3"
19
+ VERSION = "3.5.4"
20
20
  end
21
21
  end
@@ -15,6 +15,7 @@ See the License for the specific language governing permissions and
15
15
  =end
16
16
 
17
17
  require 'pathname'
18
+ require 'socket'
18
19
 
19
20
  module Wavefront
20
21
  module Constants
@@ -26,12 +27,31 @@ module Wavefront
26
27
  DEFAULT_OBSOLETE_METRICS = false
27
28
  FORMATS = [ :raw, :ruby, :graphite, :highcharts, :human ]
28
29
  ALERT_FORMATS = [:ruby, :json, :human]
30
+ SOURCE_FORMATS = [:ruby, :json, :human]
29
31
  DEFAULT_ALERT_FORMAT = :human
32
+ DEFAULT_SOURCE_FORMAT = :human
30
33
  GRANULARITIES = %w( s m h d )
31
34
  EVENT_STATE_DIR = Pathname.new('/var/tmp/wavefront/events')
32
35
  EVENT_LEVELS = %w(info smoke warn severe)
33
36
  DEFAULT_PROXY = 'wavefront'
34
37
  DEFAULT_PROXY_PORT = 2878
35
38
  DEFAULT_INFILE_FORMAT = 'tmv'
39
+
40
+ # The CLI will use these options if they are not supplied on the
41
+ # command line or in a config file
42
+ #
43
+ DEFAULT_OPTS = {
44
+ endpoint: DEFAULT_HOST, # API endpoint
45
+ proxy: 'wavefront', # proxy endpoint
46
+ port: DEFAULT_PROXY_PORT, # proxy port
47
+ profile: 'default', # stanza in config file
48
+ host: Socket.gethostname, # source host
49
+ prefixlength: DEFAULT_PREFIX_LENGTH, # no of prefix path elements
50
+ strict: DEFAULT_STRICT, # points outside query window
51
+ format: DEFAULT_FORMAT, # ts output format
52
+ alertformat: DEFAULT_ALERT_FORMAT, # alert command output format
53
+ infileformat: DEFAULT_INFILE_FORMAT, # batch writer file format
54
+ sourceformat: DEFAULT_SOURCE_FORMAT, # source more output format
55
+ }.freeze
36
56
  end
37
57
  end
@@ -23,10 +23,17 @@ module Wavefront
23
23
  include Wavefront::Mixins
24
24
  DEFAULT_PATH = '/api/events/'
25
25
 
26
- attr_reader :headers
26
+ attr_reader :headers, :noop, :verbose, :endpoint
27
27
 
28
- def initialize(token)
28
+ def initialize(token, host = DEFAULT_HOST, debug = false, options = {})
29
+ #
30
+ # Following existing convention, 'host' is the Wavefront API endpoint.
31
+ #
29
32
  @headers = { :'X-AUTH-TOKEN' => token }
33
+ @endpoint = host
34
+ debug(debug)
35
+ @noop = options[:noop]
36
+ @verbose = options[:verbose]
30
37
  end
31
38
 
32
39
  def create(payload = {}, options = {})
@@ -45,6 +52,13 @@ module Wavefront
45
52
  uri = create_uri(path: [DEFAULT_PATH, payload[:startTime],
46
53
  payload[:name]].join('/').squeeze('/'))
47
54
 
55
+ if (verbose || noop)
56
+ puts "DELETE #{uri.to_s}"
57
+ puts "HEADERS #{headers}"
58
+ end
59
+
60
+ return if noop
61
+
48
62
  RestClient.delete(uri.to_s, headers)
49
63
  end
50
64
 
@@ -52,7 +66,7 @@ module Wavefront
52
66
  #
53
67
  # Build the URI we use to send a 'create' request.
54
68
  #
55
- options[:host] ||= DEFAULT_HOST
69
+ options[:host] ||= endpoint
56
70
  options[:path] ||= DEFAULT_PATH
57
71
 
58
72
  URI::HTTPS.build(
@@ -84,7 +98,7 @@ module Wavefront
84
98
  #
85
99
  # Build the URI we use to send a 'close' request
86
100
  #
87
- options[:host] ||= DEFAULT_HOST
101
+ options[:host] ||= endpoint
88
102
  options[:path] ||= DEFAULT_PATH
89
103
 
90
104
  URI::HTTPS.build(
@@ -94,6 +108,14 @@ module Wavefront
94
108
  end
95
109
 
96
110
  def make_call(uri, query)
111
+ if (verbose || noop)
112
+ puts "PUT #{uri.to_s}"
113
+ puts "QUERY #{query}"
114
+ puts "HEADERS #{headers}"
115
+ end
116
+
117
+ return if noop
118
+
97
119
  RestClient.post(uri.to_s, query, headers)
98
120
  end
99
121
 
@@ -40,7 +40,7 @@ module Wavefront
40
40
  include Wavefront::Validators
41
41
  DEFAULT_PATH = '/api/manage/source/'.freeze
42
42
 
43
- attr_reader :headers, :host, :verbose, :endpoint
43
+ attr_reader :headers, :host, :verbose, :endpoint, :noop
44
44
 
45
45
  def initialize(token, host=DEFAULT_HOST, debug=false, options = {})
46
46
  #
@@ -50,6 +50,7 @@ module Wavefront
50
50
  @base_uri = URI::HTTPS.build(:host => host, :path => DEFAULT_PATH)
51
51
  @endpoint = host
52
52
  @verbose = options[:verbose] || false
53
+ @noop = options[:noop] || false
53
54
  debug(debug)
54
55
  end
55
56
 
@@ -158,7 +159,8 @@ module Wavefront
158
159
  end
159
160
  end
160
161
 
161
- JSON.parse(call_get(build_uri(nil, query: hash_to_qs(params))))
162
+ resp = call_get(build_uri(nil, query: hash_to_qs(params))) || '{}'
163
+ JSON.parse(resp)
162
164
  end
163
165
 
164
166
  def show_source(source)
@@ -169,7 +171,9 @@ module Wavefront
169
171
  # See the Wavefront API docs for the structure of the object.
170
172
  #
171
173
  fail Wavefront::Exception::InvalidSource unless valid_source?(source)
172
- JSON.parse(call_get(build_uri(source)))
174
+ resp = call_get(build_uri(source)) || '{}'
175
+
176
+ JSON.parse(resp)
173
177
  end
174
178
 
175
179
  def set_description(source, desc)
@@ -206,18 +210,31 @@ module Wavefront
206
210
  end
207
211
 
208
212
  def call_get(uri)
209
- puts "GET #{uri.to_s}" if verbose
213
+ if (verbose || noop)
214
+ puts 'GET ' + uri.to_s
215
+ puts 'HEADERS ' + headers.to_s
216
+ end
217
+ return if noop
210
218
  RestClient.get(uri.to_s, headers)
211
219
  end
212
220
 
213
221
  def call_delete(uri)
214
- puts "DELETE #{uri.to_s}" if verbose
222
+ if (verbose || noop)
223
+ puts 'DELETE ' + uri.to_s
224
+ puts 'HEADERS ' + headers.to_s
225
+ end
226
+ return if noop
215
227
  RestClient.delete(uri.to_s, headers)
216
228
  end
217
229
 
218
230
  def call_post(uri, query = nil)
219
- puts "POST #{uri.to_s} #{query}" if verbose
220
231
  h = headers
232
+ if (verbose || noop)
233
+ puts 'POST ' + uri.to_s
234
+ puts 'QUERY ' + query if query
235
+ puts 'HEADERS ' + h.to_s
236
+ end
237
+ return if noop
221
238
 
222
239
  RestClient.post(uri.to_s, query,
223
240
  h.merge(:'Content-Type' => 'text/plain',
@@ -0,0 +1,61 @@
1
+ require 'inifile'
2
+ require 'pathname'
3
+
4
+ module Wavefront
5
+ #
6
+ # Options to commands can come from three sources, with the
7
+ # following order of precedence: program defaults, a configuration
8
+ # file, and command-line options. Docopt is not well suited to
9
+ # this, as it will "fill in" any missing options with defaults,
10
+ # producing a single hash which must be merged with values from
11
+ # the config file. Assuming we give the command-line higher
12
+ # precedence, a default value, not supplied by the user, will
13
+ # override a value in the config file. The other way round, and
14
+ # you can't override anything in the config file from the
15
+ # command-line. I think this behaviour is far from unique to
16
+ # Docopt.
17
+ #
18
+ # So, we have a hash of defaults, and we do the merging ourselves,
19
+ # in this class. We trick Docopt into not using the defaults by
20
+ # avoiding the magic string 'default: ' in our options stanzas.
21
+ #
22
+ class OptHandler
23
+ include Wavefront::Constants
24
+
25
+ attr_reader :opts, :cli_opts, :conf_file
26
+
27
+ def initialize(conf_file, cli_opts = {})
28
+ @conf_file = if cli_opts.key?(:config) && cli_opts[:config]
29
+ Pathname.new(cli_opts[:config])
30
+ else
31
+ conf_file
32
+ end
33
+
34
+ @cli_opts = cli_opts.reject { |_k, v| v.nil? }
35
+
36
+ @opts = DEFAULT_OPTS.merge(load_profile).merge(@cli_opts)
37
+ end
38
+
39
+ def load_profile
40
+ #
41
+ # Load in configuration options from the (optionally) given
42
+ # section of an ini-style configuration file. If the file's
43
+ # not there, we don't consider that an error. Returns a hash
44
+ # of options which matches what Docopt gives us.
45
+ #
46
+ unless conf_file.exist?
47
+ puts "config file '#{conf_file}' not found. Taking options " \
48
+ 'from command-line.'
49
+ return {}
50
+ end
51
+
52
+ pf = cli_opts.fetch(:profile, 'default')
53
+
54
+ puts "reading '#{pf}' profile from '#{conf_file}'" if cli_opts[:debug]
55
+
56
+ IniFile.load(conf_file)[pf].each_with_object({}) do |(k, v), memo|
57
+ memo[k.to_sym] = v
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,584 @@
1
+ #
2
+ # cli_spec.rb
3
+ # -----------
4
+ #
5
+ # This spec file adds acceptance testing for the CLI script `bin/wavefront`. It
6
+ # runs all commands and subcommands with the `--noop` option, and ensures that
7
+ # the expected API calls are made, or the correct error is thrown. It takes
8
+ # ~15-25s to run, and is part of the CI pipeline.
9
+ #
10
+ # None of the tests in this file make real API calls, so no endpoint is
11
+ # required.
12
+ #
13
+ # Tests are run as part of the usual `rake spec` job. Custom RSpec matchers and
14
+ # support methods are in `spec_helper.rb`.
15
+ #
16
+ require 'pathname'
17
+ require 'open3'
18
+ require 'ostruct'
19
+ require 'json'
20
+ require 'socket'
21
+ require 'date'
22
+ require 'uri'
23
+ require_relative './spec_helper'
24
+ require 'wavefront/client/version'
25
+
26
+ ROOT = Pathname.new(__FILE__).dirname.parent
27
+ WF = ROOT + 'bin' + 'wavefront'
28
+ LIB = ROOT + 'lib'
29
+ CF = ROOT + 'spec' + 'wavefront' + 'resources' + 'conf.yaml'
30
+
31
+ # Things from the sample config file, for shorthand
32
+ #
33
+ DEF_TOKEN = '12345678-abcd-1234-abcd-123456789012'
34
+ OTHER_TOKEN = 'abcdefab-0123-abcd-0123-abcdefabcdef'
35
+
36
+ # Some standard start and end times
37
+ #
38
+ TIME = {
39
+ start: {
40
+ eng: '12:00',
41
+ i: DateTime.parse('12:00').to_time.to_i,
42
+ ms: DateTime.parse('12:00').to_time.to_i * 1000
43
+ },
44
+ end: {
45
+ eng: '12:05',
46
+ i: DateTime.parse('12:05').to_time.to_i,
47
+ ms: DateTime.parse('12:05').to_time.to_i * 1000
48
+ }
49
+ }
50
+
51
+ describe 'usage' do
52
+ commands = %w(alerts event source ts write)
53
+
54
+ it 'prints usage and exits 1 with no args' do
55
+ o = wf
56
+ expect(o.stdout).to be_empty
57
+ expect(o.stderr_a.first).to eq('Usage:')
58
+ expect(o.status).to eq(1)
59
+ end
60
+
61
+ it 'prints detailed usage with --help' do
62
+ o = wf('--help')
63
+ expect(o.stdout).to be_empty
64
+ expect(o.stderr_a.first).to eq('Wavefront CLI')
65
+ expect(o.status).to eq(1)
66
+ commands.each { |cmd| expect(o.stderr).to match(/\n #{cmd} /) }
67
+ end
68
+
69
+ commands.each do |cmd|
70
+ it "prints help for the #{cmd} command" do
71
+ o = wf("#{cmd} --help")
72
+ expect(o.status).to eq(1)
73
+ expect(o.stdout).to be_empty
74
+ expect(o.stderr_a.first).to eq('Usage:')
75
+ expect(o.stderr_a).to include('Global options:')
76
+ end
77
+ end
78
+
79
+ it 'displays the correct version number' do
80
+ include Wavefront::Client
81
+ o = wf('--version')
82
+ expect(o.status).to eq(1)
83
+ expect(o.stdout).to be_empty
84
+ expect(o.stderr).to eq(Wavefront::Client::VERSION)
85
+ end
86
+ end
87
+
88
+ describe 'alerts mode' do
89
+ it 'fails with no token if there is no token' do
90
+ o = wf('alerts -c/nf -E metrics.wavefront.com active')
91
+ expect(o.status).to eq(1)
92
+ expect(o.stderr).to eq('alerts query failed. Please supply an API token.')
93
+ expect(o.stdout).to eq(
94
+ "config file '/nf' not found. Taking options from command-line.")
95
+ end
96
+
97
+ it 'fails with a helpful message if an invalid state is given' do
98
+ o = wf('alerts -n -c/nf -t token -E metrics.wavefront.com badstate')
99
+ expect(o.status).to eq(1)
100
+ expect(o.stderr).to eq('alerts query failed. State must be one of: ' \
101
+ 'active, affected_by_maintenance, all, invalid, snoozed.')
102
+ expect(o.stdout).to eq("config file '/nf' not found. Taking options " \
103
+ 'from command-line.')
104
+ end
105
+
106
+ it 'performs a verbose noop with a CLI endpoint' do
107
+ o = wf('alerts -n -c/nf -t token -E test.wavefront.com active')
108
+ expect(o.status).to eq(0)
109
+ expect(o.stderr).to be_empty
110
+ expect(o.stdout_a[-1]).to eq(
111
+ 'GET https://test.wavefront.com/api/alert/active?t=token')
112
+ end
113
+
114
+ it 'performs a verbose noop with default config file options' do
115
+ o = wf("alerts -n -c #{CF} active")
116
+ expect(o.status).to eq(0)
117
+ expect(o.stderr).to be_empty
118
+ expect(o.stdout_a.last).to eq(
119
+ "GET https://default.wavefront.com/api/alert/active?t=#{DEF_TOKEN}")
120
+ end
121
+
122
+ it 'performs a verbose noop with config file and CLI options' do
123
+ o = wf("alerts -n -c #{CF} -E cli.wavefront.com active")
124
+ expect(o.status).to eq(0)
125
+ expect(o.stderr).to be_empty
126
+ expect(o.stdout_a.last).to eq(
127
+ "GET https://cli.wavefront.com/api/alert/active?t=#{DEF_TOKEN}")
128
+ end
129
+ end
130
+
131
+ describe 'source mode' do
132
+ cmds = %W(list show describe undescribe #{'tag add'} #{'tag delete'}
133
+ untag).each do |cmd|
134
+ it "#{cmd} fails with no token if there is no token" do
135
+ o = wf("source #{cmd} -c/nf arg1}")
136
+ expect(o.status).to eq(1)
137
+ expect(o.stderr).to eq('source query failed. Please supply an API token.')
138
+ expect(o.stdout).to eq(
139
+ "config file '/nf' not found. Taking options from command-line.")
140
+ end
141
+ end
142
+
143
+ describe 'list subcommand' do
144
+ it 'performs a verbose noop with default options' do
145
+ o = wf('source -c /nf -n -t token list ptn')
146
+ expect(o.stderr).to be_empty
147
+ expect(o.status).to eq(0)
148
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
149
+ expect(o.stdout_a[-2]).to start_with(
150
+ 'GET https://metrics.wavefront.com/api/manage/source/')
151
+ expect(o.stdout_a[-2]).to have_element([:desc, false])
152
+ expect(o.stdout_a[-2]).to have_element([:limit, 100])
153
+ expect(o.stdout_a[-2]).to have_element([:pattern, 'ptn'])
154
+ expect(o.stdout).to match(/Taking options from command-line/)
155
+ end
156
+
157
+ it 'performs a verbose noop with default config file options' do
158
+ o = wf("source -c #{CF} -n -s lasthost -l 55 list ptn")
159
+ expect(o.stderr).to be_empty
160
+ expect(o.status).to eq(0)
161
+ expect(o.stdout_a[-1]).to eq(
162
+ "HEADERS {\"X-AUTH-TOKEN\"=>\"#{DEF_TOKEN}\"}")
163
+ expect(o.stdout_a[-2]).to start_with(
164
+ 'GET https://default.wavefront.com/api/manage/source/')
165
+ expect(o.stdout_a[-2]).to have_element([:desc, false])
166
+ expect(o.stdout_a[-2]).to have_element([:limit, 55])
167
+ expect(o.stdout_a[-2]).to have_element([:lastEntityId, 'lasthost'])
168
+ expect(o.stdout_a[-2]).to have_element([:pattern, 'ptn'])
169
+ expect(o.stdout).to_not match(/Taking options from command-line/)
170
+ end
171
+
172
+ it 'performs a verbose noop with config file and CLI options' do
173
+ o = wf("source -c #{CF} -P other -n -s lasthost -t token list ptn")
174
+ expect(o.stderr).to be_empty
175
+ expect(o.status).to eq(0)
176
+ expect(o.stdout_a[-1]).to eq(
177
+ 'HEADERS {"X-AUTH-TOKEN"=>"token"}')
178
+ expect(o.stdout_a[-2]).to start_with(
179
+ 'GET https://other.wavefront.com/api/manage/source/')
180
+ expect(o.stdout_a[-2]).to have_element([:desc, false])
181
+ expect(o.stdout_a[-2]).to have_element([:limit, 100])
182
+ expect(o.stdout_a[-2]).to have_element([:lastEntityId, 'lasthost'])
183
+ expect(o.stdout_a[-2]).to have_element([:pattern, 'ptn'])
184
+ expect(o.stdout).to_not match(/Taking options from command-line/)
185
+ end
186
+ end
187
+
188
+ describe 'show subcommand' do
189
+ it 'performs a verbose noop with default options' do
190
+ o = wf('source -c /nf -n -t token show testhost')
191
+ expect(o.stderr).to be_empty
192
+ expect(o.status).to eq(0)
193
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
194
+ expect(o.stdout_a[-2]).to eq(
195
+ 'GET https://metrics.wavefront.com/api/manage/source/testhost')
196
+ expect(o.stdout).to match(/Taking options from command-line/)
197
+ end
198
+
199
+ it 'performs a verbose noop with CLI options' do
200
+ o = wf('source -c /nf -n -t token -E cli.wavefront.com show testhost')
201
+ expect(o.stderr).to be_empty
202
+ expect(o.status).to eq(0)
203
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
204
+ expect(o.stdout_a[-2]).to eq(
205
+ 'GET https://cli.wavefront.com/api/manage/source/testhost')
206
+ expect(o.stdout).to match(/Taking options from command-line/)
207
+ end
208
+
209
+ it 'performs a verbose noop with CLI and config file options' do
210
+ o = wf("source -c #{CF} -n show testhost")
211
+ expect(o.stderr).to be_empty
212
+ expect(o.status).to eq(0)
213
+ expect(o.stdout_a[-1]).to eq(
214
+ "HEADERS {\"X-AUTH-TOKEN\"=>\"#{DEF_TOKEN}\"}")
215
+ expect(o.stdout_a[-2]).to eq(
216
+ 'GET https://default.wavefront.com/api/manage/source/testhost')
217
+ end
218
+
219
+ it 'performs a verbose noop with CLI and non-default config file options' do
220
+ o = wf("source -c #{CF} -t token -P other -n show testhost")
221
+ expect(o.stderr).to be_empty
222
+ expect(o.status).to eq(0)
223
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
224
+ expect(o.stdout_a[-2]).to eq(
225
+ 'GET https://other.wavefront.com/api/manage/source/testhost')
226
+ end
227
+ end
228
+
229
+ describe 'describe subcommand' do
230
+ it 'performs a verbose noop with default options' do
231
+ o = wf('source -c /nf -n -t token describe "test desc"')
232
+ expect(o.stderr).to be_empty
233
+ expect(o.status).to eq(0)
234
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
235
+ expect(o.stdout_a[-3]).to eq(
236
+ 'POST https://metrics.wavefront.com/api/manage/source/'\
237
+ "#{Socket.gethostname}/description")
238
+ expect(o.stdout_a[-2]).to eq('QUERY test desc')
239
+ expect(o.stdout).to match(/Taking options from command-line/)
240
+ end
241
+
242
+ it 'performs a verbose noop with CLI and config options' do
243
+ o = wf("source -c #{CF} -H i-123456 -n -t token describe 'test desc'")
244
+ expect(o.stderr).to be_empty
245
+ expect(o.status).to eq(0)
246
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
247
+ expect(o.stdout_a[-3]).to eq(
248
+ 'POST https://default.wavefront.com/api/manage/source/'\
249
+ 'i-123456/description')
250
+ expect(o.stdout_a[-2]).to eq('QUERY test desc')
251
+ expect(o.stdout).to_not match(/Taking options from command-line/)
252
+ end
253
+
254
+ it 'performs a verbose noop with CLI and non-default config options' do
255
+ o = wf("source -c #{CF} -P other -E cli.wavefront.com -H i-123456 " \
256
+ "-n describe 'test desc'")
257
+ expect(o.stderr).to be_empty
258
+ expect(o.status).to eq(0)
259
+ expect(o.stdout_a[-1]).to eq(
260
+ "HEADERS {\"X-AUTH-TOKEN\"=>\"#{OTHER_TOKEN}\"}")
261
+ expect(o.stdout_a[-3]).to eq(
262
+ 'POST https://cli.wavefront.com/api/manage/source/'\
263
+ 'i-123456/description')
264
+ expect(o.stdout_a[-2]).to eq('QUERY test desc')
265
+ expect(o.stdout).to_not match(/Taking options from command-line/)
266
+ end
267
+ end
268
+
269
+ describe 'undescribe subcommand' do
270
+ it 'performs a verbose noop with default options' do
271
+ o = wf('source -c /nf -n -t token undescribe thost')
272
+ expect(o.stderr).to be_empty
273
+ expect(o.status).to eq(0)
274
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
275
+ expect(o.stdout_a[-3]).to eq(
276
+ 'POST https://metrics.wavefront.com/api/manage/source/'\
277
+ 'thost/description')
278
+ expect(o.stdout_a[-2]).to eq('QUERY ')
279
+ expect(o.stdout).to match(/Taking options from command-line/)
280
+ end
281
+
282
+ it 'performs a verbose noop with CLI and config options' do
283
+ o = wf("source -c #{CF} -n -t token undescribe thost")
284
+ expect(o.stderr).to be_empty
285
+ expect(o.status).to eq(0)
286
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
287
+ expect(o.stdout_a[-3]).to eq(
288
+ 'POST https://default.wavefront.com/api/manage/source/'\
289
+ 'thost/description')
290
+ expect(o.stdout_a[-2]).to eq('QUERY ')
291
+ expect(o.stdout).to_not match(/Taking options from command-line/)
292
+ end
293
+
294
+ it 'performs a verbose noop with CLI and non-default config options' do
295
+ o = wf("source -n -c #{CF} -P other -E cli.wavefront.com undescribe thost")
296
+ expect(o.stderr).to be_empty
297
+ expect(o.status).to eq(0)
298
+ expect(o.stdout_a[-1]).to eq(
299
+ "HEADERS {\"X-AUTH-TOKEN\"=>\"#{OTHER_TOKEN}\"}")
300
+ expect(o.stdout_a[-3]).to eq(
301
+ 'POST https://cli.wavefront.com/api/manage/source/thost/description')
302
+ expect(o.stdout_a[-2]).to eq('QUERY ')
303
+ expect(o.stdout).to_not match(/Taking options from command-line/)
304
+ end
305
+ end
306
+
307
+ describe 'tag subcommand' do
308
+ describe 'tag add' do
309
+ it 'performs a verbose noop with default options' do
310
+ o = wf('source -c /nf -n -t token tag add tag1 tag2')
311
+ expect(o.stderr).to be_empty
312
+ expect(o.status).to eq(0)
313
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
314
+ expect(o.stdout_a[-2]).to eq(
315
+ 'POST https://metrics.wavefront.com/api/manage/source/'\
316
+ "#{Socket.gethostname}/tags/tag2")
317
+ expect(o.stdout_a[-3]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
318
+ expect(o.stdout_a[-4]).to eq(
319
+ 'POST https://metrics.wavefront.com/api/manage/source/'\
320
+ "#{Socket.gethostname}/tags/tag1")
321
+ expect(o.stdout).to match(/Taking options from command-line/)
322
+ end
323
+
324
+ it 'performs a verbose noop with conf file and CLI options' do
325
+ o = wf("source -c #{CF} -P other -H i-123456 -n " \
326
+ '-E cli.wavefront.com tag add tag1')
327
+ expect(o.stderr).to be_empty
328
+ expect(o.status).to eq(0)
329
+ expect(o.stdout_a[-1]).to eq(
330
+ "HEADERS {\"X-AUTH-TOKEN\"=>\"#{OTHER_TOKEN}\"}")
331
+ expect(o.stdout_a[-2]).to eq(
332
+ 'POST https://cli.wavefront.com/api/manage/source/'\
333
+ "i-123456/tags/tag1")
334
+ end
335
+ end
336
+
337
+ describe 'tag delete' do
338
+ it 'performs a verbose noop with default options' do
339
+ o = wf('source -c /nf -n -t token tag delete tag1 tag2')
340
+ expect(o.stderr).to be_empty
341
+ expect(o.status).to eq(0)
342
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
343
+ expect(o.stdout_a[-2]).to eq(
344
+ 'DELETE https://metrics.wavefront.com/api/manage/source/'\
345
+ "#{Socket.gethostname}/tags/tag2")
346
+ expect(o.stdout_a[-3]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
347
+ expect(o.stdout_a[-4]).to eq(
348
+ 'DELETE https://metrics.wavefront.com/api/manage/source/'\
349
+ "#{Socket.gethostname}/tags/tag1")
350
+ expect(o.stdout).to match(/Taking options from command-line/)
351
+ end
352
+
353
+ it 'performs a verbose noop with conf file and CLI options' do
354
+ o = wf("source -c #{CF} -P other -H i-123456 -n " \
355
+ '-E cli.wavefront.com tag delete tag1')
356
+ expect(o.stderr).to be_empty
357
+ expect(o.status).to eq(0)
358
+ expect(o.stdout_a[-1]).to eq(
359
+ "HEADERS {\"X-AUTH-TOKEN\"=>\"#{OTHER_TOKEN}\"}")
360
+ expect(o.stdout_a[-2]).to eq(
361
+ 'DELETE https://cli.wavefront.com/api/manage/source/'\
362
+ "i-123456/tags/tag1")
363
+ end
364
+ end
365
+ end
366
+
367
+ describe 'untag subcommand' do
368
+ it 'performs a verbose noop with default options' do
369
+ o = wf('source -c /nf -n -t token untag thost')
370
+ expect(o.stderr).to be_empty
371
+ expect(o.status).to eq(0)
372
+ expect(o.stdout_a[-1]).to eq('HEADERS {"X-AUTH-TOKEN"=>"token"}')
373
+ expect(o.stdout_a[-2]).to eq(
374
+ 'DELETE https://metrics.wavefront.com/api/manage/source/thost/tags')
375
+ expect(o.stdout).to match(/Taking options from command-line/)
376
+ end
377
+
378
+ it 'performs a verbose noop with CLI and config options' do
379
+ o = wf("source -c #{CF} -n -E cli.wavefront.com untag thost1 thost2")
380
+ expect(o.stderr).to be_empty
381
+ expect(o.status).to eq(0)
382
+ expect(o.stdout_a[-3]).to eq(
383
+ "HEADERS {\"X-AUTH-TOKEN\"=>\"#{DEF_TOKEN}\"}")
384
+ expect(o.stdout_a[-4]).to eq(
385
+ 'DELETE https://cli.wavefront.com/api/manage/source/thost1/tags')
386
+ expect(o.stdout_a[-1]).to eq(
387
+ "HEADERS {\"X-AUTH-TOKEN\"=>\"#{DEF_TOKEN}\"}")
388
+ expect(o.stdout_a[-2]).to eq(
389
+ 'DELETE https://cli.wavefront.com/api/manage/source/thost2/tags')
390
+ expect(o.stdout).to_not match(/Taking options from command-line/)
391
+ end
392
+ end
393
+ end
394
+
395
+ describe 'event mode' do
396
+ cmds = %w(create close delete).each do |cmd|
397
+ it "#{cmd} fails with no token if there is no token" do
398
+ o = wf("event #{cmd} -c/nf #{cmd == 'delete' ? 'arg1 arg2' : 'arg1'}")
399
+ expect(o.status).to eq(1)
400
+ expect(o.stderr).to eq('Please supply an API token.')
401
+ expect(o.stdout).to eq(
402
+ "config file '/nf' not found. Taking options from command-line.")
403
+ end
404
+ end
405
+
406
+ describe 'create subcommand' do
407
+ it 'performs a verbose noop with default options' do
408
+ o = wf('event create -c/nf -t token -n test_ev')
409
+ expect(o.status).to eq(0)
410
+ expect(o.stderr).to be_empty
411
+ expect(o.stdout_a[-3]).to eq(
412
+ 'PUT https://metrics.wavefront.com/api/events/')
413
+ expect(o.stdout_a[-2]).to have_element([:h, Socket.gethostname])
414
+ expect(o.stdout_a[-1]).to eq('HEADERS {:"X-AUTH-TOKEN"=>"token"}')
415
+ end
416
+
417
+ it 'performs a verbose noop on a bounded event with CLI options' do
418
+ o = wf("event create -c/nf --start #{TIME[:start][:eng]} " \
419
+ "--end #{TIME[:end][:eng]} -t token -d 'some description' " \
420
+ '-l info -E test.wavefront.com -n test_ev')
421
+ expect(o.status).to eq(0)
422
+ expect(o.stderr).to be_empty
423
+ expect(o.stdout_a[-3]).to eq('PUT https://test.wavefront.com/api/events/')
424
+ expect(o.stdout_a[-2]).to have_element([:h, Socket.gethostname])
425
+ expect(o.stdout_a[-2]).to have_element([:s, TIME[:start][:ms]])
426
+ expect(o.stdout_a[-2]).to have_element([:e, TIME[:end][:ms]])
427
+ expect(o.stdout_a[-2]).to have_element([:l, 'info'])
428
+ expect(o.stdout_a[-2]).to have_element([:c, false])
429
+ expect(o.stdout_a[-2]).to have_element([:d, 'some description'])
430
+ expect(o.stdout_a[-1]).to eq('HEADERS {:"X-AUTH-TOKEN"=>"token"}')
431
+ end
432
+
433
+ it 'performs a verbose noop unbounded event with CLI and file options' do
434
+ o = wf("event create -c #{CF} -P other -s #{TIME[:start][:eng]} " \
435
+ "-t token -H i-123456,i-abcdef -d 'some description' -n test_ev")
436
+ expect(o.stderr).to be_empty
437
+ expect(o.status).to eq(0)
438
+ expect(o.stdout_a[-3]).to eq(
439
+ 'PUT https://other.wavefront.com/api/events/')
440
+ expect(o.stdout_a[-2]).to have_element([:h, 'i-123456'])
441
+ expect(o.stdout_a[-2]).to have_element([:h, 'i-abcdef'])
442
+ expect(o.stdout_a[-2]).to have_element([:s, TIME[:start][:ms]])
443
+ expect(o.stdout_a[-2]).not_to match(/&e=/)
444
+ expect(o.stdout_a[-2]).to have_element([:c, false])
445
+ expect(o.stdout_a[-2]).to have_element([:d, 'some description'])
446
+ end
447
+
448
+ it 'performs a verbose noop on an instantaneous event with CLI and ' \
449
+ 'file options' do
450
+ o = wf("event create -c #{CF} -i -H i-123456 -d 'some description' " \
451
+ '-l smoke -E test.wavefront.com -n test_ev')
452
+ expect(o.stderr).to be_empty
453
+ expect(o.status).to eq(0)
454
+ expect(o.stdout_a[-3]).to eq('PUT https://test.wavefront.com/api/events/')
455
+ expect(o.stdout_a[-2]).to have_element([:h, 'i-123456'])
456
+ expect(o.stdout_a[-2]).not_to match(/&s=/)
457
+ expect(o.stdout_a[-2]).not_to match(/&e=/)
458
+ expect(o.stdout_a[-2]).to have_element([:c, 'true'])
459
+ expect(o.stdout_a[-2]).to have_element([:l, 'smoke'])
460
+ expect(o.stdout_a[-2]).to have_element([:d, 'some description'])
461
+ expect(o.stdout_a[-1]).to eq(
462
+ "HEADERS {:\"X-AUTH-TOKEN\"=>\"#{DEF_TOKEN}\"}")
463
+ end
464
+ end
465
+
466
+ describe 'close subcommand' do
467
+ it 'refuses to close an unknown event' do
468
+ o = wf("event close -c #{CF} -t token -n unknown_event")
469
+ expect(o.stderr).to eq(
470
+ "event query failed. No event 'unknown_event' to close.")
471
+ expect(o.status).to eq(1)
472
+ expect(o.stdout).to be_empty
473
+ end
474
+
475
+ it 'performs a verbose noop with default options' do
476
+ o = wf("event close -c/nf -t token -n test_ev #{TIME[:start][:ms]}")
477
+ expect(o.stderr).to be_empty
478
+ expect(o.status).to eq(0)
479
+ expect(o.stdout_a[-3]).to eq(
480
+ 'PUT https://metrics.wavefront.com/api/events/close')
481
+ expect(o.stdout_a[-2]).to have_element([:s, TIME[:start][:ms]])
482
+ expect(o.stdout_a[-2]).to have_element([:n, 'test_ev'])
483
+ expect(o.stdout_a[-1]).to eq('HEADERS {:"X-AUTH-TOKEN"=>"token"}')
484
+ end
485
+
486
+ it 'performs a verbose noop with CLI and file options' do
487
+ o = wf("event close -P other -c #{CF} -t token -n test_ev " \
488
+ "#{TIME[:start][:ms]}")
489
+ expect(o.stderr).to be_empty
490
+ expect(o.status).to eq(0)
491
+ expect(o.stdout_a[-3]).to eq(
492
+ 'PUT https://other.wavefront.com/api/events/close')
493
+ expect(o.stdout_a[-2]).to have_element([:s, TIME[:start][:ms]])
494
+ expect(o.stdout_a[-2]).to have_element([:n, 'test_ev'])
495
+ expect(o.stdout_a[-1]).to eq('HEADERS {:"X-AUTH-TOKEN"=>"token"}')
496
+ end
497
+
498
+ it 'performs a verbose noop with CLI options' do
499
+ o = wf("event close -c/nf -E cli.wavefront.com -t token -n test_ev " +
500
+ TIME[:start][:ms].to_s)
501
+ expect(o.stderr).to be_empty
502
+ expect(o.status).to eq(0)
503
+ expect(o.stdout_a[-3]).to eq(
504
+ 'PUT https://cli.wavefront.com/api/events/close')
505
+ expect(o.stdout_a[-2]).to have_element([:s, TIME[:start][:ms]])
506
+ expect(o.stdout_a[-2]).to have_element([:n, 'test_ev'])
507
+ expect(o.stdout_a[-1]).to eq('HEADERS {:"X-AUTH-TOKEN"=>"token"}')
508
+ end
509
+ end
510
+
511
+ describe 'delete subcommand' do
512
+ it 'performs a verbose noop with default options' do
513
+ o = wf("event delete -c/nf -t token -n #{TIME[:start][:ms]} test_ev")
514
+ expect(o.stderr).to be_empty
515
+ expect(o.status).to eq(0)
516
+ expect(o.stdout_a[-2]).to eq('DELETE https://metrics.wavefront.com/' \
517
+ "api/events/#{TIME[:start][:ms]}/test_ev")
518
+ expect(o.stdout_a[-1]).to eq('HEADERS {:"X-AUTH-TOKEN"=>"token"}')
519
+ end
520
+ end
521
+ end
522
+
523
+ describe 'ts mode' do
524
+ it 'fails with no granularity and default options' do
525
+ o = wf('ts -t token -c/nf "ts(dev.cli.test)"')
526
+ expect(o.status).to eq(1)
527
+ expect(o.stdout).to match(
528
+ /^config file.*not found. Taking options from command-line./)
529
+ expect(o.stderr).to start_with(
530
+ 'ts query failed. You must specify a granularity')
531
+ end
532
+
533
+ it 'fails with no token if there is no token' do
534
+ o = wf('ts -c/nf -E metrics.wavefront.com "ts(dev.cli.test)"')
535
+ expect(o.status).to eq(1)
536
+ expect(o.stderr).to eq('ts query failed. Please supply an API token.')
537
+ expect(o.stdout).to eq(
538
+ "config file '/nf' not found. Taking options from command-line.")
539
+ end
540
+
541
+ it 'performs a verbose noop with default options' do
542
+ o = wf('ts -c/nf -t token -Sn "ts(dev.cli.test)"')
543
+ expect(o.status).to eq(0)
544
+ expect(o.stderr).to be_empty
545
+ expect(o.stdout_a[-2]).to eq('GET https://metrics.wavefront.com/chart/api')
546
+ q = raw(o.stdout_a.last)
547
+ expect(q['X-AUTH-TOKEN']).to eq('token')
548
+ expect(q[:params][:g]).to eq('s')
549
+ expect(q[:params][:q]).to eq('ts(dev.cli.test)')
550
+ end
551
+
552
+ it 'performs a verbose noop with a CLI endpoint' do
553
+ o = wf('ts -c/nf -t token -Sn -E test.wavefront.com "ts(dev.cli.test)"')
554
+ expect(o.status).to eq(0)
555
+ expect(o.stderr).to be_empty
556
+ expect(o.stdout_a[-2]).to eq('GET https://test.wavefront.com/chart/api')
557
+ q = raw(o.stdout_a.last)
558
+ expect(q['X-AUTH-TOKEN']).to eq('token')
559
+ expect(q[:params][:g]).to eq('s')
560
+ expect(q[:params][:q]).to eq('ts(dev.cli.test)')
561
+ end
562
+
563
+ it 'performs a verbose noop with default config file options' do
564
+ o = wf("ts -c #{CF} -Hn 'ts(dev.cli.test)'")
565
+ expect(o.status).to eq(0)
566
+ expect(o.stderr).to be_empty
567
+ expect(o.stdout_a[-2]).to eq('GET https://default.wavefront.com/chart/api')
568
+ q = raw(o.stdout_a.last)
569
+ expect(q['X-AUTH-TOKEN']).to eq(DEF_TOKEN)
570
+ expect(q[:params][:g]).to eq('h')
571
+ expect(q[:params][:q]).to eq('ts(dev.cli.test)')
572
+ end
573
+
574
+ it 'performs a verbose noop with config file and CLI options' do
575
+ o = wf("ts -c #{CF} -P other -mn -E cli.wavefront.com 'ts(dev.cli.test)'")
576
+ expect(o.status).to eq(0)
577
+ expect(o.stderr).to be_empty
578
+ expect(o.stdout_a[-2]).to eq('GET https://cli.wavefront.com/chart/api')
579
+ q = raw(o.stdout_a.last)
580
+ expect(q['X-AUTH-TOKEN']).to eq(OTHER_TOKEN)
581
+ expect(q[:params][:g]).to eq('m')
582
+ expect(q[:params][:q]).to eq('ts(dev.cli.test)')
583
+ end
584
+ end