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