diff --git a/README.md b/README.md index 6aceb64..0b02a81 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,39 @@ The collection is still at a very experimental stage and is growing bit by bit a It was created with the aim of refreshing my Ansible knowledge and getting in touch with Collections. Any hints for improvements are therefore welcome. +## Roles + +* [Role: repos](roles/repos/README.md) - Install the official InfluxDb repositories +* [Role: influxdb](roles/influxdb/README.md) - Install and configure InfluxDB + +## Example + +``` +- hosts: all + become: true + vars: + influxdb_influxdb_admin_token: 12345678abcdefg + influxdb_influxdb_buckets: + - name: foobar1 + state: absent + org: default + token: "{{ influxdb_influxdb_admin_token }}" + host: "{{ influxdb_influxdb_host }}" + retention: + type: 'expire' + everySeconds: '50000' + shardGroupDurationSeconds: '0' + + collections: + tbauriedel.influxdb + + roles: + - repos + - influxdb +``` + ## Supported systems | Distribution | Tested on | |--------------|-----------| | Ubuntu | 22.04 | | Centos | 9 Stream | - -## Roles - -* [Role: repos](roles/repos/README.md) (add repositories) -* [Role: influxdb](roles/influxdb/README.md) (install and configure influxdb) diff --git a/Vagrantfile b/Vagrantfile index 8ee6629..aa2dc2c 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -8,6 +8,7 @@ Vagrant.configure("2") do |config| ubuntu.vm.network "forwarded_port", guest: 8086, host: 8086 ubuntu.vm.provision "ansible" do |ansible| ansible.playbook = "vagrant-tests.yml" +# ansible.verbose = "vvv" end end @@ -17,6 +18,7 @@ Vagrant.configure("2") do |config| centos.vm.network "forwarded_port", guest: 8086, host: 8087 centos.vm.provision "ansible" do |ansible| ansible.playbook = "vagrant-tests.yml" +# ansible.verbose = "vvv" end end end \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..a0ab6ea --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,17 @@ +namespace: tbauriedel +name: influxdb +version: 0.0.0 +readme: README.md +authors: + - Tobias Bauriedel +description: > + This collection manages InfluxDB components. + Including the official repositories, installation and configuration. +license: + - Apache-2.0 +tags: + - collection + - influxdb + - metrics +dependencies: {} +repository: https://github.com/tbauriedel/ansible-collection-influxdb diff --git a/plugins/module_utils/utils.py b/plugins/module_utils/utils.py new file mode 100644 index 0000000..4459cc9 --- /dev/null +++ b/plugins/module_utils/utils.py @@ -0,0 +1,121 @@ +#!/usr/bin/python3 + +import json +import requests + +class InfluxApi(): + ''' + Common api requests to for InfluxDBv2 + ''' + def get_bucket_status(module): + ''' + Get state of single bucket of org from InfluxDB. Returns 'present' if found and 'absent' if not present. + ''' + headers = { + 'Authorization': 'Token ' + module.params['token'] + } + + url = module.params['host'] + '/api/v2/buckets?name=' + module.params['name'] + "&orgID=" + InfluxApi.get_orgID_by_name(module) + response = requests.get(url, headers=headers) + json_resp = json.loads(response.content) + + if "code" in json_resp: + if json_resp["code"] == "not found": + return "absent" + + for bucket in json_resp["buckets"]: + if bucket['name'] == module.params['name']: + return 'present' + else: + return 'absent' + + + def get_all_orgs(module): + ''' + Get all organizations from InfluxDB. Queries. + Returns JSON. + ''' + + headers = { + 'Authorization': 'Token ' + module.params['token'] + } + response = requests.get(module.params['host'] + '/api/v2/orgs', headers=headers) + + return json.loads(response.content) + + + def get_orgID_by_name(module): + ''' + Get organization ID by name. Returns ID + If no organization is found by name, 'not found' will be returned. + ''' + + orgs = InfluxApi.get_all_orgs(module) + + for org in orgs['orgs']: + if org['name'] == module.params['org']: + return org['id'] + + return "not found" + + + def create_bucket(module): + ''' + Create bucket + ''' + + headers = { + 'Authorization': 'Token ' + module.params['token'], + 'Content-type': 'application/json' + } + + url = module.params['host'] + '/api/v2/buckets' + payload = { + 'orgID': InfluxApi.get_orgID_by_name(module), + 'name': module.params['name'], + 'retentionRules': [ + { + 'type': module.params['retention']['type'], + 'everySeconds': int(module.params['retention']['everySeconds']), + 'shardGroupDurationSeconds': int(module.params['retention']['shardGroupDurationSeconds']) + } + ] + } + response = requests.post(url, headers=headers, data=json.dumps(payload)) + + return response.status_code, response.content + + + def get_bucketID_by_name(module): + ''' + Get bucket ID by name. Returns ID + If no bucket is found by name, 'not found' will be returned + ''' + + headers = { + 'Authorization': 'Token ' + module.params['token'] + } + + url = module.params['host'] + '/api/v2/buckets?name=' + module.params['name'] + "&orgID=" + InfluxApi.get_orgID_by_name(module) + response = requests.get(url, headers=headers) + json_resp = json.loads(response.content) + + for bucket in json_resp['buckets']: + return bucket['id'] + + return "not found" + + + def delete_bucket(module): + ''' + Delete bucket + ''' + + headers = { + 'Authorization': 'Token ' + module.params['token'] + } + + url = module.params['host'] + '/api/v2/buckets/' + InfluxApi.get_bucketID_by_name(module) + response = requests.delete(url, headers=headers) + + return response.status_code, response.content \ No newline at end of file diff --git a/plugins/modules/organize_bucket.py b/plugins/modules/organize_bucket.py new file mode 100644 index 0000000..19173e6 --- /dev/null +++ b/plugins/modules/organize_bucket.py @@ -0,0 +1,82 @@ +#!/usr/bin/python3 + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.tbauriedel.influxdb.plugins.module_utils.utils import ( + InfluxApi +) + +def run_module(): + # Define new Module with arguments + module = AnsibleModule( + argument_spec=dict( + name=dict(type=str, required=True), + state=dict(type=str, required=True), + token=dict(type=str, Required=True, no_log=True), + host=dict(type=str, Required=True), + org=dict(type=str, required=False), + retention=dict(type=dict, required=False) + ) + ) + + if module.params['state'] != 'absent' and module.params['state'] != 'present': + module.exit_json( + failed=True, + stderr="Invalid state provided. Use 'present' or 'absent'" + ) + + # Default result + result = dict( + changed=False, + failed=False, + rc="" + ) + + # Get state of current bucket + orgID = InfluxApi.get_orgID_by_name(module) + if orgID == "not found": + module.exit_json(dict( + failed=True, + stderr="No orgID found for given org name" + )) + + # Get state of bucket ('present' or 'absent') + bucketState = InfluxApi.get_bucket_status(module) + + # Create bucket if not 'present' but 'present' in configuration + if module.params['state'] == 'present' and bucketState == 'absent': + result['debug'] = "Create bucket" + + rc, content = InfluxApi.create_bucket(module) + result['rc'] = rc + if rc != 201: + module.exit_json( + failed=True, + stderr=content + ) + + result['changed'] = True + + # Delete bucket if 'present' but 'absent' in configuration + elif module.params['state'] == 'absent' and bucketState == 'present': + result['debug'] = "Delete bucket" + + rc, content = InfluxApi.delete_bucket(module) + result['rc'] = rc + if rc != 204: + module.exit_json( + failed=True, + stderr=content + ) + + result['changed'] = True + + else: + result['debug'] = "Keep state of bucket" + + module.exit_json(**result) + +def main(): + run_module() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d9c31ef --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# ansible collections +# - community.general \ No newline at end of file diff --git a/roles/influxdb/README.md b/roles/influxdb/README.md index 2233a93..84f55c5 100644 --- a/roles/influxdb/README.md +++ b/roles/influxdb/README.md @@ -10,7 +10,7 @@ At the moment the configuration is very basic. Over time, this role will be expa **Configuration:** * `influxdb_influxdb_bolt_path`: InfluxDB bolt-path * `influxdb_influxdb_engine_path`: InfluxDB engine-path -* `influxdb_influxdb_extra_config`: Extra configuration +* `influxdb_influxdb_extra_config`: Some extra configuration **Setup:** * `influxdb_influxdb_host`: HTTP address of InfluxDB (default: 'http://localhost:8086') @@ -19,6 +19,18 @@ At the moment the configuration is very basic. Over time, this role will be expa * `influxdb_influxdb_primary_org`: Primary organization (default: 'default') * `influxdb_influxdb_primary_bucket`: Primary bucket (default: 'default') * `influxdb_influxdb_retention`: Retention for primary bucket (default: '0') +* `influxdb_influxdb_admin_token`: Admin API token created in the setup (default: 'Random123ChangeMe!') + +**Buckets** +* `influxdb_influxdb_buckets`: List of buckets to manage + * `name`: Name of bucket. (watch [bucket naming restrictions](https://docs.influxdata.com/influxdb/v2/admin/buckets/create-bucket/?t=InfluxDB+API#bucket-naming-restrictions)) + * `state`: state of bucket ('present' or 'absent') + * `org`: InfluxDB organization name + * `token`: API token to manage the bucket + * `retention`: List of retention rules containing a single object with the following fields + * `type`: expire + * `everySeconds`: Number of seconds to retain data (0 means forever) + * `shardGroupDurationSeconds`: Number of seconds to retain shard groups (0 means forever) Defaults can be viewed in vars/defaults.yml diff --git a/roles/influxdb/defaults/main.yml b/roles/influxdb/defaults/main.yml index 5cfe486..2b8c8c7 100644 --- a/roles/influxdb/defaults/main.yml +++ b/roles/influxdb/defaults/main.yml @@ -14,4 +14,7 @@ influxdb_influxdb_primary_user: password: ChangeMe123! influxdb_influxdb_primary_org: default influxdb_influxdb_primary_bucket: default +influxdb_influxdb_admin_token: "Random123ChangeMe!" influxdb_influxdb_retention: 0 + +influxdb_influxdb_buckets: [] diff --git a/roles/influxdb/tasks/setup.yml b/roles/influxdb/tasks/setup.yml index 697d8ce..1ab7bc8 100644 --- a/roles/influxdb/tasks/setup.yml +++ b/roles/influxdb/tasks/setup.yml @@ -1,4 +1,9 @@ --- +- name: Ensure admin setup token is definded + ansible.builtin.fail: + msg: Variable influxdb_influxdb_admin_token is not defined in variables + when: influxdb_influxdb_admin_token == "" + - name: Ensure InfluxDB is set up ansible.builtin.command: influx setup \ --host "{{ influxdb_influxdb_host }}" \ @@ -7,6 +12,7 @@ --org "{{ influxdb_influxdb_primary_org }}" \ --bucket "{{ influxdb_influxdb_primary_bucket }}" \ --retention "{{ influxdb_influxdb_retention }}" \ + --token "{{ influxdb_influxdb_admin_token }}" \ --force register: _influx_setup failed_when: @@ -15,3 +21,15 @@ changed_when: _influx_setup.rc == 0 # TODO add handling for orgs, buckets, etc. +- name: Organize buckets + tbauriedel.influxdb.organize_bucket: + name: "{{ item.name }}" + state: "{{ item.state }}" + org: "{{ item.org }}" + token: "{{ item.token }}" + host: "{{ influxdb_influxdb_host }}" + retention: + type: "{{ item.retention.type }}" + everySeconds: "{{ item.retention.everySeconds }}" + shardGroupDurationSeconds: "{{ item.retention.shardGroupDurationSeconds }}" + loop: "{{ influxdb_influxdb_buckets }}" diff --git a/vagrant-tests.yml b/vagrant-tests.yml index 4bd54d5..f1673ba 100644 --- a/vagrant-tests.yml +++ b/vagrant-tests.yml @@ -1,6 +1,21 @@ --- - hosts: all become: true + vars: + influxdb_influxdb_admin_token: 12345678abcdefg + influxdb_influxdb_buckets: + - name: foobar1 + state: absent + org: default + token: "{{ influxdb_influxdb_admin_token }}" + host: "{{ influxdb_influxdb_host }}" + retention: + type: 'expire' + everySeconds: 50000 + shardGroupDurationSeconds: 0 + + collections: + tbauriedel.influxdb roles: - repos