wavefront-client 3.5.3 → 3.5.4

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