|
1 | | -#!/usr/bin/env python |
| 1 | +#!/usr/bin/python |
2 | 2 | # -*- coding: utf-8 -*- |
3 | 3 |
|
4 | | -from base64 import b64encode |
5 | 4 | import urllib |
6 | 5 | import time |
7 | 6 | import json |
8 | | -try: |
9 | | - import httplib |
10 | | -except ImportError: |
11 | | - # Python 3 |
12 | | - import http.client as httplib |
13 | 7 |
|
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 * |
15 | 10 |
|
16 | 11 | DOCUMENTATION = ''' |
17 | 12 | --- |
|
88 | 83 | ''' |
89 | 84 |
|
90 | 85 |
|
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 |
108 | 86 |
|
109 | 87 |
|
110 | 88 | def default_filter(annos, annotation): |
@@ -139,102 +117,135 @@ def region_filter(annos, annotation): |
139 | 117 |
|
140 | 118 | def filter_annotations(annos, annotation): |
141 | 119 | """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): |
143 | 122 | return region_filter(annos, annotation) |
144 | 123 | return default_filter(annos, annotation) |
145 | 124 |
|
146 | 125 |
|
147 | 126 | class GrafanaManager(object): |
148 | 127 | """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: |
158 | 136 | authorization = "Bearer %s" % token |
159 | 137 | self.headers["Authorization"] = authorization |
160 | 138 |
|
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 | + |
168 | 159 |
|
169 | 160 | def get_annotation(self, annotation): |
170 | 161 | """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()) |
175 | 170 | return filter_annotations(annos, annotation) |
176 | 171 |
|
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 | +# }}} |
183 | 209 |
|
184 | 210 |
|
185 | 211 | 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 | + ) |
186 | 220 | 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']] |
199 | 224 | ) |
200 | 225 |
|
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'] |
204 | 229 | 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'] |
207 | 232 | tags = ["ansible"] + module.params['tags'] |
208 | 233 | 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)") |
213 | 234 |
|
214 | | - grafana = GrafanaManager(addr, user=user, passwd=passwd, token=token, secure=secure) |
| 235 | + annotation = Annotation(text, tags, tstamp=tstamp, end_tstamp=end_tstamp) |
215 | 236 |
|
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) |
220 | 238 |
|
221 | | - annotation = {"time": _time, "tags": tags, "text": text} |
222 | | - |
223 | | - if time_end: |
224 | | - annotation['timeEnd'] = time_end * 1000 |
225 | | - annotation['isRegion'] = True |
226 | 239 |
|
227 | 240 | changed = False |
228 | 241 | try: |
229 | 242 | annotations = grafana.get_annotation(annotation) |
230 | 243 | if not annotations: |
231 | | - annotation = grafana.create_annotation(annotation) |
| 244 | + annotation = grafana.send_annotation(annotation) |
232 | 245 | annotations = [annotation] |
233 | 246 | changed = True |
234 | 247 | except Exception as err: |
235 | | - module.fail_json(msg=str(err), annotation=annotation) |
| 248 | + module.fail_json(msg=str(err), annotation=annotation.json) |
236 | 249 | module.exit_json(annotations=annotations, changed=changed) |
237 | 250 |
|
238 | | - |
239 | | -#<<INCLUDE_ANSIBLE_MODULE_COMMON>> |
240 | 251 | main() |
0 commit comments