wavefront-cli 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ require_relative 'base'
2
+
3
+ module WavefrontOutput
4
+ #
5
+ # Display as YAML
6
+ #
7
+ class Yaml < Base
8
+ # We don't want the YAML keys to be symbols, so we load it as
9
+ # JSON and turn *that* into YAML.
10
+ #
11
+ def run
12
+ puts JSON.parse(resp.to_json).to_yaml
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'base_write'
2
+
3
+ module WavefrontCli
4
+ class Report < BaseWrite
5
+ def send_point(p)
6
+ wf.write(p)
7
+ rescue Wavefront::Exception::InvalidEndpoint
8
+ abort 'could not speak to API'
9
+ end
10
+
11
+ def open_connection; end
12
+
13
+ def close_connection; end
14
+ end
15
+ end
@@ -34,7 +34,6 @@ class String
34
34
  #
35
35
  def fold(tw = TW, indent = 10, prefix = '')
36
36
  chunks = scan_line(tw - 8)
37
-
38
37
  first_line = format("%s%s\n", prefix, chunks.shift)
39
38
 
40
39
  return first_line if chunks.empty?
@@ -65,8 +64,20 @@ class String
65
64
  number.to_i * unit_factor(unit.to_sym)
66
65
  end
67
66
 
67
+ # How many seconds in the given unit
68
+ # @param unit [Symbol]
69
+ # @return [Integer]
70
+ #
68
71
  def unit_factor(unit)
69
72
  factors = { s: 1, m: 60, h: 3600, d: 86_400, w: 604_800 }
70
73
  factors[unit] || 1
71
74
  end
75
+
76
+ # Make a camelCase string be snake_case
77
+ # @return [String]
78
+ #
79
+ def to_snake
80
+ self.gsub(/(.)([A-Z])/) { Regexp.last_match[1] + '_' +
81
+ Regexp.last_match[2].downcase }
82
+ end
72
83
  end
@@ -1 +1 @@
1
- WF_CLI_VERSION = '2.3.1'.freeze
1
+ WF_CLI_VERSION = '2.4.0'.freeze
@@ -1,224 +1,15 @@
1
- require 'wavefront-sdk/mixins'
2
- require_relative './base'
1
+ require_relative 'base_write'
3
2
 
4
3
  module WavefrontCli
5
4
  #
6
- # Send points to a proxy.
5
+ # Send points via a proxy. This inherits from the same base class
6
+ # as Report, but has to do a couple of things differently, as it
7
+ # speaks to a proxy rather than to the API.
7
8
  #
8
- class Write < Base
9
- attr_reader :fmt
10
- include Wavefront::Mixins
11
-
9
+ class Write < BaseWrite
12
10
  def mk_creds
13
11
  { proxy: options[:proxy], port: options[:port] || 2878 }
14
12
  end
15
- def do_point
16
- p = { path: options[:'<metric>'],
17
- value: options[:'<value>'].delete('\\').to_f,
18
- tags: tags_to_hash(options[:tag]) }
19
-
20
- p[:source] = options[:host] if options[:host]
21
- p[:ts] = parse_time(options[:time]) if options[:time]
22
-
23
- begin
24
- wf.write(p)
25
- rescue Wavefront::Exception::InvalidEndpoint
26
- abort 'could not speak to proxy ' \
27
- "'#{options[:proxy]}:#{options[:port]}'."
28
- end
29
- end
30
-
31
- def do_file
32
- valid_format?(options[:infileformat])
33
- setup_fmt(options[:infileformat] || 'tmv')
34
- process_input(options[:'<file>'])
35
- end
36
-
37
- # Read the input, from a file or from STDIN, and turn each line
38
- # into Wavefront points.
39
- #
40
- def process_input(file)
41
- if file == '-'
42
- read_stdin
43
- else
44
- data = load_data(Pathname.new(file)).split("\n").map do |l|
45
- process_line(l)
46
- end
47
-
48
- wf.write(data)
49
- end
50
- end
51
-
52
- # Read from standard in and stream points through an open
53
- # socket. If the user hits ctrl-c, close the socket and exit
54
- # politely.
55
- #
56
- def read_stdin
57
- wf.open
58
- STDIN.each_line { |l| wf.write(process_line(l.strip), false) }
59
- wf.close
60
- rescue SystemExit, Interrupt
61
- puts 'ctrl-c. Exiting.'
62
- wf.close
63
- exit 0
64
- end
65
-
66
- # Find and return the value in a chunked line of input
67
- #
68
- # param chunks [Array] a chunked line of input from #process_line
69
- # return [Float] the value
70
- # raise TypeError if field does not exist
71
- # raise Wavefront::Exception::InvalidValue if it's not a value
72
- #
73
- def extract_value(chunks)
74
- v = chunks[fmt.index('v')]
75
- v.to_f
76
- end
77
-
78
- # Find and return the source in a chunked line of input.
79
- #
80
- # param chunks [Array] a chunked line of input from #process_line
81
- # return [Float] the timestamp, if it is there, or the current
82
- # UTC time if it is not.
83
- # raise TypeError if field does not exist
84
- #
85
- def extract_ts(chunks)
86
- ts = chunks[fmt.index('t')]
87
- return parse_time(ts) if valid_timestamp?(ts)
88
- rescue TypeError
89
- Time.now.utc.to_i
90
- end
91
-
92
- def extract_tags(chunks)
93
- tags_to_hash(chunks.last.split(/\s(?=(?:[^"]|"[^"]*")*$)/))
94
- end
95
-
96
- # Find and return the metric path in a chunked line of input.
97
- # The path can be in the data, or passed as an option, or both.
98
- # If the latter, then we assume the option is a prefix, and
99
- # concatenate the value in the data.
100
- #
101
- # param chunks [Array] a chunked line of input from #process_line
102
- # return [String] the metric path
103
- # raise TypeError if field does not exist
104
- #
105
- def extract_path(chunks)
106
- m = chunks[fmt.index('m')]
107
- return options[:metric] ? [options[:metric], m].join('.') : m
108
- rescue TypeError
109
- return options[:metric] if options[:metric]
110
- raise
111
- end
112
-
113
- # Find and return the source in a chunked line of input.
114
- #
115
- # param chunks [Array] a chunked line of input from #process_line
116
- # return [String] the source, if it is there, or if not, the
117
- # value passed through by -H, or the local hostname.
118
- #
119
- def extract_source(chunks)
120
- return chunks[fmt.index('s')]
121
- rescue TypeError
122
- options[:source] || Socket.gethostname
123
- end
124
-
125
- # Process a line of input, as described by the format string
126
- # held in @fmt. Produces a hash suitable for the SDK to send on.
127
- #
128
- # We let the user define most of the fields, but anything beyond
129
- # what they define is always assumed to be point tags. This is
130
- # because you can have arbitrarily many of those for each point.
131
- #
132
- def process_line(l)
133
- return true if l.empty?
134
- chunks = l.split(/\s+/, fmt.length)
135
- raise 'wrong number of fields' unless enough_fields?(l)
136
-
137
- begin
138
- point = { path: extract_path(chunks),
139
- value: extract_value(chunks) }
140
- point[:ts] = extract_ts(chunks) if fmt.include?('t')
141
- point[:source] = extract_source(chunks) if fmt.include?('s')
142
- point[:tags] = line_tags(chunks)
143
- rescue TypeError
144
- raise "could not process #{l}"
145
- end
146
-
147
- point
148
- end
149
-
150
- # We can get tags from the file, from the -T option, or both.
151
- # Merge them, making the -T win if there is a collision.
152
- #
153
- def line_tags(chunks)
154
- file_tags = fmt.last == 'T' ? extract_tags(chunks) : {}
155
- opt_tags = tags_to_hash(options[:tag])
156
- file_tags.merge(opt_tags)
157
- end
158
-
159
- # Takes an array of key=value tags (as produced by docopt) and
160
- # turns it into a hash of key: value tags. Anything not of the
161
- # form key=val is dropped. If key or value are quoted, we
162
- # remove the quotes.
163
- #
164
- # @param tags [Array]
165
- # return Hash
166
- #
167
- def tags_to_hash(tags)
168
- return nil unless tags
169
-
170
- [tags].flatten.each_with_object({}) do |t, ret|
171
- k, v = t.split('=', 2)
172
- k.gsub!(/^["']|["']$/, '')
173
- ret[k] = v.to_s.gsub(/^["']|["']$/, '') if v
174
- end
175
- end
176
-
177
- # The format string must contain a 'v'. It must not contain
178
- # anything other than 'm', 't', 'T', 's', or 'v', and the 'T',
179
- # if there, must be at the end. No letter must appear more than
180
- # once.
181
- #
182
- # @param fmt [String] format of input file
183
- #
184
- def valid_format?(fmt)
185
- if fmt.include?('v') && fmt.match(/^[mstv]+T?$/) &&
186
- fmt == fmt.split('').uniq.join
187
- return true
188
- end
189
-
190
- raise 'Invalid format string.'
191
- end
192
-
193
- # Make sure we have the right number of columns, according to
194
- # the format string. We want to take every precaution we can to
195
- # stop users accidentally polluting their metric namespace with
196
- # junk.
197
- #
198
- # If the format string says we are expecting point tags, we
199
- # may have more columns than the length of the format string.
200
- #
201
- def enough_fields?(l)
202
- ncols = l.split.length
203
-
204
- if fmt.include?('T')
205
- return false unless ncols >= fmt.length
206
- else
207
- return false unless ncols == fmt.length
208
- end
209
-
210
- true
211
- end
212
-
213
- # Although the SDK does value checking, we'll add another layer
214
- # of input checing here. See if the time looks valid. We'll
215
- # assume anything before 2000/01/01 or after a year from now is
216
- # wrong. Arbitrary, but there has to be a cut-off somewhere.
217
- #
218
- def valid_timestamp?(ts)
219
- (ts.is_a?(Integer) || ts.match(/^\d+$/)) &&
220
- ts.to_i > 946_684_800 && ts.to_i < (Time.now.to_i + 31_557_600)
221
- end
222
13
 
223
14
  def validate_opts
224
15
  unless options[:metric] || options[:format].include?('m')
@@ -228,15 +19,12 @@ module WavefrontCli
228
19
  raise 'Please supply a proxy address.' unless options[:proxy]
229
20
  end
230
21
 
231
- private
232
-
233
- def setup_fmt(fmt)
234
- @fmt = fmt.split('')
22
+ def open_connection
23
+ wf.open
235
24
  end
236
25
 
237
- def load_data(file)
238
- raise "Cannot open file '#{file}'." unless file.exist?
239
- IO.read(file)
26
+ def close_connection
27
+ wf.close
240
28
  end
241
29
  end
242
30
  end
@@ -20,6 +20,17 @@ class StringTest < MiniTest::Test
20
20
  'alpha] [-b beta] [-c gamma] <id>')
21
21
  end
22
22
 
23
+ def test_opt_fold
24
+ assert_equal('short string'.opt_fold, " short string\n")
25
+
26
+ str = '-o, --option PARAMETER a rather pointless option with a ' \
27
+ 'needlessly wordy description string'
28
+ pad = "\n" + ' ' * 12
29
+ assert_equal(" -o, --option PARAMETER a#{pad}rather pointless" \
30
+ "#{pad}option with a#{pad}needlessly wordy#{pad}" \
31
+ "description#{pad}string\n",str.opt_fold(30, 10))
32
+ end
33
+
23
34
  def test_fold_options
24
35
  str = '-l, --longoption a long option with a quite long ' \
25
36
  'description which needs folding'
@@ -42,4 +53,17 @@ class StringTest < MiniTest::Test
42
53
  assert_raises(ArgumentError) { 'm'.to_seconds }
43
54
  assert_raises(ArgumentError) { '3m5s'.to_seconds }
44
55
  end
56
+
57
+ def test_unit_factor
58
+ assert_equal(60, '1'.unit_factor(:m))
59
+ assert_equal(1, '1'.unit_factor('m'))
60
+ assert_equal(1, '1'.unit_factor(:t))
61
+ end
62
+
63
+ def test_to_snake
64
+ assert_equal('snake_case', 'snakeCase'.to_snake)
65
+ assert_equal('lots_and_lots_of_words', 'lotsAndLotsOfWords'.to_snake)
66
+ assert_equal('unchanged', 'unchanged'.to_snake)
67
+ assert_equal('Unchanged', 'Unchanged'.to_snake)
68
+ end
45
69
  end
@@ -23,7 +23,7 @@ Gem::Specification.new do |gem|
23
23
  gem.require_paths = %w(lib)
24
24
 
25
25
  gem.add_dependency 'docopt', '~> 0.6.0'
26
- gem.add_runtime_dependency 'wavefront-sdk', '~> 1.2', '>= 1.2.1'
26
+ gem.add_runtime_dependency 'wavefront-sdk', '~> 1.4', '>= 1.4.0'
27
27
 
28
28
  gem.add_development_dependency 'bundler', '~> 1.3'
29
29
  gem.add_development_dependency 'rake', '~> 12.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: 2.3.1
4
+ version: 2.4.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: 2018-03-24 00:00:00.000000000 Z
11
+ date: 2018-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docopt
@@ -30,20 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.2'
33
+ version: '1.4'
34
34
  - - ">="
35
35
  - !ruby/object:Gem::Version
36
- version: 1.2.1
36
+ version: 1.4.0
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - "~>"
42
42
  - !ruby/object:Gem::Version
43
- version: '1.2'
43
+ version: '1.4'
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 1.2.1
46
+ version: 1.4.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: bundler
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -166,6 +166,7 @@ files:
166
166
  - bin/wf
167
167
  - lib/wavefront-cli/alert.rb
168
168
  - lib/wavefront-cli/base.rb
169
+ - lib/wavefront-cli/base_write.rb
169
170
  - lib/wavefront-cli/cloudintegration.rb
170
171
  - lib/wavefront-cli/commands/alert.rb
171
172
  - lib/wavefront-cli/commands/base.rb
@@ -179,6 +180,7 @@ files:
179
180
  - lib/wavefront-cli/commands/notificant.rb
180
181
  - lib/wavefront-cli/commands/proxy.rb
181
182
  - lib/wavefront-cli/commands/query.rb
183
+ - lib/wavefront-cli/commands/report.rb
182
184
  - lib/wavefront-cli/commands/savedsearch.rb
183
185
  - lib/wavefront-cli/commands/source.rb
184
186
  - lib/wavefront-cli/commands/user.rb
@@ -204,6 +206,7 @@ files:
204
206
  - lib/wavefront-cli/display/printer/terse.rb
205
207
  - lib/wavefront-cli/display/proxy.rb
206
208
  - lib/wavefront-cli/display/query.rb
209
+ - lib/wavefront-cli/display/report.rb
207
210
  - lib/wavefront-cli/display/savedsearch.rb
208
211
  - lib/wavefront-cli/display/source.rb
209
212
  - lib/wavefront-cli/display/user.rb
@@ -218,8 +221,19 @@ files:
218
221
  - lib/wavefront-cli/metric.rb
219
222
  - lib/wavefront-cli/notificant.rb
220
223
  - lib/wavefront-cli/opt_handler.rb
224
+ - lib/wavefront-cli/output/base.rb
225
+ - lib/wavefront-cli/output/hcl.rb
226
+ - lib/wavefront-cli/output/hcl/alert.rb
227
+ - lib/wavefront-cli/output/hcl/base.rb
228
+ - lib/wavefront-cli/output/hcl/dashboard.rb
229
+ - lib/wavefront-cli/output/hcl/notificant.rb
230
+ - lib/wavefront-cli/output/json.rb
231
+ - lib/wavefront-cli/output/ruby.rb
232
+ - lib/wavefront-cli/output/wavefront.tf
233
+ - lib/wavefront-cli/output/yaml.rb
221
234
  - lib/wavefront-cli/proxy.rb
222
235
  - lib/wavefront-cli/query.rb
236
+ - lib/wavefront-cli/report.rb
223
237
  - lib/wavefront-cli/savedsearch.rb
224
238
  - lib/wavefront-cli/source.rb
225
239
  - lib/wavefront-cli/string.rb