wavefront-sdk 0.0.1
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 +7 -0
- data/.codeclimate.yml +20 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +1157 -0
- data/.travis.yml +16 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +58 -0
- data/LICENSE.txt +27 -0
- data/README.md +103 -0
- data/Rakefile +18 -0
- data/lib/wavefront-sdk/alert.rb +195 -0
- data/lib/wavefront-sdk/base.rb +251 -0
- data/lib/wavefront-sdk/cloudintegration.rb +88 -0
- data/lib/wavefront-sdk/credentials.rb +79 -0
- data/lib/wavefront-sdk/dashboard.rb +157 -0
- data/lib/wavefront-sdk/event.rb +173 -0
- data/lib/wavefront-sdk/exception.rb +39 -0
- data/lib/wavefront-sdk/externallink.rb +77 -0
- data/lib/wavefront-sdk/maintenancewindow.rb +75 -0
- data/lib/wavefront-sdk/message.rb +36 -0
- data/lib/wavefront-sdk/metric.rb +52 -0
- data/lib/wavefront-sdk/mixins.rb +60 -0
- data/lib/wavefront-sdk/proxy.rb +95 -0
- data/lib/wavefront-sdk/query.rb +96 -0
- data/lib/wavefront-sdk/response.rb +56 -0
- data/lib/wavefront-sdk/savedsearch.rb +88 -0
- data/lib/wavefront-sdk/search.rb +58 -0
- data/lib/wavefront-sdk/source.rb +131 -0
- data/lib/wavefront-sdk/user.rb +108 -0
- data/lib/wavefront-sdk/validators.rb +395 -0
- data/lib/wavefront-sdk/version.rb +1 -0
- data/lib/wavefront-sdk/webhook.rb +73 -0
- data/lib/wavefront-sdk/write.rb +225 -0
- data/pkg/wavefront-client-3.5.0.gem +0 -0
- data/spec/.rubocop.yml +14 -0
- data/spec/spec_helper.rb +157 -0
- data/spec/wavefront-sdk/alert_spec.rb +83 -0
- data/spec/wavefront-sdk/base_spec.rb +88 -0
- data/spec/wavefront-sdk/cloudintegration_spec.rb +54 -0
- data/spec/wavefront-sdk/credentials_spec.rb +55 -0
- data/spec/wavefront-sdk/dashboard_spec.rb +74 -0
- data/spec/wavefront-sdk/event_spec.rb +83 -0
- data/spec/wavefront-sdk/externallink_spec.rb +65 -0
- data/spec/wavefront-sdk/maintenancewindow_spec.rb +48 -0
- data/spec/wavefront-sdk/message_spec.rb +19 -0
- data/spec/wavefront-sdk/metric_spec.rb +21 -0
- data/spec/wavefront-sdk/mixins_spec.rb +27 -0
- data/spec/wavefront-sdk/proxy_spec.rb +41 -0
- data/spec/wavefront-sdk/query_spec.rb +51 -0
- data/spec/wavefront-sdk/resources/test.conf +10 -0
- data/spec/wavefront-sdk/response_spec.rb +47 -0
- data/spec/wavefront-sdk/savedsearch_spec.rb +54 -0
- data/spec/wavefront-sdk/search_spec.rb +47 -0
- data/spec/wavefront-sdk/source_spec.rb +48 -0
- data/spec/wavefront-sdk/user_spec.rb +56 -0
- data/spec/wavefront-sdk/validators_spec.rb +238 -0
- data/spec/wavefront-sdk/webhook_spec.rb +50 -0
- data/spec/wavefront-sdk/write_spec.rb +167 -0
- data/wavefront-sdk.gemspec +34 -0
- metadata +269 -0
@@ -0,0 +1 @@
|
|
1
|
+
WF_SDK_VERSION = '0.0.1'.freeze
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module Wavefront
|
4
|
+
#
|
5
|
+
# Manage and query Wavefront webhooks
|
6
|
+
#
|
7
|
+
class Webhook < Wavefront::Base
|
8
|
+
|
9
|
+
# GET /api/v2/webhook
|
10
|
+
# Get all webhooks for a customer.
|
11
|
+
#
|
12
|
+
# @param offset [Integer] webhook at which the list begins
|
13
|
+
# @param limit [Integer] the number of webhooks to return
|
14
|
+
#
|
15
|
+
def list(offset = 0, limit = 100)
|
16
|
+
api_get('', { offset: offset, limit: limit })
|
17
|
+
end
|
18
|
+
|
19
|
+
# POST /api/v2/webhook
|
20
|
+
# Create a specific webhook.
|
21
|
+
#
|
22
|
+
# @param body [Hash] a hash of parameters describing the webhook.
|
23
|
+
# Please refer to the Wavefront Swagger docs for key:value
|
24
|
+
# information
|
25
|
+
# @return [Hash]
|
26
|
+
#
|
27
|
+
def create(body)
|
28
|
+
raise ArgumentError unless body.is_a?(Hash)
|
29
|
+
api_post('', body, 'application/json')
|
30
|
+
end
|
31
|
+
|
32
|
+
# DELETE /api/v2/webhook/{id}
|
33
|
+
# Delete a specific webhook.
|
34
|
+
#
|
35
|
+
# @param id [String, Integer] ID of the webhook
|
36
|
+
# @return [Hash]
|
37
|
+
#
|
38
|
+
def delete(id)
|
39
|
+
wf_webhook_id?(id)
|
40
|
+
api_delete(id)
|
41
|
+
end
|
42
|
+
|
43
|
+
# GET /api/v2/webhook/{id}
|
44
|
+
# Get a specific webhook.
|
45
|
+
#
|
46
|
+
# @param id [String, Integer] ID of the webhook
|
47
|
+
# @return [Hash]
|
48
|
+
#
|
49
|
+
def describe(id)
|
50
|
+
wf_webhook_id?(id)
|
51
|
+
api_get(id)
|
52
|
+
end
|
53
|
+
|
54
|
+
# PUT /api/v2/webhook/{id}
|
55
|
+
# Update a specific webhook.
|
56
|
+
#
|
57
|
+
# @param body [Hash] a hash of parameters describing the webhook.
|
58
|
+
# @return [Hash]
|
59
|
+
# @raise any validation errors from body
|
60
|
+
#
|
61
|
+
def update(id, body)
|
62
|
+
wf_webhook_id?(id)
|
63
|
+
raise ArgumentError unless body.is_a?(Hash)
|
64
|
+
api_put(id, body)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# A standard response
|
69
|
+
#
|
70
|
+
class Response
|
71
|
+
class Webhook < Base; end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require_relative './base'
|
3
|
+
|
4
|
+
HOSTNAME = Socket.gethostname.freeze
|
5
|
+
|
6
|
+
module Wavefront
|
7
|
+
#
|
8
|
+
# This class helps you send points to a Wavefront proxy in native
|
9
|
+
# format. Usually this is done on port 2878.
|
10
|
+
#
|
11
|
+
class Write < Wavefront::Base
|
12
|
+
attr_reader :sock, :summary
|
13
|
+
|
14
|
+
# Construct an object which allows us to write points to a
|
15
|
+
# Wavefront proxy.
|
16
|
+
#
|
17
|
+
# @pram creds [Hash] must contain the keys endpoint: and port.
|
18
|
+
# @param options [Hash] can contain the following keys:
|
19
|
+
# proxy [String] the address of the Wavefront proxy. ('wavefront')
|
20
|
+
# port [Integer] the port of the Wavefront proxy (2878)
|
21
|
+
# tags [Hash] point tags which will be applied to every point
|
22
|
+
# noop [Bool] if true, no proxy connection will be made, and
|
23
|
+
# instead of sending the points, they will be printed in
|
24
|
+
# Wavefront wire format.
|
25
|
+
# novalidate [Bool] if true, points will not be validated.
|
26
|
+
# This might make things go marginally quicker if you have
|
27
|
+
# done point validation higher up in the chain.
|
28
|
+
# verbose [Bool]
|
29
|
+
# debug [Bool]
|
30
|
+
#
|
31
|
+
def post_initialize(_creds = {}, options = {})
|
32
|
+
defaults = { tags: nil,
|
33
|
+
noop: false,
|
34
|
+
novalidate: false,
|
35
|
+
verbose: false,
|
36
|
+
debug: false }
|
37
|
+
|
38
|
+
@summary = { sent: 0, rejected: 0, unsent: 0 }
|
39
|
+
@opts = setup_options(options, defaults)
|
40
|
+
|
41
|
+
wf_point_tags?(opts[:tags]) if opts[:tags]
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_options(user, defaults)
|
45
|
+
defaults.merge(user)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Send raw data to a Wavefront proxy, automatically opening and
|
49
|
+
# closing a socket.
|
50
|
+
#
|
51
|
+
# @param point [Array[String]] an array of points in native
|
52
|
+
# Wavefront wire format, as described in
|
53
|
+
# https://community.wavefront.com/docs/DOC-1031. No validation
|
54
|
+
# is performed.
|
55
|
+
#
|
56
|
+
def raw(points, openclose = true)
|
57
|
+
open if openclose
|
58
|
+
|
59
|
+
begin
|
60
|
+
[points].flatten.each{ |p| send_point(p) }
|
61
|
+
ensure
|
62
|
+
close if openclose
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Send multiple points to a Wavefront proxy.
|
67
|
+
#
|
68
|
+
# @param points [Array[Hash]] an array of points. Each point is
|
69
|
+
# defined as a hash with the following keys:
|
70
|
+
# path [String] metric path. (mandatory)
|
71
|
+
# value [Numeric] value of metric. Numeric. Mandatory.
|
72
|
+
# ts [Time, Integer] timestamp for point. Defaults to
|
73
|
+
# current UTC time.
|
74
|
+
# source [String] originating source of metric. Defaults to
|
75
|
+
# the local hostname.
|
76
|
+
# tags [Hash] key: value point tags which will be applied in
|
77
|
+
# addition to any tags defined in the #initialize()
|
78
|
+
# method.
|
79
|
+
# @param openclose [Bool] if this is false, you must have
|
80
|
+
# already opened a socket to the proxy. If it is true, a
|
81
|
+
# connection will be opened for you, used, and closed.
|
82
|
+
# @raise any unhandled point validation error is passed through
|
83
|
+
# @return true if no points are rejected, otherwise false
|
84
|
+
#
|
85
|
+
def write(points = [], openclose = true)
|
86
|
+
open if openclose
|
87
|
+
|
88
|
+
begin
|
89
|
+
[points].flatten.each do |p|
|
90
|
+
p[:ts] = p[:ts].to_i if p[:ts].is_a?(Time)
|
91
|
+
valid_point?(p)
|
92
|
+
send_point(hash_to_wf(p))
|
93
|
+
end
|
94
|
+
ensure
|
95
|
+
close if openclose
|
96
|
+
end
|
97
|
+
|
98
|
+
Wavefront::Response::Write.new(summary.to_json, nil)
|
99
|
+
end
|
100
|
+
|
101
|
+
def valid_point?(p)
|
102
|
+
return true if opts[:novalidate]
|
103
|
+
|
104
|
+
begin
|
105
|
+
wf_point?(p)
|
106
|
+
return true
|
107
|
+
rescue Wavefront::Exception::InvalidMetricName,
|
108
|
+
Wavefront::Exception::InvalidMetricValue,
|
109
|
+
Wavefront::Exception::InvalidTimestamp,
|
110
|
+
Wavefront::Exception::InvalidSourceId,
|
111
|
+
Wavefront::Exception::InvalidTag => e
|
112
|
+
log('Invalid point, skipping.', :info)
|
113
|
+
log("Invalid point: #{p}. (#{e})", :debug)
|
114
|
+
summary[:rejected] += 1
|
115
|
+
return false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Convert a validated point has to a string conforming to
|
120
|
+
# https://community.wavefront.com/docs/DOC-1031. No validation
|
121
|
+
# is done here.
|
122
|
+
#
|
123
|
+
# @param p [Hash] a hash describing a point. See #write() for
|
124
|
+
# the format.
|
125
|
+
#
|
126
|
+
def hash_to_wf(p)
|
127
|
+
unless p.key?(:path) && p.key?(:value)
|
128
|
+
raise Wavefront::Exception::InvalidPoint
|
129
|
+
end
|
130
|
+
|
131
|
+
p[:source] = HOSTNAME unless p.key?(:source)
|
132
|
+
|
133
|
+
m = [p[:path], p[:value]]
|
134
|
+
m.<< p[:ts] if p[:ts]
|
135
|
+
m.<< 'source=' + p[:source]
|
136
|
+
m.<< p[:tags].to_wf_tag if p[:tags]
|
137
|
+
m.<< opts[:tags].to_wf_tag if opts[:tags]
|
138
|
+
m.join(' ')
|
139
|
+
end
|
140
|
+
|
141
|
+
# Send a point which is already in Wavefront wire format.
|
142
|
+
#
|
143
|
+
# @param point [String] a point description, probably from
|
144
|
+
# #hash_to_wf()
|
145
|
+
#
|
146
|
+
def send_point(point)
|
147
|
+
if opts[:noop]
|
148
|
+
log "Would send: #{point}"
|
149
|
+
return
|
150
|
+
end
|
151
|
+
|
152
|
+
log("Sending: #{point}", :info)
|
153
|
+
|
154
|
+
begin
|
155
|
+
sock.puts(point)
|
156
|
+
rescue => e
|
157
|
+
summary[:unsent] += 1
|
158
|
+
log('WARNING: failed to send point.')
|
159
|
+
log(e.to_s, :debug)
|
160
|
+
return false
|
161
|
+
end
|
162
|
+
|
163
|
+
summary[:sent] += 1
|
164
|
+
true
|
165
|
+
end
|
166
|
+
|
167
|
+
# Open a socket to a Wavefront proxy, putting the descriptor
|
168
|
+
# in instance variable @sock.
|
169
|
+
#
|
170
|
+
def open
|
171
|
+
if opts[:noop]
|
172
|
+
log('No-op requested. Not opening connection to proxy.')
|
173
|
+
return true
|
174
|
+
end
|
175
|
+
|
176
|
+
log("Connecting to #{net[:proxy]}:#{net[:port]}.", :info)
|
177
|
+
|
178
|
+
begin
|
179
|
+
@sock = TCPSocket.new(net[:proxy], net[:port])
|
180
|
+
rescue => e
|
181
|
+
log(e, :error)
|
182
|
+
raise Wavefront::Exception::InvalidEndpoint
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Close the socket described by the @sock instance variable.
|
187
|
+
#
|
188
|
+
def close
|
189
|
+
return if opts[:noop]
|
190
|
+
log('Closing connection to proxy.', :info)
|
191
|
+
sock.close
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
# Overload the method which sets an API endpoint. A proxy
|
197
|
+
# endpoint has an address and a port, rather than an address and
|
198
|
+
# a token.
|
199
|
+
#
|
200
|
+
def setup_endpoint(creds)
|
201
|
+
@net = creds
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class Response
|
206
|
+
# The Write response forges status and response methods to look
|
207
|
+
# like other classes and create a more consistent interface. As
|
208
|
+
# the request does not happen over HTTP, there's not much to put
|
209
|
+
# in the status object. The response object is the contents of
|
210
|
+
# the summary variable.
|
211
|
+
#
|
212
|
+
# @response=#<struct sent=1, rejected=0, unsent=0>,
|
213
|
+
# @status=#<struct result="OK", message=nil, code=nil>>
|
214
|
+
#
|
215
|
+
class Write < Base
|
216
|
+
def populate(raw, status)
|
217
|
+
@response = Struct.new(*raw.keys).new(*raw.values).freeze
|
218
|
+
|
219
|
+
ok = raw[:rejected] == 0 && raw[:unsent] == 0 ? 'OK' : 'ERROR'
|
220
|
+
|
221
|
+
@status = Struct.new(:result, :message, :code).new(ok, nil, nil)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
Binary file
|
data/spec/.rubocop.yml
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
#
|
2
|
+
# Stuff needed by multiple tests
|
3
|
+
#
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'webmock/minitest'
|
6
|
+
|
7
|
+
CREDS = {
|
8
|
+
endpoint: 'test.example.com',
|
9
|
+
token: '0123456789-ABCDEF'
|
10
|
+
}
|
11
|
+
|
12
|
+
POST_HEADERS = {
|
13
|
+
:'Content-Type' => 'text/plain', :Accept => 'application/json'
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
JSON_POST_HEADERS = {
|
17
|
+
:'Content-Type' => 'application/json', :Accept => 'application/json'
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
DUMMY_RESPONSE = '{"status":{"result":"OK","message":"","code":200},' \
|
21
|
+
'"response":{"items":[{"name":"test data"}],"offset":0,' \
|
22
|
+
'"limit":100,"totalItems":3,"moreItems":false}}'
|
23
|
+
|
24
|
+
class WavefrontTestBase < MiniTest::Test
|
25
|
+
attr_reader :wf, :wf_noop, :uri_base, :headers
|
26
|
+
|
27
|
+
def initialize(args)
|
28
|
+
require_relative "../lib/wavefront-sdk/#{class_basename.downcase}"
|
29
|
+
super(args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def class_basename
|
33
|
+
self.class.name.match(/Wavefront(\w+)Test/)[1]
|
34
|
+
end
|
35
|
+
|
36
|
+
def api_base
|
37
|
+
class_basename.downcase
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup
|
41
|
+
klass = Object.const_get('Wavefront').const_get(class_basename)
|
42
|
+
@wf = klass.new(CREDS)
|
43
|
+
@uri_base = "https://#{CREDS[:endpoint]}/api/v2/" + api_base
|
44
|
+
@headers = { 'Authorization' => "Bearer #{CREDS[:token]}" }
|
45
|
+
end
|
46
|
+
|
47
|
+
def target_uri(path)
|
48
|
+
[uri_base, path].join(path.start_with?('?') ? '' : '/')
|
49
|
+
end
|
50
|
+
|
51
|
+
# A shorthand method for very common tests.
|
52
|
+
#
|
53
|
+
# @param method [String] the method you wish to test
|
54
|
+
# @args [String, Integer, Array] arguments with which to call method
|
55
|
+
# @path [String] extra API path components (beyond /api/v2/class)
|
56
|
+
# @call [Symbol] the type of API call (:get, :put etc.)
|
57
|
+
# @more_headers [Hash] any additional headers which should be
|
58
|
+
# sent. You will normally need to add these for :put and :post
|
59
|
+
# requests.
|
60
|
+
# @body [String] a JSON object you expect to be sent as part of
|
61
|
+
# the request
|
62
|
+
#
|
63
|
+
#
|
64
|
+
def should_work(method, args, path, call = :get, more_headers = {},
|
65
|
+
body = nil, id = nil)
|
66
|
+
path = Array(path)
|
67
|
+
uri = target_uri(path.first).sub(/\/$/, '')
|
68
|
+
|
69
|
+
headers = { 'Accept': /.*/,
|
70
|
+
'Accept-Encoding': /.*/,
|
71
|
+
'Authorization': 'Bearer 0123456789-ABCDEF',
|
72
|
+
'User-Agent': "wavefront-sdk #{WF_SDK_VERSION}"
|
73
|
+
}.merge(more_headers)
|
74
|
+
|
75
|
+
if body
|
76
|
+
stub_request(call, uri).with(body: body, headers:headers)
|
77
|
+
.to_return(body: DUMMY_RESPONSE, status: 200)
|
78
|
+
else
|
79
|
+
stub_request(call, uri).to_return(body: DUMMY_RESPONSE, status: 200)
|
80
|
+
end
|
81
|
+
|
82
|
+
if args.is_a?(Hash)
|
83
|
+
if id
|
84
|
+
wf.send(method, id, args)
|
85
|
+
else
|
86
|
+
wf.send(method, args)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
if id
|
90
|
+
wf.send(method, id, *args)
|
91
|
+
else
|
92
|
+
wf.send(method, *args)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
assert_requested(call, uri, headers: headers)
|
97
|
+
WebMock.reset!
|
98
|
+
end
|
99
|
+
|
100
|
+
def standard_exception
|
101
|
+
Object.const_get('Wavefront::Exception')
|
102
|
+
.const_get("Invalid#{class_basename}Id")
|
103
|
+
end
|
104
|
+
|
105
|
+
def should_be_invalid(method, args = '!!invalid_val!!')
|
106
|
+
assert_raises(standard_exception) { wf.send(method, *args) }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Generic tag method testing.
|
110
|
+
#
|
111
|
+
def tag_tester(id)
|
112
|
+
# Can we get tags? : tests #tags
|
113
|
+
#
|
114
|
+
should_work('tags', id, "#{id}/tag")
|
115
|
+
should_be_invalid('tags')
|
116
|
+
|
117
|
+
# Can we set tags? tests #tag_set
|
118
|
+
#
|
119
|
+
should_work('tag_set', [id, 'tag'],
|
120
|
+
["#{id}/tag", ['tag'].to_json], :post, JSON_POST_HEADERS)
|
121
|
+
should_work('tag_set', [id, %w(tag1 tag2)],
|
122
|
+
["#{id}/tag", %w(tag1 tag2).to_json], :post,
|
123
|
+
JSON_POST_HEADERS)
|
124
|
+
should_fail_tags('tag_set', id)
|
125
|
+
|
126
|
+
# Can we add tags? : tests #tag_add
|
127
|
+
#
|
128
|
+
should_work('tag_add', [id, 'tagval'],
|
129
|
+
["#{id}/tag/tagval", nil], :put, JSON_POST_HEADERS)
|
130
|
+
should_fail_tags('tag_add', id)
|
131
|
+
|
132
|
+
# Can we delete tags? : tests #tag_delete
|
133
|
+
#
|
134
|
+
should_work('tag_delete', [id, 'tagval'], "#{id}/tag/tagval", :delete)
|
135
|
+
should_fail_tags('tag_delete', id)
|
136
|
+
end
|
137
|
+
|
138
|
+
def should_fail_tags(method, id)
|
139
|
+
assert_raises(standard_exception) do
|
140
|
+
wf.send(method, '!!invalid!!', 'tag1')
|
141
|
+
end
|
142
|
+
|
143
|
+
assert_raises(Wavefront::Exception::InvalidString) do
|
144
|
+
wf.send(method, id, '<!!!>')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Hash
|
150
|
+
|
151
|
+
# A quick way to deep-copy a hash.
|
152
|
+
#
|
153
|
+
def dup
|
154
|
+
Marshal.load(Marshal.dump(self))
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|