diff --git a/roles/cobald/library/influx_dashboard.py b/roles/cobald/library/influx_dashboard.py new file mode 100755 index 0000000..ef5a1cc --- /dev/null +++ b/roles/cobald/library/influx_dashboard.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +import json +import requests +from ansible.module_utils.basic import AnsibleModule + +DOCUMENTATION = r''' +--- +module: influx2_dashboard +short_description: create dashboard in influxdb2 +description: create dashboard in influxdb2 +notes: + - just works with influxdb version 2 + - does not create dashboard description + - does not update dashboards + - just creates a dashboard if it does not exist. + +options: + base: + description: URL for path, e.g. `https://localhost:8086` + type: str + required: True + org: + description: influxdb2 organisation + type: str + required: True + token: + description: influxdb2 authentication token + type: str + required: True + data: + description: exported dashboard json file + type: json + required: True + force: + description: force creation even if dashboard already exists + (adds a new one) + type: bool + required: False + default: False +author: + - Thorsten M. (@thoto) +''' + +EXAMPLES = r''' +- name: "fetch auth token" + raw: influx auth list --user my-user --hide-headers | cut -f 3 + register: influx_token_fetch + delegate_to: ed-influxdb-2 + +- name: "create dashboard" + influx_dashboard: + base: "http://localhost:8086" + org: "my-org" + token: "{{influx_token_fetch.stdout_lines[0]}}" + data: "{{lookup('file', 'influxdb-dashboard-cobald.json')}}" +''' + + +def parse_dashboard_template(data): + j = json.loads(data) + + return { + 'name': j["content"]["data"]["attributes"]["name"], + 'cells': {i["relationships"]["view"]["data"]["id"]: i["attributes"] + for i in j["content"]["included"] if i["type"] == "cell"}, + 'views': {i["id"]: i["attributes"] + for i in j["content"]["included"] if i["type"] == "view"}, + } + + +def get_auth(base, org_name, token): + h = {"Authorization": "Token {token}".format(token=token)} + + rm = requests.get("{base}/api/v2/me".format(base=base), headers=h) + rm.raise_for_status() + # me_name = rm.json()["name"] + me = rm.json()["id"] + + ro = requests.get("{base}/api/v2/orgs".format(base=base), headers=h, + json={"userID": me}) + ro.raise_for_status() + org_id = [o["id"] for o in ro.json()["orgs"] if o["name"] == org_name] + + return {"org_id": org_id[0], "org_name": org_name, "uid": me, "h": h} + + +def check(base, auth, dashboard): + h = auth["h"] + rd = requests.get("{base}/api/v2/dashboards".format(base=base), headers=h) + rd.raise_for_status() + x = [i for i in rd.json()["dashboards"] + if i["name"] == dashboard["name"] and i["orgID"] == auth["org_id"]] + return len(x) == 0, x + + +def create(base, auth, dashboard): + h = auth["h"] + + # create dashboard + rd = requests.post("{base}/api/v2/dashboards".format(base=base), headers=h, + json={"orgID": auth["org_id"], + "name": dashboard["name"]}) + rd.raise_for_status() + dash_id = rd.json()["id"] + + for k, v in dashboard["cells"].items(): + # create cells in dashboard + rc = requests.post( + "{base}/api/v2/dashboards/{dash_id}/cells".format( + base=base, dash_id=dash_id), + headers=h, json=v) + rc.raise_for_status() + cell_id = rc.json()["id"] + + # create view of cell in dashboard + rv = requests.patch( + "{base}/api/v2/dashboards/{dash_id}/cells/{cell_id}/view".format( + base=base, dash_id=dash_id, cell_id=cell_id), + headers=h, json=dashboard["views"][k]) + rv.raise_for_status() + + +if __name__ == "__main__": + result = dict(changed=False, message="") + module = AnsibleModule( + argument_spec=dict( + base=dict(type="str", required=True), + org=dict(type="str", required=True), + token=dict(type="str", required=True), + data=dict(type="json", required=True), + force=dict(type="bool", default=False), + ), + supports_check_mode=True + ) + dashboard = parse_dashboard_template(module.params["data"]) + auth = get_auth(module.params["base"], module.params["org"], + module.params["token"]) + changed, x = check(module.params["base"], auth, dashboard) + + result['changed'] = changed + + if module.check_mode: + module.exit_json(**result) + + if changed or module.params["force"]: + create(module.params["base"], auth, dashboard) + module.exit_json(**result) diff --git a/roles/cobald/library/influx_token.py b/roles/cobald/library/influx_token.py new file mode 100755 index 0000000..9985a06 --- /dev/null +++ b/roles/cobald/library/influx_token.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python +import requests +from ansible.module_utils.basic import AnsibleModule + +DOCUMENTATION = r''' +--- +module: influx2_dashboard +short_description: create dashboard in influxdb2 +description: create dashboard in influxdb2 +notes: + - just works with influxdb version 2 + - does not create dashboard description + - does not update dashboards + - just creates a dashboard if it does not exist. + +options: + base: + description: URL for path, e.g. `https://localhost:8086` + type: str + required: True + org: + description: influxdb2 organisation + type: str + required: True + token: + description: influxdb2 authentication token + type: str + required: True + data: + description: exported dashboard json file + type: json + required: True + force: + description: force creation even if dashboard already exists + (adds a new one) + type: bool + required: False + default: False +author: + - Thorsten M. (@thoto) +''' + +EXAMPLES = r''' +- name: "fetch auth token" + raw: influx auth list --user my-user --hide-headers | cut -f 3 + register: influx_token_fetch + delegate_to: ed-influxdb-2 + +- name: "create dashboard" + influx_dashboard: + base: "http://localhost:8086" + org: "my-org" + token: "{{influx_token_fetch.stdout_lines[0]}}" + data: "{{lookup('file', 'influxdb-dashboard-cobald.json')}}" +''' + + +def get_org_id(base, org_name, h): + ro = requests.get("{base}/api/v2/orgs".format(base=base), headers=h, + json={"org": org_name}) + ro.raise_for_status() + org_id = [o["id"] for o in ro.json()["orgs"] if o["name"] == org_name] + return org_id[0] + + +def marker(key): + return "__ANSIBLE_TOKEN_{key}__".format(key=key) + + +def _filter_perms(perms): + for r in perms: + r["resource"] = {k: v for k, v in r["resource"].items() if v} + return perms + + +class Token: + def __init__(self, base, h, data): + self.base = base + self.h = h + self.marker = marker(data["key"]) + + self.org_id = data["org_id"] + self.perms = _filter_perms(data["perms"]) + self.description = data["description"]+" "+self.marker + + self.result_token = None + self.f = None + + def check(self): + ra = requests.get("{base}/api/v2/authorizations".format( + base=self.base), + params={"orgID": self.org_id}, + headers=self.h) + ra.raise_for_status() + x = [i for i in ra.json()["authorizations"] + if self.marker in i["description"] + and i["orgID"] == self.org_id] + + update = None + for i in x: # FIXME: one loop + if self._match_perms(self.perms, i["permissions"]): + if self.description == i["description"]: + self.result_token = i + return False # everything matches -> no change needed + else: + self.result_token = i + update = {"auth_id": i["id"], + "description": self.description} + # TODO: may remove token because permissions do not match? + if update: + self.f = lambda: self._update(**update) + else: + self.result_token = None + self.f = lambda: self._create({ + "orgID": self.org_id, # "permissions": self.perms, + "description": self.description, + "permissions": self.perms + }) + return True + + def run(self): + if not self.f: + self.check() + ra = self.f() + + def _match_perms(self, pa, pb): + a = pa.copy() + b = pb.copy() + for i in a: + try: + b.remove(i) + except ValueError: + return False # permission i not present in b + + if b: # not empty + return False # some permissions in b not in a + return True + + def _update(self, auth_id, description): + ra = requests.patch( + "{base}/api/v2/authorizations/{auth_id}".format( + base=self.base, auth_id=auth_id), + headers=self.h, json={"description": description}) + ra.raise_for_status() + return ra + + def _create(self, data): + ra = requests.post("{base}/api/v2/authorizations".format( + base=self.base), + headers=self.h, json=data) + ra.raise_for_status() + self.result_token = ra.json() + return ra + + +if __name__ == "__main__": + result = dict(changed=False, message="") + module = AnsibleModule( + argument_spec=dict( + base=dict(type="str", required=True), + org=dict(type="str", required=True), + auth_token=dict(type="str", required=True), + key=dict(type="str", required=True), + description=dict(type="str", default=""), + permissions=dict(type="list", elements='dict', options=dict( + action=dict(type='str', choices=['read', 'write'], + required=True), + resource=dict(type='dict', options=dict( + id=dict(type='str'), + name=dict(type='str'), + org=dict(type='str'), + orgID=dict(type='str'), + type=dict(type='str', required=True), + ), required=True), + ), required=True), + force=dict(type="bool", default=False), + ), + supports_check_mode=True + ) + h = {"Authorization": "Token {token}".format( + token=module.params["auth_token"])} + t = Token(module.params["base"], h, { + "org_id": get_org_id(module.params["base"], module.params["org"], h), + "key": module.params["key"], + # "perms": [{"action": "write", "resource": { "type": "buckets"}}], + "perms": module.params["permissions"], + "description": module.params["description"]}) + + changed = t.check() + if t.result_token: + result['token'] = t.result_token["token"] + result['changed'] = changed + + if module.check_mode: + module.exit_json(**result) + + if changed or module.params["force"]: + t.run() + result['token'] = t.result_token["token"] + module.exit_json(**result)