wavefront-sdk 1.2.1 → 1.3.0
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.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/.rubocop.yml +62 -72
- data/.travis.yml +5 -4
- data/README.md +10 -0
- data/lib/wavefront-sdk/base.rb +28 -8
- data/lib/wavefront-sdk/constants.rb +3 -0
- data/lib/wavefront-sdk/credentials.rb +36 -17
- data/lib/wavefront-sdk/mixins.rb +3 -22
- data/lib/wavefront-sdk/notificant.rb +1 -1
- data/lib/wavefront-sdk/parse_time.rb +58 -0
- data/lib/wavefront-sdk/response.rb +7 -6
- data/lib/wavefront-sdk/search.rb +5 -2
- data/lib/wavefront-sdk/user.rb +7 -0
- data/lib/wavefront-sdk/validators.rb +15 -7
- data/lib/wavefront-sdk/version.rb +1 -1
- data/lib/wavefront-sdk/write.rb +29 -3
- data/spec/.rubocop.yml +3 -0
- data/spec/spec_helper.rb +24 -18
- data/spec/wavefront-sdk/alert_spec.rb +5 -0
- data/spec/wavefront-sdk/base_spec.rb +9 -9
- data/spec/wavefront-sdk/credentials_spec.rb +123 -4
- data/spec/wavefront-sdk/event_spec.rb +4 -5
- data/spec/wavefront-sdk/externallink_spec.rb +1 -1
- data/spec/wavefront-sdk/maintenancewindow_spec.rb +2 -2
- data/spec/wavefront-sdk/metric_spec.rb +2 -2
- data/spec/wavefront-sdk/mixins_spec.rb +1 -1
- data/spec/wavefront-sdk/parse_time_spec.rb +65 -0
- data/spec/wavefront-sdk/resources/test2.conf +4 -0
- data/spec/wavefront-sdk/response_spec.rb +3 -5
- data/spec/wavefront-sdk/savedsearch_spec.rb +1 -1
- data/spec/wavefront-sdk/source_spec.rb +1 -1
- data/spec/wavefront-sdk/user_spec.rb +5 -3
- data/spec/wavefront-sdk/validators_spec.rb +65 -62
- data/spec/wavefront-sdk/webhook_spec.rb +1 -1
- data/spec/wavefront-sdk/write_spec.rb +19 -1
- data/wavefront-sdk.gemspec +4 -3
- metadata +29 -15
data/.travis.yml
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
language: ruby
|
2
2
|
cache: bundler
|
3
3
|
rvm:
|
4
|
-
- 2.2.
|
5
|
-
- 2.3.
|
6
|
-
- 2.4.
|
4
|
+
- 2.2.9
|
5
|
+
- 2.3.6
|
6
|
+
- 2.4.3
|
7
|
+
- 2.5.0
|
7
8
|
before_install: gem install bundler --no-rdoc --no-ri
|
8
9
|
deploy:
|
9
10
|
provider: rubygems
|
@@ -13,4 +14,4 @@ deploy:
|
|
13
14
|
on:
|
14
15
|
tags: true
|
15
16
|
repo: snltd/wavefront-sdk
|
16
|
-
ruby: 2.3.
|
17
|
+
ruby: 2.3.6
|
data/README.md
CHANGED
@@ -69,6 +69,16 @@ wf.list.response.items.each { |user| puts user[:identifier] }
|
|
69
69
|
wf.delete('lolex@oldplace.com')
|
70
70
|
```
|
71
71
|
|
72
|
+
All API classes expect `user` support pagination and will only
|
73
|
+
return blocks of results. The `everything()` method returns a lazy
|
74
|
+
enumerator to make dealing with pagination simpler.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Wavefront::Alert.new(c.creds).everything.each_with_index do |m, i|
|
78
|
+
puts "#{i} #{m.id}"
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
72
82
|
Retrieve a timeseries over the last 10 minutes, with one minute bucket
|
73
83
|
granularity. We will describe the time as a Ruby object, but could also use
|
74
84
|
an epoch timestamp. The SDK happily converts between the two.
|
data/lib/wavefront-sdk/base.rb
CHANGED
@@ -21,8 +21,8 @@ module Wavefront
|
|
21
21
|
class Base
|
22
22
|
include Wavefront::Validators
|
23
23
|
include Wavefront::Mixins
|
24
|
-
attr_reader :opts, :debug, :noop, :verbose, :net, :
|
25
|
-
:
|
24
|
+
attr_reader :opts, :debug, :noop, :verbose, :net, :conn, :update_keys,
|
25
|
+
:logger
|
26
26
|
|
27
27
|
# Create a new API object. This will always be called from a
|
28
28
|
# class which inherits this one. If the inheriting class defines
|
@@ -181,18 +181,21 @@ module Wavefront
|
|
181
181
|
# :debug to DEBUG.
|
182
182
|
#
|
183
183
|
def log(msg, level = nil)
|
184
|
-
|
185
184
|
if logger
|
186
185
|
logger.send(level || :info, msg)
|
187
186
|
else
|
188
|
-
|
189
|
-
#
|
190
|
-
return if level == :debug && ! opts[:debug]
|
191
|
-
return if level == :info && ! opts[:verbose]
|
192
|
-
puts msg
|
187
|
+
print_message(msg, level)
|
193
188
|
end
|
194
189
|
end
|
195
190
|
|
191
|
+
# Print it unless it's a debug and we're not in debug
|
192
|
+
#
|
193
|
+
def print_message(msg, level)
|
194
|
+
return if level == :debug && ! opts[:debug]
|
195
|
+
return if level == :info && ! opts[:verbose]
|
196
|
+
puts msg
|
197
|
+
end
|
198
|
+
|
196
199
|
# If we need to massage a raw response to fit what the
|
197
200
|
# Wavefront::Response class expects (I'm looking at you,
|
198
201
|
# 'User'), a class can provide a {#response_shim} method.
|
@@ -205,6 +208,23 @@ module Wavefront
|
|
205
208
|
Wavefront::Response.new(body, resp.status, debug)
|
206
209
|
end
|
207
210
|
|
211
|
+
# Return all objects using a lazy enumerator
|
212
|
+
# @return Enumerable
|
213
|
+
#
|
214
|
+
def everything
|
215
|
+
Enumerator.new do |y|
|
216
|
+
offset = 0
|
217
|
+
limit = 100
|
218
|
+
|
219
|
+
loop do
|
220
|
+
resp = api_get('', { offset: offset, limit: limit }).response
|
221
|
+
resp.items.map { |i| y.<< i }
|
222
|
+
offset += limit
|
223
|
+
raise StopIteration unless resp.moreItems == true
|
224
|
+
end
|
225
|
+
end.lazy
|
226
|
+
end
|
227
|
+
|
208
228
|
private
|
209
229
|
|
210
230
|
# Try to describe the actual HTTP calls we make. There's a bit
|
@@ -31,11 +31,17 @@ module Wavefront
|
|
31
31
|
# and/or 'profile' which select a profile section from 'file'
|
32
32
|
#
|
33
33
|
def initialize(options = {})
|
34
|
-
raw = load_from_file(options)
|
35
|
-
|
36
|
-
populate(raw)
|
34
|
+
raw = load_from_file(cred_files(options), options[:profile] ||
|
35
|
+
'default')
|
36
|
+
populate(env_override(raw))
|
37
37
|
end
|
38
38
|
|
39
|
+
# If the user has set certain environment variables, their
|
40
|
+
# values will override values from the config file or
|
41
|
+
# command-line.
|
42
|
+
# @param raw [Hash] the existing credentials
|
43
|
+
# @return [Hash] the modified credentials
|
44
|
+
#
|
39
45
|
def env_override(raw)
|
40
46
|
{ endpoint: 'WAVEFRONT_ENDPOINT',
|
41
47
|
token: 'WAVEFRONT_TOKEN',
|
@@ -44,25 +50,39 @@ module Wavefront
|
|
44
50
|
raw
|
45
51
|
end
|
46
52
|
|
53
|
+
# Make the helper values. We use a Map so they're super-easy to
|
54
|
+
# access
|
55
|
+
#
|
56
|
+
# @param raw [Hash] the combined options from config file,
|
57
|
+
# command-line and env vars.
|
58
|
+
# @return void
|
59
|
+
#
|
47
60
|
def populate(raw)
|
48
61
|
@config = Map(raw)
|
49
62
|
@creds = Map(raw.select { |k, _v| [:endpoint, :token].include?(k) })
|
50
63
|
@proxy = Map(raw.select { |k, _v| [:proxy, :port].include?(k) })
|
51
64
|
end
|
52
65
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
66
|
+
# @return [Array] a list of possible credential files
|
67
|
+
#
|
68
|
+
def cred_files(opts = {})
|
69
|
+
if opts.key?(:file)
|
70
|
+
Array(Pathname.new(opts[:file]))
|
71
|
+
else
|
72
|
+
[Pathname.new('/etc/wavefront/credentials'),
|
73
|
+
Pathname.new(ENV['HOME']) + '.wavefront']
|
74
|
+
end
|
75
|
+
end
|
57
76
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
77
|
+
# @param files [Array][Pathname] a list of ini-style config files
|
78
|
+
# @param profile [String] a profile name
|
79
|
+
# @return [Hash] the given profile from the given list of files.
|
80
|
+
# If multiple files match, the last one will be used
|
81
|
+
#
|
82
|
+
def load_from_file(files, profile = 'default')
|
83
|
+
ret = {}
|
64
84
|
|
65
|
-
|
85
|
+
files.each do |f|
|
66
86
|
next unless f.exist?
|
67
87
|
ret = load_profile(f, profile)
|
68
88
|
ret[:file] = f
|
@@ -71,9 +91,8 @@ module Wavefront
|
|
71
91
|
ret
|
72
92
|
end
|
73
93
|
|
74
|
-
# Load in
|
75
|
-
#
|
76
|
-
# an error.
|
94
|
+
# Load in an (optionally) given section of an ini-style
|
95
|
+
# configuration file not there, we don't consider that an error.
|
77
96
|
#
|
78
97
|
# @param file [Pathname] the file to read
|
79
98
|
# @param profile [String] the section in the config to read
|
data/lib/wavefront-sdk/mixins.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'date'
|
2
2
|
require_relative './exception'
|
3
|
+
require_relative './parse_time'
|
3
4
|
|
4
5
|
module Wavefront
|
5
6
|
module Mixins
|
@@ -13,28 +14,8 @@ module Wavefront
|
|
13
14
|
# @raise Wavefront::InvalidTimestamp
|
14
15
|
#
|
15
16
|
def parse_time(t, ms = false)
|
16
|
-
|
17
|
-
|
18
|
-
# through. No validation, because maybe the user means one
|
19
|
-
# second past the epoch, or the year 2525.
|
20
|
-
#
|
21
|
-
return t if t.is_a?(Numeric)
|
22
|
-
|
23
|
-
if t.is_a?(String)
|
24
|
-
return t.to_i if t.match(/^\d+$/)
|
25
|
-
|
26
|
-
if t.start_with?('-') || t.start_with?('+')
|
27
|
-
return relative_time(t, ms)
|
28
|
-
end
|
29
|
-
|
30
|
-
begin
|
31
|
-
t = DateTime.parse("#{t} #{Time.now.getlocal.zone}")
|
32
|
-
rescue
|
33
|
-
raise Wavefront::Exception::InvalidTimestamp
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
ms ? t.to_datetime.strftime('%Q').to_i : t.strftime('%s').to_i
|
17
|
+
return relative_time(t, ms) if t =~ /^[\-+]/
|
18
|
+
ParseTime.new(t, ms).parse!
|
38
19
|
end
|
39
20
|
|
40
21
|
# Return a timetamp described by the given string. That is,
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Wavefront
|
2
|
+
# Parse various times into integers. This class is not for direct
|
3
|
+
# consumption: it's used by the mixins parse_time method, which
|
4
|
+
# does all the type sanity stuff.
|
5
|
+
#
|
6
|
+
class ParseTime
|
7
|
+
attr_reader :t, :ms
|
8
|
+
|
9
|
+
# param t [Numeric] a timestamp
|
10
|
+
# param ms [Bool] whether the timestamp is in milliseconds
|
11
|
+
#
|
12
|
+
def initialize(t, ms = false)
|
13
|
+
@t = t
|
14
|
+
@ms = ms
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Fixnum] timestamp
|
18
|
+
#
|
19
|
+
def parse_time_Fixnum
|
20
|
+
t
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Integer] timestamp
|
24
|
+
#
|
25
|
+
def parse_time_Integer
|
26
|
+
t
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Fixnum] timestamp
|
30
|
+
#
|
31
|
+
def parse_time_String
|
32
|
+
return t.to_i if t =~ /^\d+$/
|
33
|
+
@t = DateTime.parse("#{t} #{Time.now.getlocal.zone}")
|
34
|
+
parse_time_Time
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Integer] timestamp
|
38
|
+
#
|
39
|
+
def parse_time_Time
|
40
|
+
if ms
|
41
|
+
t.to_datetime.strftime('%Q').to_i
|
42
|
+
else
|
43
|
+
t.strftime('%s').to_i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_time_DateTime
|
48
|
+
parse_time_Time
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse!
|
52
|
+
method = ('parse_time_' + t.class.name).to_sym
|
53
|
+
send(method)
|
54
|
+
rescue
|
55
|
+
raise Wavefront::Exception::InvalidTimestamp
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -31,12 +31,7 @@ module Wavefront
|
|
31
31
|
# has changed underneath us.
|
32
32
|
#
|
33
33
|
def initialize(json, status, debug = false)
|
34
|
-
|
35
|
-
raw = json.empty? ? {} : JSON.parse(json, symbolize_names: true)
|
36
|
-
rescue
|
37
|
-
raw = { message: json, code: status }
|
38
|
-
end
|
39
|
-
|
34
|
+
raw = raw_response(json, status)
|
40
35
|
@status = build_status(raw, status)
|
41
36
|
@response = build_response(raw)
|
42
37
|
|
@@ -47,6 +42,12 @@ module Wavefront
|
|
47
42
|
raise Wavefront::Exception::UnparseableResponse
|
48
43
|
end
|
49
44
|
|
45
|
+
def raw_response(json, status)
|
46
|
+
json.empty? ? {} : JSON.parse(json, symbolize_names: true)
|
47
|
+
rescue
|
48
|
+
{ message: json, code: status }
|
49
|
+
end
|
50
|
+
|
50
51
|
def build_status(raw, status)
|
51
52
|
Wavefront::Type::Status.new(raw, status)
|
52
53
|
end
|
data/lib/wavefront-sdk/search.rb
CHANGED
@@ -72,8 +72,11 @@ module Wavefront
|
|
72
72
|
# active (false) entities
|
73
73
|
#
|
74
74
|
def raw_search(entity = nil, body = nil, deleted = false)
|
75
|
-
|
76
|
-
|
75
|
+
unless (entity.is_a?(String) || entity.is_a?(Symbol)) &&
|
76
|
+
body.is_a?(Hash)
|
77
|
+
raise ArgumentError
|
78
|
+
end
|
79
|
+
|
77
80
|
path = [entity]
|
78
81
|
path.<< 'deleted' if deleted
|
79
82
|
api_post(path, body.to_json, 'application/json')
|
data/lib/wavefront-sdk/user.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative './constants'
|
1
2
|
require_relative './exception'
|
2
3
|
|
3
4
|
module Wavefront
|
@@ -35,7 +36,8 @@ module Wavefront
|
|
35
36
|
#
|
36
37
|
def wf_metric_name?(v)
|
37
38
|
if v.is_a?(String) && v.size < 1024 &&
|
38
|
-
(v.match(
|
39
|
+
(v.match(/^#{DELTA}?[\w\-\.]+$/) ||
|
40
|
+
v.match(%r{^\"#{DELTA}?[\w\-\.\/,]+\"$}))
|
39
41
|
return true
|
40
42
|
end
|
41
43
|
|
@@ -162,13 +164,19 @@ module Wavefront
|
|
162
164
|
#
|
163
165
|
def wf_point_tags?(tags)
|
164
166
|
raise Wavefront::Exception::InvalidTag unless tags.is_a?(Hash)
|
167
|
+
tags.each { |k, v| wf_point_tag?(k, v) }
|
168
|
+
end
|
165
169
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
170
|
+
# Validate a single point tag, probably on behalf of
|
171
|
+
# #wf_point_tags?
|
172
|
+
# @param k [String] tag key
|
173
|
+
# @param v [String] tag value
|
174
|
+
# @raise Wavefront::Exception::InvalidTag if any tag is not valid
|
175
|
+
# @return nil
|
176
|
+
#
|
177
|
+
def wf_point_tag?(k, v)
|
178
|
+
return if k && v && (k.size + v.size < 254) && k =~ /^[\w\-\.:]+$/
|
179
|
+
raise Wavefront::Exception::InvalidTag
|
172
180
|
end
|
173
181
|
|
174
182
|
# Ensure the given argument is a valid Wavefront proxy ID
|
@@ -1 +1 @@
|
|
1
|
-
WF_SDK_VERSION = '1.
|
1
|
+
WF_SDK_VERSION = '1.3.0'.freeze
|
data/lib/wavefront-sdk/write.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require_relative './constants'
|
2
3
|
require_relative './base'
|
3
4
|
|
4
5
|
HOSTNAME = Socket.gethostname.freeze
|
@@ -108,6 +109,28 @@ module Wavefront
|
|
108
109
|
Wavefront::Response.new(resp, nil)
|
109
110
|
end
|
110
111
|
|
112
|
+
# A wrapper method around #write() which guarantees all points
|
113
|
+
# will be sent as deltas. You can still manually prefix any
|
114
|
+
# metric with a Δ and use #write(), but depending on your
|
115
|
+
# use-case, this method may be safer. It's easy to forget the Δ.
|
116
|
+
#
|
117
|
+
# @param points [Array[Hash]] see #write()
|
118
|
+
# @param openclose [Bool] see #write()
|
119
|
+
#
|
120
|
+
def write_delta(points, openclose = true)
|
121
|
+
write(paths_to_deltas(points), openclose)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Prefix all paths in a points array (as passed to
|
125
|
+
# #write_delta() with a delta symbol
|
126
|
+
#
|
127
|
+
# @param points [Array[Hash]] see #write()
|
128
|
+
# @return [Array[Hash]]
|
129
|
+
#
|
130
|
+
def paths_to_deltas(points)
|
131
|
+
[points].flatten.map { |p| p.tap { p[:path] = DELTA + p[:path] } }
|
132
|
+
end
|
133
|
+
|
111
134
|
def valid_point?(p)
|
112
135
|
return true if opts[:novalidate]
|
113
136
|
|
@@ -126,13 +149,14 @@ module Wavefront
|
|
126
149
|
end
|
127
150
|
end
|
128
151
|
|
129
|
-
# Convert a validated point
|
152
|
+
# Convert a validated point to a string conforming to
|
130
153
|
# https://community.wavefront.com/docs/DOC-1031. No validation
|
131
154
|
# is done here.
|
132
155
|
#
|
133
156
|
# @param p [Hash] a hash describing a point. See #write() for
|
134
157
|
# the format.
|
135
158
|
#
|
159
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
136
160
|
def hash_to_wf(p)
|
137
161
|
unless p.key?(:path) && p.key?(:value)
|
138
162
|
raise Wavefront::Exception::InvalidPoint
|
@@ -183,10 +207,12 @@ module Wavefront
|
|
183
207
|
return true
|
184
208
|
end
|
185
209
|
|
186
|
-
|
210
|
+
port = net[:port] || 2878
|
211
|
+
|
212
|
+
log("Connecting to #{net[:proxy]}:#{port}.", :info)
|
187
213
|
|
188
214
|
begin
|
189
|
-
@sock = TCPSocket.new(net[:proxy],
|
215
|
+
@sock = TCPSocket.new(net[:proxy], port)
|
190
216
|
rescue => e
|
191
217
|
log(e, :error)
|
192
218
|
raise Wavefront::Exception::InvalidEndpoint
|