Skip to content
This repository was archived by the owner on Jan 24, 2024. It is now read-only.

Commit d9de7e8

Browse files
Rémi REYrrey
authored andcommitted
Use the ansible url helpers
1 parent fa62daf commit d9de7e8

File tree

3 files changed

+164
-119
lines changed

3 files changed

+164
-119
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2017 Rémi REY
3+
Copyright (c) 2017-2018 Rémi REY
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

library/grafana_annotations.py

Lines changed: 98 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/python
22
# -*- coding: utf-8 -*-
33

4-
from base64 import b64encode
54
import urllib
65
import time
76
import json
8-
try:
9-
import httplib
10-
except ImportError:
11-
# Python 3
12-
import http.client as httplib
137

14-
API_URI = "/api/annotations"
8+
from ansible.module_utils.urls import fetch_url, basic_auth_header, url_argument_spec
9+
from ansible.module_utils.basic import *
1510

1611
DOCUMENTATION = '''
1712
---
@@ -88,23 +83,6 @@
8883
'''
8984

9085

91-
def build_search_uri(uri, annotation):
92-
"""Return the search uri based on the annotation"""
93-
params = []
94-
tags = annotation.get("tags", None)
95-
if tags:
96-
for tag in tags:
97-
params.append("tags=%s" % urllib.quote_plus(tag))
98-
if annotation.get('time', None):
99-
params.append("from=%s" % annotation.get('time'))
100-
if annotation.get('timeEnd', None):
101-
params.append("to=%s" % annotation.get('timeEnd'))
102-
else:
103-
params.append("to=%s" % (int(time.time()) * 1000))
104-
105-
if params:
106-
uri = "%s?%s" % (uri, '&'.join(params))
107-
return uri
10886

10987

11088
def default_filter(annos, annotation):
@@ -139,102 +117,135 @@ def region_filter(annos, annotation):
139117

140118
def filter_annotations(annos, annotation):
141119
"""Filter the annotations that does not match `annotation`"""
142-
if annotation.get("isRegion", False) is True:
120+
annotation = annotation.as_dict()
121+
if annotation.get("isRegion", False):
143122
return region_filter(annos, annotation)
144123
return default_filter(annos, annotation)
145124

146125

147126
class GrafanaManager(object):
148127
"""Manage communication with grafana HTTP API"""
149-
def __init__(self, addr, user=None, passwd=None, token=None, secure=False):
150-
self.addr = addr
151-
self.headers = {"Content-Type": "application-json",
152-
"Accept": "application/json"}
153-
self.secure = secure
154-
if user and passwd:
155-
cred = "%s:%s" % (user, passwd)
156-
authorization = "Basic %s" % b64encode(cred).decode('ascii')
157-
if token:
128+
129+
def __init__(self, module, url, url_username, url_password, token):
130+
self.module = module
131+
self.url = url
132+
self.headers = {"Content-Type": "application-json", "Accept": "application/json"}
133+
if url_username and url_password:
134+
authorization = basic_auth_header(url_username, url_password)
135+
elif token:
158136
authorization = "Bearer %s" % token
159137
self.headers["Authorization"] = authorization
160138

161-
def query(self, method, uri, data=None):
162-
http = httplib.HTTPConnection(self.addr)
163-
if self.secure:
164-
http = httplib.HTTPSConnection(self.addr)
165-
http.request(method, uri, data, headers=self.headers)
166-
response = http.getresponse()
167-
return response.status, json.loads(response.read())
139+
140+
def build_search_uri(self, annotation):
141+
"""Return the search url based on the annotation"""
142+
params = []
143+
annotation = annotation.as_dict()
144+
tags = annotation.get("tags", None)
145+
if tags:
146+
for tag in tags:
147+
params.append("tags=%s" % urllib.quote_plus(tag))
148+
if annotation.get('time', None):
149+
params.append("from=%s" % annotation.get('time'))
150+
if annotation.get('timeEnd', None):
151+
params.append("to=%s" % annotation.get('timeEnd'))
152+
else:
153+
params.append("to=%s" % (int(time.time()) * 1000))
154+
155+
if params:
156+
uri = "%s?%s" % (self.url, '&'.join(params))
157+
return uri
158+
168159

169160
def get_annotation(self, annotation):
170161
"""Search for the annotation in grafana"""
171-
uri = build_search_uri(API_URI, annotation)
172-
status, annos = self.query("GET", uri)
173-
if status != 200:
174-
raise Exception("Grafana answered with HTTP %d" % status)
162+
url = self.build_search_uri(annotation)
163+
resp, info = fetch_url(self.module, url, data=annotation.json, headers=self.headers, method="GET")
164+
165+
status_code = info["status"]
166+
if status_code != 200:
167+
raise Exception("Grafana answered with HTTP %d" % status_code)
168+
169+
annos = json.loads(resp.read())
175170
return filter_annotations(annos, annotation)
176171

177-
def create_annotation(self, annotation):
178-
"""Submit an annotation to grafana"""
179-
status, data = self.query("POST", API_URI, json.dumps(annotation))
180-
if status != 200:
181-
raise Exception("Grafana answered with HTTP %d" % response.status)
182-
return data
172+
173+
def send_annotation(self, annotation):
174+
resp, info = fetch_url(self.module, self.url, data=annotation.json, headers=self.headers, method="POST")
175+
176+
status_code = info["status"]
177+
if not 200 <= status_code <= 299:
178+
raise Exception("Grafana answered with HTTP %d" % status_code)
179+
return resp.read()
180+
181+
182+
# {{{ Annotation object
183+
184+
class Annotation(object):
185+
186+
def __init__(self, text, tags, tstamp=None, end_tstamp=None):
187+
self.text = text
188+
self.tags = tags
189+
self._set_time(tstamp)
190+
self.isRegion = False
191+
if end_tstamp:
192+
self.timeEnd = int(end_tstamp) * 1000
193+
self.isRegion = True
194+
195+
def _set_time(self, tstamp=None, end_tstamp=None):
196+
if tstamp:
197+
self.time = int(tstamp) * 1000
198+
else:
199+
self.time = int(time.time()) * 1000
200+
201+
def as_dict(self):
202+
return self.__dict__
203+
204+
@property
205+
def json(self):
206+
return json.dumps(self.__dict__)
207+
208+
# }}}
183209

184210

185211
def main():
212+
base_arg_spec = url_argument_spec()
213+
base_arg_spec.update(
214+
token=dict(required=False, default=None, no_log=True),
215+
tstamp=dict(required=False, default=None),
216+
end_tstamp=dict(required=False, default=None, type='int'),
217+
tags=dict(required=False, default=[], type='list'),
218+
text=dict(required=True, type='str'),
219+
)
186220
module = AnsibleModule(
187-
argument_spec={
188-
'addr': dict(required=True),
189-
'user': dict(required=False, default=None),
190-
'passwd': dict(required=False, default=None, no_log=True),
191-
'token': dict(required=False, default=None, no_log=True),
192-
'time': dict(required=False, default=None),
193-
'timeEnd': dict(required=False, default=None, type=int),
194-
'tags': dict(required=False, default=[], type=list),
195-
'text': dict(required=True, type=str),
196-
'secure': dict(required=False, default=False, type=bool),
197-
},
198-
supports_check_mode=False
221+
argument_spec=base_arg_spec,
222+
supports_check_mode=False,
223+
mutually_exclusive=[['url_username', 'token']]
199224
)
200225

201-
addr = module.params['addr']
202-
user = module.params['user']
203-
passwd = module.params['passwd']
226+
url = module.params['url']
227+
url_username = module.params['url_username']
228+
url_password = module.params['url_password']
204229
token = module.params['token']
205-
_time = module.params['time']
206-
time_end = module.params['timeEnd']
230+
tstamp = module.params['tstamp']
231+
end_tstamp = module.params['end_tstamp']
207232
tags = ["ansible"] + module.params['tags']
208233
text = module.params['text']
209-
secure = module.params['secure']
210-
211-
if (not user and not passwd) and not token:
212-
module.fail_json(msg="Authentication method must be provided (user/passwd or token)")
213234

214-
grafana = GrafanaManager(addr, user=user, passwd=passwd, token=token, secure=secure)
235+
annotation = Annotation(text, tags, tstamp=tstamp, end_tstamp=end_tstamp)
215236

216-
if not _time:
217-
_time = int(time.time()) * 1000
218-
else:
219-
_time = int(_time) * 1000
237+
grafana = GrafanaManager(module, url, url_username, url_password, token)
220238

221-
annotation = {"time": _time, "tags": tags, "text": text}
222-
223-
if time_end:
224-
annotation['timeEnd'] = time_end * 1000
225-
annotation['isRegion'] = True
226239

227240
changed = False
228241
try:
229242
annotations = grafana.get_annotation(annotation)
230243
if not annotations:
231-
annotation = grafana.create_annotation(annotation)
244+
annotation = grafana.send_annotation(annotation)
232245
annotations = [annotation]
233246
changed = True
234247
except Exception as err:
235-
module.fail_json(msg=str(err), annotation=annotation)
248+
module.fail_json(msg=str(err), annotation=annotation.json)
236249
module.exit_json(annotations=annotations, changed=changed)
237250

238-
239-
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
240251
main()

test.yml

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,113 @@
11
- hosts: localhost
22
connection: local
3+
gather_facts: no
4+
vars:
5+
grafana_login: admin
6+
grafana_password: admin
7+
grafana_url: http://127.0.0.1:3000/api/annotations
38
tasks:
9+
410
- name: Create a global annotation without time information (login/passwd)
511
grafana_annotations:
6-
addr: "127.0.0.1:3000"
7-
user: "admin"
8-
passwd: "admin"
12+
url: "{{ grafana_url }}"
13+
url_username: "{{ grafana_login }}"
14+
url_password: "{{ grafana_password }}"
915
text: "This is an annotation with automatic time value"
1016
register: anno
17+
tags:
18+
- base
1119

1220
- name: Create a global annotation without time information (token)
1321
grafana_annotations:
14-
addr: "127.0.0.1:3000"
22+
url: "{{ grafana_url }}"
1523
token: "{{ lookup('env','GRAFANA_API_TOKEN') }}"
1624
text: "This is an annotation with automatic time value"
1725
register: anno
26+
tags:
27+
- token
1828

1929
- debug:
2030
var: anno
31+
tags:
32+
- token
2133

22-
- shell: date +%s
23-
register: curdate
34+
- set_fact:
35+
curdate: "{{ '%s' | strftime() | int}}"
36+
tags:
37+
- base
2438

2539
- name: Create a global annotation with time annotation
2640
grafana_annotations:
27-
addr: "127.0.0.1:3000"
28-
user: "admin"
29-
passwd: "admin"
30-
time: "{{ curdate.stdout }}"
31-
text: "This is an annotation explicitly set at 1514822276"
41+
url: "{{ grafana_url }}"
42+
url_username: "{{ grafana_login }}"
43+
url_password: "{{ grafana_password }}"
44+
tstamp: "{{ curdate }}"
45+
text: "This is an annotation explicitly set at {{ curdate }}"
46+
tags:
47+
- test-tag
3248
register: anno
49+
tags:
50+
- base
3351

3452
- debug:
3553
var: anno
54+
tags:
55+
- base
3656

3757
- name: test idempotence
3858
grafana_annotations:
39-
addr: "127.0.0.1:3000"
40-
user: "admin"
41-
passwd: "admin"
42-
time: "{{ curdate.stdout }}"
43-
text: "This is an annotation explicitly set at 1514822276"
59+
url: "{{ grafana_url }}"
60+
url_username: "{{ grafana_login }}"
61+
url_password: "{{ grafana_password }}"
62+
tstamp: "{{ curdate }}"
63+
text: "This is an annotation explicitly set at {{ curdate }}"
64+
tags:
65+
- test-tag
4466
register: anno
67+
tags:
68+
- base
4569

4670
- assert:
4771
that: not anno.changed
72+
tags:
73+
- base
4874

49-
- shell: date +%s
50-
register: curdate
75+
- set_fact:
76+
start_date: "{{ '%s' | strftime() | int - 3600 }}"
77+
end_date: "{{ '%s' | strftime() | int}}"
78+
tags:
79+
- region
5180

52-
- shell: expr $(date +%s) - 3600
53-
register: start_date
54-
5581
- name: Create a global region annotation
5682
grafana_annotations:
57-
addr: "127.0.0.1:3000"
58-
user: "admin"
59-
passwd: "admin"
60-
time: "{{ start_date.stdout }}"
61-
timeEnd: "{{ curdate.stdout }}"
83+
url: "{{ grafana_url }}"
84+
url_username: "{{ grafana_login }}"
85+
url_password: "{{ grafana_password }}"
86+
tstamp: "{{ start_date }}"
87+
end_tstamp: "{{ end_date }}"
6288
text: "This is a global region annotation"
6389
register: anno
90+
tags:
91+
- region
6492

6593
- debug:
6694
var: anno
95+
tags:
96+
- region
6797

6898
- name: Test idempotence on region annotation
6999
grafana_annotations:
70-
addr: "127.0.0.1:3000"
71-
user: "admin"
72-
passwd: "admin"
73-
time: "{{ start_date.stdout }}"
74-
timeEnd: "{{ curdate.stdout }}"
100+
url: "{{ grafana_url }}"
101+
url_username: "{{ grafana_login }}"
102+
url_password: "{{ grafana_password }}"
103+
tstamp: "{{ start_date }}"
104+
end_tstamp: "{{ end_date }}"
75105
text: "This is a global region annotation"
76106
register: anno
107+
tags:
108+
- region
77109

78110
- assert:
79111
that: not anno.changed
112+
tags:
113+
- region

0 commit comments

Comments
 (0)