#!/usr/bin/env python import requests from ansible.module_utils.basic import AnsibleModule DOCUMENTATION = r''' --- module: influx2_token short_description: generate token via influxdb2 api description: generate token via influxdb2 api notes: - just works with influxdb version 2 - needs token to authenticate against API (use `influx auth list --user my-user --hide-headers | cut -f 3` - tokens may not be removed - permissions can not be updated. a new token is created and the old one is not removed. options: base: description: URL for path, e.g. `https://localhost:8086` type: str required: True org: description: influxdb2 organisation type: str required: True auth_token: description: influxdb2 authentication token type: str required: True key: description: some key used to identify token. This is put into the tokens description type: str required: True description: description: textual description for token. key gets appended type: str required: False permissions: description: list of permissions, each dict(action, resource) type: list 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_token: base: "http://localhost:8086" org: "my-org" auth_token: "{{influx_token_fetch.stdout_lines[0]}}" key: "foo123" description: "token for foo key" permissions: - action: "write" resource: type: "buckets" register: ed-influx-token - debug: msg="Token: {{ed-influx-token.token}}" ''' 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() update = None for i in ra.json()["authorizations"]: if self.marker not in i["description"] \ or i["orgID"] != self.org_id: continue if self._match_perms(self.perms, i["permissions"]): self.result_token = i if self.description == i["description"]: return False # everything matches -> no change needed else: 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, "description": self.description, "permissions": self.perms }) return True def run(self): if not self.f: self.check() self.f() def _match_perms(self, pa, pb): def g(match, lst): for idx, i in enumerate(lst): if i['action'] != match['action']: continue for k, v in match['resource'].items(): if k not in i['resource'] or i['resource'][k] != v: continue else: # first best match return idx else: raise ValueError b = [b.copy() for b in pb] for i in pa: try: b.pop(g(i, b)) 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, choices=[ "authorizations", "buckets", "dashboards", "orgs", "sources", "tasks", "telegrafs", "users", "variables", "scrapers", "secrets", "labels", "views", "documents", "notificationRules", "notificationEndpoints", "checks", "dbrp", "flows", "annotations", "functions"]), ), 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": 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)