grafana 0.8.5 → 1.1.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.
Files changed (49) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +8 -3
  3. data/lib/_logging.rb_ +55 -0
  4. data/lib/grafana.rb +1 -1
  5. data/lib/grafana/admin.rb +2 -1
  6. data/lib/grafana/alerts.rb +338 -14
  7. data/lib/grafana/annotations.rb +284 -9
  8. data/lib/grafana/auth.rb +150 -0
  9. data/lib/grafana/client.rb +81 -5
  10. data/lib/grafana/dashboard.rb +99 -7
  11. data/lib/grafana/dashboard_permissions.rb +132 -0
  12. data/lib/grafana/dashboard_versions.rb +101 -5
  13. data/lib/grafana/datasource.rb +34 -38
  14. data/lib/grafana/folder.rb +198 -0
  15. data/lib/grafana/folder_and_dashboard_search.rb +57 -0
  16. data/lib/grafana/folder_permissions.rb +155 -0
  17. data/lib/grafana/logging.rb +55 -0
  18. data/lib/grafana/login.rb +93 -49
  19. data/lib/grafana/network.rb +130 -101
  20. data/lib/grafana/organization.rb +2 -1
  21. data/lib/grafana/organizations.rb +15 -6
  22. data/lib/grafana/playlist.rb +594 -0
  23. data/lib/grafana/preferences.rb +122 -0
  24. data/lib/grafana/tags.rb +16 -0
  25. data/lib/grafana/teams.rb +364 -0
  26. data/lib/grafana/tools.rb +42 -9
  27. data/lib/grafana/user.rb +6 -2
  28. data/lib/grafana/users.rb +19 -11
  29. data/lib/grafana/validator.rb +47 -2
  30. data/lib/grafana/version.rb +3 -3
  31. metadata +16 -39
  32. data/doc/Array.html +0 -200
  33. data/doc/Boolean.html +0 -122
  34. data/doc/FalseClass.html +0 -132
  35. data/doc/Grafana.html +0 -172
  36. data/doc/Hash.html +0 -212
  37. data/doc/Logging.html +0 -326
  38. data/doc/Object.html +0 -286
  39. data/doc/Time.html +0 -200
  40. data/doc/TrueClass.html +0 -132
  41. data/doc/_index.html +0 -380
  42. data/doc/class_list.html +0 -51
  43. data/doc/file.README.html +0 -117
  44. data/doc/file_list.html +0 -56
  45. data/doc/frames.html +0 -17
  46. data/doc/index.html +0 -117
  47. data/doc/method_list.html +0 -747
  48. data/doc/top-level-namespace.html +0 -112
  49. data/lib/logging.rb +0 -35
@@ -1,39 +1,314 @@
1
1
 
2
2
  module Grafana
3
3
 
4
- # http://docs.grafana.org/http_api/annotations/
4
+ # This is the API documentation for the new Grafana Annotations feature released in Grafana 4.6.
5
+ # Annotations are saved in the Grafana database (sqlite, mysql or postgres).
6
+ #
7
+ # Annotations can be global annotations that can be shown on any dashboard by configuring an annotation
8
+ # data source - they are filtered by tags.
9
+ #
10
+ # Or they can be tied to a panel on a dashboard and are then only shown on that panel.
11
+ #
12
+ # original API Documentation can be found under: http://docs.grafana.org/http_api/annotations/
5
13
  #
6
14
  module Annotations
7
15
 
8
16
  # Find Annotations
9
17
  # http://docs.grafana.org/http_api/annotations/#find-annotations
10
- # GET /api/annotations?from=1506676478816&to=1507281278816&tags=tag1&tags=tag2&limit=100
11
- def find_annotation( params ); end
18
+ #
19
+ # @param [Hash] params
20
+ # @option params [Integer] from: epoch datetime in milliseconds. Optional.
21
+ # @option params [Integer] to: epoch datetime in milliseconds. Optional.
22
+ # @option params [Integer] limit: number. Optional - default is 10. Max limit for results returned.
23
+ # @option params [Integer] alert_id: number. Optional. Find annotations for a specified alert.
24
+ # @option params [Mixed] dashboard: number. Optional. Find annotations that are scoped to a specific dashboard
25
+ # @option params [Integer] panel_id: number. Optional. Find annotations that are scoped to a specific panel
26
+ # @option params [Array] tags: Optional. Use this to filter global annotations.
27
+ # Global annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel.
28
+ # To do an "AND" filtering with multiple tags, specify the tags parameter multiple times e.g.
29
+ #
30
+ # @example
31
+ # params = {
32
+ # limit: 5,
33
+ # tags: [ 'spec', 'test' ]
34
+ # }
35
+ # find_annotation( params )
36
+ #
37
+ # @return [Array]
38
+ #
39
+ def find_annotation( params )
40
+
41
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
42
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
43
+
44
+ dashboard = validate( params, required: false, var: 'dashboard' )
45
+ from = validate( params, required: false, var: 'from', type: Integer )
46
+ to = validate( params, required: false, var: 'to', type: Integer )
47
+ limit = validate( params, required: false, var: 'limit', type: Integer ) || 10
48
+ alert_id = validate( params, required: false, var: 'alert_id', type: Integer )
49
+ panel_id = validate( params, required: false, var: 'panel_id', type: Integer )
50
+ tags = validate( params, required: false, var: 'tags', type: Array )
51
+
52
+ if( dashboard.is_a?(String) )
53
+
54
+ dashboard = search_dashboards( query: dashboard )
55
+
56
+ return { 'status' => 404, 'message' => format( 'No Dashboard \'%s\' found', dashboard) } if( dashboard.nil? || dashboard.dig('status').to_i != 200 )
57
+
58
+ dashboard = dashboard.dig('message').first unless( dashboard.nil? && dashboard.dig('status').to_i == 200 )
59
+ dashboard = dashboard.dig('id') unless( dashboard.nil? )
60
+
61
+ return { 'status' => 404, 'message' => format( 'No Dashboard \'%s\' found', dashboard) } if( dashboard.nil? )
62
+ end
63
+
64
+ api = []
65
+ api << format( 'from=%s', from ) unless( from.nil? )
66
+ api << format( 'to=%s', to ) unless( to.nil? )
67
+ api << format( 'limit=%s', limit ) unless( limit.nil? )
68
+ api << format( 'alertId=%s', alert_id ) unless( alert_id.nil? )
69
+ api << format( 'panelId=%s', panel_id ) unless( panel_id.nil? )
70
+ api << format( 'dashboardId=%s', dashboard ) unless( dashboard.nil? )
71
+
72
+ unless( tags.nil? )
73
+ tags = tags.join( '&tags=' ) if( tags.is_a?( Array ) )
74
+ api << format( 'tags=%s', tags )
75
+ end
76
+ api = api.join( '&' )
77
+
78
+ endpoint = format( '/api/annotations/?%s' , api )
79
+
80
+ @logger.debug("Attempting to search for annotations (GET #{endpoint})") if @debug
81
+
82
+ get( endpoint )
83
+ end
12
84
 
13
85
  # Create Annotation
86
+ #
87
+ # Creates an annotation in the Grafana database.
88
+ # The dashboard_id and panel_id fields are optional.
89
+ # If they are not specified then a global annotation is created and can be queried in any dashboard that adds
90
+ # the Grafana annotations data source.
91
+ #
92
+ # When creating a region annotation the response will include both id and endId, if not only id.
93
+ #
14
94
  # http://docs.grafana.org/http_api/annotations/#create-annotation
15
95
  # POST /api/annotations
16
- def create_annotation( params ); end
96
+ #
97
+ #
98
+ # @param [Hash] params
99
+ # @option params [Mixed] dashboard
100
+ # @option params [Integer] panel_id
101
+ # @option params [Integer] time:
102
+ # @option params [Integer] time_end:
103
+ # @option params [Boolean] region:
104
+ # @option params [Array] tags:
105
+ # @option params [String] text:
106
+ #
107
+ # @example
108
+ # params = {
109
+ # time: Time.now.to_i,
110
+ # tags: [ 'spec', 'test' ],
111
+ # text: 'test annotation'
112
+ # }
113
+ # create_annotation( params )
114
+ #
115
+ # @return [Hash]
116
+ #
117
+ def create_annotation( params )
118
+
119
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
120
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
121
+
122
+ dashboard = validate( params, required: false, var: 'dashboard' )
123
+ panel_id = validate( params, required: false, var: 'panel_id', type: Integer )
124
+ time = validate( params, required: false, var: 'time', type: Integer ) || Time.now.to_i
125
+ time_end = validate( params, required: false, var: 'time_end', type: Integer )
126
+ region = validate( params, required: false, var: 'region', type: Boolean )
127
+ tags = validate( params, required: true , var: 'tags', type: Array )
128
+ text = validate( params, required: true , var: 'text', type: String )
129
+
130
+ if( dashboard.is_a?(String) )
131
+
132
+ dashboard = search_dashboards( query: dashboard )
133
+
134
+ return { 'status' => 404, 'message' => format( 'No Dashboard \'%s\' found', dashboard) } if( dashboard.nil? || dashboard.dig('status').to_i != 200 )
135
+
136
+ dashboard = dashboard.dig('message').first unless( dashboard.nil? && dashboard.dig('status').to_i == 200 )
137
+ dashboard = dashboard.dig('id') unless( dashboard.nil? )
138
+
139
+ return { 'status' => 404, 'message' => format( 'No Dashboard \'%s\' found', dashboard) } if( dashboard.nil? )
140
+ end
141
+
142
+ unless( time_end.nil? )
143
+ return { 'status' => 404, 'message' => format( '\'end_time\' can\'t be lower then \'time\'' ) } if( time_end < time )
144
+ end
145
+
146
+ endpoint = '/api/annotations'
147
+ payload = {
148
+ dashboardId: dashboard,
149
+ panelId: panel_id,
150
+ time: time,
151
+ timeEnd: time_end,
152
+ isRegion: region,
153
+ tags: tags,
154
+ text: text
155
+ }
156
+ payload.reject!{ |_k, v| v.nil? }
157
+
158
+ post(endpoint, payload.to_json)
159
+ end
17
160
 
18
161
  # Create Annotation in Graphite format
162
+ #
163
+ # Creates an annotation by using Graphite-compatible event format.
164
+ # The when and data fields are optional.
165
+ # If when is not specified then the current time will be used as annotation's timestamp.
166
+ # The tags field can also be in prior to Graphite 0.10.0 format (string with multiple tags being separated by a space).
167
+ #
19
168
  # http://docs.grafana.org/http_api/annotations/#create-annotation-in-graphite-format
20
169
  # POST /api/annotations/graphite
21
- def create_annotation_graphite( params ); end
170
+ #
171
+ # @param [Hash] params
172
+ # @option params [Integer] what
173
+ # @option params [Integer] when
174
+ # @option params [Array] tags
175
+ # @option params [String] data
176
+ #
177
+ # @example
178
+ # params = {
179
+ # what: 'spec test graphite annotation',
180
+ # when: Time.now.to_i,
181
+ # tags: [ 'spec', 'test' ],
182
+ # data: 'test annotation'
183
+ # }
184
+ # create_annotation_graphite( params )
185
+ #
186
+ # @return [Hash]
187
+ #
188
+ def create_annotation_graphite( params )
189
+
190
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
191
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
192
+
193
+ what = validate( params, required: true , var: 'what', type: String )
194
+ time_when = validate( params, required: false, var: 'when', type: Integer ) || Time.now.to_i
195
+ tags = validate( params, required: true , var: 'tags', type: Array )
196
+ data = validate( params, required: false, var: 'data', type: String )
197
+
198
+ endpoint = '/api/annotations/graphite'
199
+ payload = {
200
+ what: what,
201
+ when: time_when,
202
+ tags: tags,
203
+ data: data
204
+ }
205
+ payload.reject!{ |_k, v| v.nil? }
206
+
207
+ post(endpoint, payload.to_json)
208
+ end
22
209
 
23
210
  # Update Annotation
211
+ #
24
212
  # http://docs.grafana.org/http_api/annotations/#update-annotation
25
- # PUT /api/annotations/:id
26
- def update_annotation( params ); end
213
+ #
214
+ # @param [Hash] params
215
+ # @option params [Integer] annotation
216
+ # @option params [Integer] time
217
+ # @option params [Integer] time_end
218
+ # @option params [Boolean] region
219
+ # @option params [Array] tags
220
+ # @option params [String] text
221
+ #
222
+ # @example
223
+ # params = {
224
+ # annotation: 1,
225
+ # tags: [ 'deployment' ],
226
+ # text: 'git tag #1234'
227
+ # }
228
+ # update_annotation( params )
229
+ #
230
+ # @return [Hash]
231
+ #
232
+ def update_annotation( params )
233
+
234
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
235
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
236
+
237
+ annotation_id = validate( params, required: true, var: 'annotation', type: Integer )
238
+ time = validate( params, required: false, var: 'time', type: Integer )
239
+ time_end = validate( params, required: false, var: 'time_end', type: Integer )
240
+ region = validate( params, required: false, var: 'region', type: Boolean )
241
+ tags = validate( params, required: false, var: 'tags', type: Array )
242
+ text = validate( params, required: false, var: 'text', type: String )
243
+
244
+ unless( time_end.nil? )
245
+ return { 'status' => 404, 'message' => format( '\'end_time\' can\'t be lower then \'time\'' ) } if( time_end < time )
246
+ end
247
+
248
+ endpoint = format( '/api/annotations/%d', annotation_id)
249
+ payload = {
250
+ time: time,
251
+ timeEnd: time_end,
252
+ isRegion: region,
253
+ text: text,
254
+ tags: tags
255
+ }
256
+ payload.reject!{ |_k, v| v.nil? }
257
+
258
+ put(endpoint, payload.to_json)
259
+ end
27
260
 
28
261
  # Delete Annotation By Id
262
+ #
263
+ # Deletes the annotation that matches the specified id.
264
+ #
29
265
  # http://docs.grafana.org/http_api/annotations/#delete-annotation-by-id
30
266
  # DELETE /api/annotation/:id
31
- def delete_annotation( params ); end
267
+ #
268
+ # @param [Integer] annotation_id
269
+ #
270
+ # @example
271
+ # delete_annotation( 1 )
272
+ #
273
+ # @return [Hash]
274
+ #
275
+ def delete_annotation( annotation_id )
276
+
277
+ raise ArgumentError.new(format('wrong type. user \'annotation_id\' must be an Integer, given \'%s\'', annotation_id.class.to_s)) unless( annotation_id.is_a?(Integer) )
278
+ raise ArgumentError.new('missing \'annotation_id\'') if( annotation_id.size.zero? )
279
+ raise ArgumentError.new('\'annotation_id\' can not be 0') if( annotation_id.zero? )
280
+
281
+ endpoint = format( '/api/annotation/%d', annotation_id )
282
+
283
+ delete(endpoint)
284
+ end
32
285
 
33
286
  # Delete Annotation By RegionId
287
+ #
288
+ # Deletes the annotation that matches the specified region id.
289
+ # A region is an annotation that covers a timerange and has a start and end time.
290
+ # In the Grafana database, this is a stored as two annotations connected by a region id.
291
+ #
34
292
  # http://docs.grafana.org/http_api/annotations/#delete-annotation-by-regionid
35
293
  # DELETE /api/annotation/region/:id
36
- def delete_annotation_by_region( params ); end
294
+ #
295
+ # @param [Integer] region_id
296
+ #
297
+ # @example
298
+ # delete_annotation_by_region( 1 )
299
+ #
300
+ # @return [Hash]
301
+ #
302
+ def delete_annotation_by_region( region_id )
303
+
304
+ raise ArgumentError.new(format('wrong type. user \'region_id\' must be an Integer, given \'%s\'', region_id.class.to_s)) unless( region_id.is_a?(Integer) )
305
+ raise ArgumentError.new('missing \'region_id\'') if( region_id.size.zero? )
306
+ # raise ArgumentError.new('\'region_id\' can not be 0') if( region_id.zero? )
307
+
308
+ endpoint = format( '/api/annotation/region/%d', region_id )
309
+
310
+ delete(endpoint)
311
+ end
37
312
 
38
313
  end
39
314
 
@@ -0,0 +1,150 @@
1
+
2
+ module Grafana
3
+
4
+ # abstract base class for authentication API
5
+ #
6
+ # https://grafana.com/docs/grafana/latest/http_api/auth/
7
+ #
8
+ # Token
9
+ # - Currently you can authenticate via an API Token or via a Session cookie (acquired using regular login or oauth).
10
+ #
11
+ # Basic Auth
12
+ # - If basic auth is enabled (it is enabled by default) you can authenticate your HTTP request via standard
13
+ # basic auth. Basic auth will also authenticate LDAP users.
14
+ #
15
+ module Auth
16
+
17
+ # Auth HTTP resources / actions
18
+ # Api Keys
19
+ #
20
+ # GET /api/auth/keys
21
+ def api_keys
22
+
23
+ endpoint = '/api/auth/keys'
24
+
25
+ @logger.debug("Attempting to get all existing api keys (GET #{endpoint})") if @debug
26
+
27
+ get( endpoint )
28
+ end
29
+
30
+
31
+ def api_key( api_id )
32
+
33
+ if( api_id.is_a?(String) && api_id.is_a?(Integer) )
34
+ raise ArgumentError.new(format('wrong type. API token \'api_id\' must be an String (for an API name) or an Integer (for an API Id), given \'%s\'', api_id.class.to_s))
35
+ end
36
+ raise ArgumentError.new('missing \'api_id\'') if( api_id.size.zero? )
37
+
38
+ if(api_id.is_a?(String))
39
+ keys = api_keys
40
+ keys = JSON.parse(keys) if(keys.is_a?(String))
41
+
42
+ # logger.debug(keys)
43
+
44
+ status = keys.dig('status')
45
+ return keys if( status != 200 )
46
+
47
+ u = keys.dig('message').detect { |v| v['id'] == api_id || v['name'] == api_id }
48
+
49
+ # logger.debug(u)
50
+
51
+ return { 'status' => 404, 'message' => format( 'No API token \'%s\' found', api_id ) } if( u.nil? )
52
+
53
+ # api_id = u.dig('id') unless(u.nil?)
54
+ end
55
+
56
+ { 'status' => 200, 'message' => u }
57
+
58
+ end
59
+
60
+
61
+ # Create API Key
62
+ #
63
+ # POST /api/auth/keys
64
+ # https://grafana.com/docs/grafana/latest/http_api/auth/#create-api-key
65
+ #
66
+ # @param [Hash] params
67
+ # @option params [String] name The key name - (required)
68
+ # @option params [String] role Sets the access level/Grafana Role for the key. Can be one of the following values: Viewer, Editor or Admin. - (required)
69
+ # @option params [Integer] seconds_to_live Sets the key expiration in seconds.
70
+ # It is optional. If it is a positive number an expiration date for the key is set.
71
+ # If it is null, zero or is omitted completely (unless api_key_max_seconds_to_live configuration option is set) the key will never expire.
72
+ #
73
+ #
74
+ # @return [Hash]
75
+ #
76
+ # @example:
77
+ #
78
+ def create_api_key( params )
79
+
80
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
81
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
82
+
83
+ name = validate( params, required: true, var: 'name' )
84
+ role = validate( params, required: true, var: 'role' )
85
+ seconds_to_live = validate( params, required: false, var: 'seconds_to_live', type: Integer )
86
+
87
+ valid_roles = %w[Viewer Editor Admin]
88
+
89
+ # https://stackoverflow.com/questions/9333952/case-insensitive-arrayinclude?answertab=votes#tab-top
90
+ # Do this once, or each time the array changes
91
+ downcased = Set.new valid_roles.map(&:downcase)
92
+ unless( downcased.include?( role.downcase ) )
93
+ return {
94
+ 'status' => 404,
95
+ 'login_or_email' => login_or_email,
96
+ 'role' => role,
97
+ 'message' => format( 'wrong role. Role must be one of %s, given \'%s\'', valid_roles.join(', '), role )
98
+ }
99
+ end
100
+
101
+ seconds_to_live = 86_400 if seconds_to_live.nil?
102
+
103
+ endpoint = '/api/auth/keys'
104
+
105
+ data = {
106
+ name: name,
107
+ role: role,
108
+ secondsToLive: seconds_to_live
109
+ }
110
+
111
+ data.reject!{ |_k, v| v.nil? }
112
+
113
+ payload = data.deep_string_keys
114
+ # payload = existing_ds.merge(payload).deep_symbolize_keys
115
+
116
+ @logger.debug("create API token (POST #{endpoint})") if @debug
117
+
118
+ post(endpoint, payload.to_json)
119
+
120
+ end
121
+
122
+ # Delete API Key
123
+ #
124
+ # DELETE /api/auth/keys/:id
125
+ def delete_api_key( key_id )
126
+
127
+ if( key_id.is_a?(String) && key_id.is_a?(Integer) )
128
+ raise ArgumentError.new(format('wrong type. \'key_id\' must be an String (for an API Key name) or an Integer (for an API Key Id), given \'%s\'', key_id.class.to_s))
129
+ end
130
+ raise ArgumentError.new('missing \'key_id\'') if( key_id.size.zero? )
131
+
132
+ if(key_id.is_a?(String))
133
+ data = api_keys.select { |_k,v| v['name'] == key_id }
134
+ key_id = data.keys.first if( data )
135
+ end
136
+
137
+ return { 'status' => 404, 'message' => format( 'No API key \'%s\' found', key_id) } if( key_id.nil? )
138
+
139
+ raise format('API Key can not be 0') if( key_id.zero? )
140
+
141
+ endpoint = format('/api/auth/keys/%d', key_id)
142
+ logger.debug("Deleting API key #{key_id} (DELETE #{endpoint})") if @debug
143
+
144
+ delete(endpoint)
145
+
146
+ end
147
+
148
+ end
149
+
150
+ end