wavefront-sdk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +20 -0
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +1157 -0
  5. data/.travis.yml +16 -0
  6. data/Gemfile +2 -0
  7. data/Gemfile.lock +58 -0
  8. data/LICENSE.txt +27 -0
  9. data/README.md +103 -0
  10. data/Rakefile +18 -0
  11. data/lib/wavefront-sdk/alert.rb +195 -0
  12. data/lib/wavefront-sdk/base.rb +251 -0
  13. data/lib/wavefront-sdk/cloudintegration.rb +88 -0
  14. data/lib/wavefront-sdk/credentials.rb +79 -0
  15. data/lib/wavefront-sdk/dashboard.rb +157 -0
  16. data/lib/wavefront-sdk/event.rb +173 -0
  17. data/lib/wavefront-sdk/exception.rb +39 -0
  18. data/lib/wavefront-sdk/externallink.rb +77 -0
  19. data/lib/wavefront-sdk/maintenancewindow.rb +75 -0
  20. data/lib/wavefront-sdk/message.rb +36 -0
  21. data/lib/wavefront-sdk/metric.rb +52 -0
  22. data/lib/wavefront-sdk/mixins.rb +60 -0
  23. data/lib/wavefront-sdk/proxy.rb +95 -0
  24. data/lib/wavefront-sdk/query.rb +96 -0
  25. data/lib/wavefront-sdk/response.rb +56 -0
  26. data/lib/wavefront-sdk/savedsearch.rb +88 -0
  27. data/lib/wavefront-sdk/search.rb +58 -0
  28. data/lib/wavefront-sdk/source.rb +131 -0
  29. data/lib/wavefront-sdk/user.rb +108 -0
  30. data/lib/wavefront-sdk/validators.rb +395 -0
  31. data/lib/wavefront-sdk/version.rb +1 -0
  32. data/lib/wavefront-sdk/webhook.rb +73 -0
  33. data/lib/wavefront-sdk/write.rb +225 -0
  34. data/pkg/wavefront-client-3.5.0.gem +0 -0
  35. data/spec/.rubocop.yml +14 -0
  36. data/spec/spec_helper.rb +157 -0
  37. data/spec/wavefront-sdk/alert_spec.rb +83 -0
  38. data/spec/wavefront-sdk/base_spec.rb +88 -0
  39. data/spec/wavefront-sdk/cloudintegration_spec.rb +54 -0
  40. data/spec/wavefront-sdk/credentials_spec.rb +55 -0
  41. data/spec/wavefront-sdk/dashboard_spec.rb +74 -0
  42. data/spec/wavefront-sdk/event_spec.rb +83 -0
  43. data/spec/wavefront-sdk/externallink_spec.rb +65 -0
  44. data/spec/wavefront-sdk/maintenancewindow_spec.rb +48 -0
  45. data/spec/wavefront-sdk/message_spec.rb +19 -0
  46. data/spec/wavefront-sdk/metric_spec.rb +21 -0
  47. data/spec/wavefront-sdk/mixins_spec.rb +27 -0
  48. data/spec/wavefront-sdk/proxy_spec.rb +41 -0
  49. data/spec/wavefront-sdk/query_spec.rb +51 -0
  50. data/spec/wavefront-sdk/resources/test.conf +10 -0
  51. data/spec/wavefront-sdk/response_spec.rb +47 -0
  52. data/spec/wavefront-sdk/savedsearch_spec.rb +54 -0
  53. data/spec/wavefront-sdk/search_spec.rb +47 -0
  54. data/spec/wavefront-sdk/source_spec.rb +48 -0
  55. data/spec/wavefront-sdk/user_spec.rb +56 -0
  56. data/spec/wavefront-sdk/validators_spec.rb +238 -0
  57. data/spec/wavefront-sdk/webhook_spec.rb +50 -0
  58. data/spec/wavefront-sdk/write_spec.rb +167 -0
  59. data/wavefront-sdk.gemspec +34 -0
  60. 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
@@ -0,0 +1,14 @@
1
+ #
2
+ # Allow long blocks of tests
3
+ #
4
+ Metrics/BlockLength:
5
+ Max: 120
6
+
7
+ Metrics/MethodLength:
8
+ Max: 80
9
+
10
+ Metrics/AbcSize:
11
+ Max: 45
12
+
13
+ Metrics/ClassLength:
14
+ Max: 300
@@ -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
+