Compare commits

...

15 commits
v1.0.0 ... main

29 changed files with 1043 additions and 521 deletions

View file

@ -0,0 +1,18 @@
---
on: [push]
jobs:
lint:
runs-on: docker
container:
image: code.waks.be/nishiki/molecule:docker
steps:
- uses: actions/checkout@v3
- run: ansible-lint .
- run: yamllint .
molecule:
runs-on: docker
container:
image: code.waks.be/nishiki/molecule:docker
steps:
- uses: actions/checkout@v3
- run: molecule test

1
.gitignore vendored
View file

@ -1 +1,2 @@
.kitchen/* .kitchen/*
*.pyc

10
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,10 @@
---
image: nishiki/molecule:docker
before_script:
- molecule --version
molecule:
stage: test
script:
- molecule test

View file

@ -1,27 +0,0 @@
---
driver:
name: docker_cli
transport:
name: docker_cli
provisioner:
name: ansible_playbook
hosts: localhost
require_ansible_repo: false
require_ansible_omnibus: false
require_chef_for_busser: true
ansible_verbose: false
ansible_inventory: ./test/integration/inventory
platforms:
- name: debian-9
driver_config:
image: "nishiki/debian9:ansible-<%= ENV['ANSIBLE_VERSION'] ? ENV['ANSIBLE_VERSION'] : '2.7' %>"
command: /bin/systemd
volume:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
security_opt: seccomp=unconfined
suites:
- name: default

View file

@ -2,11 +2,11 @@
extends: default extends: default
ignore: | ignore: |
.kitchen/* .kitchen*
vendor/ vendor/
.forgejo/
rules: rules:
line-length: line-length:
max: 120 max: 120
level: warning level: warning
truthy: false

View file

@ -5,6 +5,40 @@ Which is based on [Keep A Changelog](http://keepachangelog.com/)
## [Unreleased] ## [Unreleased]
### Break
- upgrade to influxdb 2
### Changed
- test: use personal docker registry
## [2.0.0] 2021-08-18
### Break
- change user's privileges
### Added
- add retention policies
- add support for debian 11
- add support for debian 10 and python3
### Changed
- test: replace kitchen to molecule
- chore: use FQCN for module name
### Fixed
- fix no_log
### Removed
- remove support for debian 9
- the influxdb_api_password variable isn't set by default
## [1.0.0] 2019-04-12 ## [1.0.0] 2019-04-12
- first version - first version

View file

@ -1,8 +0,0 @@
source 'https://rubygems.org'
group :development do
gem 'kitchen-ansible'
gem 'kitchen-docker_cli'
gem 'rubocop', '0.50.0'
gem 'test-kitchen'
end

View file

@ -1,93 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
ast (2.4.0)
builder (3.2.3)
erubis (2.7.0)
ffi (1.10.0)
gssapi (1.2.0)
ffi (>= 1.0.1)
gyoku (1.3.1)
builder (>= 2.1.2)
httpclient (2.8.3)
kitchen-ansible (0.47.4)
mixlib-shellout (>= 2.3.2)
net-ssh (>= 3)
test-kitchen (>= 1.17.0)
kitchen-docker_cli (0.19.0)
test-kitchen (>= 1.3)
little-plugger (1.1.4)
logging (2.2.2)
little-plugger (~> 1.1)
multi_json (~> 1.10)
mixlib-install (3.11.11)
mixlib-shellout
mixlib-versioning
thor
mixlib-shellout (2.4.4)
mixlib-versioning (1.2.7)
multi_json (1.13.1)
net-scp (2.0.0)
net-ssh (>= 2.6.5, < 6.0.0)
net-ssh (5.2.0)
net-ssh-gateway (2.0.0)
net-ssh (>= 4.0.0)
nori (2.6.0)
parallel (1.17.0)
parser (2.6.2.1)
ast (~> 2.4.0)
powerpack (0.1.2)
rainbow (2.2.2)
rake
rake (12.3.2)
rubocop (0.50.0)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 2.2.2, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.10.0)
rubyntlm (0.6.2)
rubyzip (1.2.2)
test-kitchen (2.0.1)
mixlib-install (~> 3.6)
mixlib-shellout (>= 1.2, < 3.0)
net-scp (>= 1.1, < 3.0)
net-ssh (>= 2.9, < 6.0)
net-ssh-gateway (>= 1.2, < 3.0)
thor (~> 0.19)
winrm (~> 2.0)
winrm-elevated (~> 1.0)
winrm-fs (~> 1.1)
thor (0.20.3)
unicode-display_width (1.5.0)
winrm (2.3.1)
builder (>= 2.1.2)
erubis (~> 2.7)
gssapi (~> 1.2)
gyoku (~> 1.0)
httpclient (~> 2.2, >= 2.2.0.2)
logging (>= 1.6.1, < 3.0)
nori (~> 2.0)
rubyntlm (~> 0.6.0, >= 0.6.1)
winrm-elevated (1.1.1)
winrm (~> 2.0)
winrm-fs (~> 1.0)
winrm-fs (1.3.2)
erubis (~> 2.7)
logging (>= 1.6.1, < 3.0)
rubyzip (~> 1.1)
winrm (~> 2.0)
PLATFORMS
ruby
DEPENDENCIES
kitchen-ansible
kitchen-docker_cli
rubocop (= 0.50.0)
test-kitchen
BUNDLED WITH
1.16.0

100
README.md
View file

@ -1,48 +1,67 @@
# Ansible role: Influxdb # Ansible role: Influxdb
[![Version](https://img.shields.io/badge/latest_version-1.0.0-green.svg)](https://git.yaegashi.fr/nishiki/ansible-role-influxdb/releases) [![Version](https://img.shields.io/badge/latest_version-2.0.0-green.svg)](https://code.waks.be/nishiki/ansible-role-influxdb/releases)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://git.yaegashi.fr/nishiki/ansible-role-influxdb/src/branch/master/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://code.waks.be/nishiki/ansible-role-influxdb/src/branch/main/LICENSE)
[![Build](https://code.waks.be/nishiki/ansible-role-influxdb/actions/workflows/molecule.yml/badge.svg?branch=main)](https://code.waks.be/nishiki/ansible-role-influxdb/actions?workflow=molecule.yml)
Install and configure InfluxDB Install and configure InfluxDB 2
## Requirements ## Requirements
* Ansible >= 2.7 - Ansible >= 2.10
* Debian Stretch - Debian
- Bookworm
## Role variables ## Role variables
* `influxdb_databases` - array with the databases name - `influxdb_init_username` - user created during the influxdb setup
* `influxdb_users` - array with the users informations - `influxdb_init_org` - organization created during the influxdb setup
- `influxdb_init_bucket` - bucket created during the influxdb setup
- `influxdb_api_token` - token to manage influxdb
- `influxdb_orgs` - hash with organizations
``` ```yaml
- name: test myorg:
password: secret description: it's a test
admin: true
state: present state: present
``` ```
* `influxdb_privileges` - array with the privileges - `influxdb_buckets` - hash with the buckets
``` ```yaml
- user: test mybucket:
database: metric description: KFC
privilege: WRITE retention: 3600
org: neworg
state: present state: present
``` ```
* `influxdb_api_user` - set the api user if you have enabled http authentification - `influxdb_users` - hash with the users
* `influxdb_api_password` - set the api password if you have enabled http authentification
* `influxdb_api_port` - set the api port (default: `8086`)
* `influxdb_config` - hash with the influxdb configuration (see [influxdb documentation](https://docs.influxdata.com/influxdb/v1.7/administration/config/))
``` ```yaml
meta: myuser:
dir: /var/lib/influxdb/meta status: active
data: state: present
dir: /var/lib/influxdb/data ```
wal-dir: /var/lib/influxdb/wal
``` - `influxdb_authorizations` - array with the authorizations
```yaml
- user: myuser
description: write bucket
org: neworg
status: active
state: present
permissions:
- action: write
resource:
type: buckets
org: myorg
name: mybucket
- action: read
resource:
type: buckets
```
## How to use ## How to use
@ -54,25 +73,12 @@ Install and configure InfluxDB
## Development ## Development
### Test syntax with yamllint ### Test with molecule and docker
* install `python` and `python-pip` - install [docker](https://docs.docker.com/engine/installation/)
* install yamllint `pip install yamllint` - install `python3` and `python3-pip`
* run `yamllint .` - install molecule and dependencies `pip3 install molecule molecule-plugins[docker] ansible-lint pytest-testinfra yamllint`
- run `molecule test`
### Test syntax with ansible-lint
* install `python` and `python-pip`
* install yamllint `pip install ansible-lint`
* run `ansible-lint .`
### Tests with docker
* install [docker](https://docs.docker.com/engine/installation/)
* install ruby
* install bundler `gem install bundler`
* install dependencies `bundle install`
* run the tests `kitchen test`
## License ## License

View file

@ -1,15 +1,5 @@
--- ---
influxdb_databases: [] influxdb_orgs: {}
influxdb_users: [] influxdb_buckets: {}
influxdb_privileges: [] influxdb_users: {}
influxdb_api_user: user influxdb_authorizations: []
influxdb_api_password: password
influxdb_api_port: 8086
influxdb_default_config:
meta:
dir: /var/lib/influxdb/meta
data:
dir: /var/lib/influxdb/data
wal-dir: /var/lib/influxdb/wal
influxdb_config: {}
influxdb_full_config: '{{ influxdb_default_config|combine(influxdb_config) }}'

View file

@ -1,5 +0,0 @@
---
- name: restart influxdb
systemd:
name: influxdb
state: restarted

235
library/influxdb_auth.py Normal file
View file

@ -0,0 +1,235 @@
#!/usr/bin/python3
from ansible.module_utils.basic import AnsibleModule
import requests
import json
class InfluxdbAuth:
def __init__(self, api_url, api_token, org, user, permissions):
self.api_url = api_url
self.headers = {'Authorization': 'Token {}'.format(api_token)}
self.org = org
self.user = user
self.permissions = permissions
def exists(self):
url = '{}/api/v2/authorizations'.format(self.api_url)
r = requests.get(
url,
headers=self.headers,
params={'org': self.org, 'user': self.user}
)
if r.status_code == 404:
return False
elif r.status_code == 200:
data = json.loads(r.text)
for auth in data['authorizations']:
if self.check_permissions(auth['permissions']):
self.id = auth['id']
self.status = auth['status']
self.description = auth['description']
return True
return False
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def check_permissions(self, permissions):
for pa in self.permissions:
find = False
for pb in permissions:
if pa['action'] != pb['action']:
continue
if pa['resource']['type'] != pb['resource']['type']:
continue
if 'org' in pa['resource'] and 'org' in pb['resource']:
if pa['resource']['org'] != pb['resource']['org']:
continue
if 'name' in pa['resource'] and 'name' in pb['resource']:
if pa['resource']['name'] != pb['resource']['name']:
continue
find = True
break
if not find:
return False
return True
def transform_permissions(self):
permissions = list()
for perm in self.permissions:
org = None
if 'org' in perm['resource'] and 'orgID' not in perm['resource']:
org = perm['resource']['org']
perm['resource']['orgID'] = self.get_orgid(org)
if 'name' in perm['resource'] and 'id' not in perm['resource']:
if perm['resource']['type'] == 'buckets':
if org is None:
org = self.org
perm['resource']['id'] = self.get_bucketid(
perm['resource']['name'], org)
permissions.append(perm)
return permissions
def has_changed(self, status, description):
if self.status != status:
return True
if self.description != description:
return True
return False
def create(self, status, description):
url = '{}/api/v2/authorizations'.format(self.api_url)
r = requests.post(
url,
headers=self.headers,
json={
'userID': self.get_userid(self.user, self.org),
'orgID': self.get_orgid(self.org),
'status': status,
'description': description,
'permissions': self.transform_permissions()
}
)
if r.status_code != 201:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def delete(self):
url = '{}/api/v2/authorizations/{}'.format(self.api_url, self.id)
r = requests.delete(url, headers=self.headers)
if r.status_code != 204:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def update(self, status, description):
url = '{}/api/v2/authorizations/{}'.format(self.api_url, self.id)
r = requests.patch(
url,
headers=self.headers,
json={
'description': description,
'status': status
}
)
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def get_bucketid(self, bucket_name, org):
url = '{}/api/v2/buckets'.format(self.api_url)
r = requests.get(
url,
headers=self.headers,
params={'name': bucket_name, 'org': org}
)
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
data = json.loads(r.text)
for bucket in data['buckets']:
if bucket['name'] == bucket_name:
return bucket['id']
raise Exception('Influxdb', 'Don\'t get the bucketID')
def get_orgid(self, org_name):
url = '{}/api/v2/orgs'.format(self.api_url)
r = requests.get(url, headers=self.headers, params={'org': org_name})
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
data = json.loads(r.text)
for org in data['orgs']:
if org['name'] == org_name:
return org['id']
raise Exception('Influxdb', 'Don\'t get the orgID')
def get_userid(self, user_name, org):
url = '{}/api/v2/users'.format(self.api_url)
r = requests.get(
url,
headers=self.headers,
params={'name': user_name, 'org': org}
)
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
data = json.loads(r.text)
for user in data['users']:
if user['name'] == user_name:
return user['id']
raise Exception('Influxdb', 'Don\'t get the userID')
def main():
fields = {
'user': {'type': 'str', 'required': True},
'org': {'type': 'str', 'required': True},
'permissions': {'type': 'list', 'required': True},
'status': {'type': 'str',
'default': 'active',
'choice': ['active', 'inactive']},
'description': {'type': 'str', 'default': ''},
'api_url': {'type': 'str', 'default': 'http://127.0.0.1:8086'},
'api_token': {'type': 'str', 'required': True},
'state': {'type': 'str',
'default': 'present',
'choice': ['present', 'absent']},
}
module = AnsibleModule(argument_spec=fields)
auth = InfluxdbAuth(
module.params['api_url'],
module.params['api_token'],
module.params['org'],
module.params['user'],
module.params['permissions'],
)
changed = False
if auth.exists():
if module.params['state'] == 'absent':
auth.delete()
changed = True
elif auth.has_changed(module.params['status'],
module.params['description']):
auth.update(module.params['status'], module.params['description'])
changed = True
elif module.params['state'] == 'present':
auth.create(module.params['status'], module.params['description'])
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

163
library/influxdb_bucket.py Normal file
View file

@ -0,0 +1,163 @@
#!/usr/bin/python3
from ansible.module_utils.basic import AnsibleModule
import requests
import json
class InfluxdbBucket:
def __init__(self, api_url, api_token, name, org):
self.api_url = api_url
self.headers = {'Authorization': 'Token {}'.format(api_token)}
self.name = name
self.org = org
def exists(self):
url = '{}/api/v2/buckets'.format(self.api_url)
r = requests.get(
url,
headers=self.headers,
params={'name': self.name, 'org': self.org}
)
if r.status_code == 404:
return False
elif r.status_code == 200:
data = json.loads(r.text)
for bucket in data['buckets']:
if bucket['name'] == self.name:
self.id = bucket['id']
self.retention = bucket['retentionRules'][0]['everySeconds']
self.description = ''
if 'description' in bucket:
self.description = bucket['description']
return True
return False
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def has_changed(self, description, retention):
if self.description != description:
return True
if self.retention != retention:
return True
return False
def get_orgid(self):
url = '{}/api/v2/orgs'.format(self.api_url)
r = requests.get(url, headers=self.headers, params={'org': self.org})
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
data = json.loads(r.text)
for org in data['orgs']:
if org['name'] == self.org:
return org['id']
raise Exception('Influxdb', 'Don\'t get the orgID')
def create(self, description, retention):
url = '{}/api/v2/buckets'.format(self.api_url)
r = requests.post(
url,
headers=self.headers,
json={
'name': self.name,
'description': description,
'orgID': self.get_orgid(),
'retentionRules': [{
'everySeconds': retention,
'shardGroupDurationSeconds': 0,
'type': 'expire'
}]
}
)
if r.status_code != 201:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def delete(self):
url = '{}/api/v2/buckets/{}'.format(self.api_url, self.id)
r = requests.delete(url, headers=self.headers)
if r.status_code != 204:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def update(self, description, retention):
url = '{}/api/v2/buckets/{}'.format(self.api_url, self.id)
r = requests.patch(
url,
headers=self.headers,
json={
'name': self.name,
'description': description,
'retentionRules': [{
'everySeconds': retention,
'shardGroupDurationSeconds': 0,
'type': 'expire'
}]
}
)
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def main():
fields = {
'name': {'type': 'str', 'required': True},
'org': {'type': 'str', 'required': True},
'description': {'type': 'str', 'default': ''},
'retention': {'type': 'int', 'default': 0},
'api_url': {'type': 'str', 'default': 'http://127.0.0.1:8086'},
'api_token': {'type': 'str', 'required': True},
'state': {'type': 'str',
'default': 'present',
'choice': ['present', 'absent']},
}
module = AnsibleModule(argument_spec=fields)
bucket = InfluxdbBucket(
module.params['api_url'],
module.params['api_token'],
module.params['name'],
module.params['org'],
)
changed = False
if bucket.exists():
if module.params['state'] == 'absent':
bucket.delete()
changed = True
elif bucket.has_changed(module.params['description'],
module.params['retention']):
bucket.update(
module.params['description'],
module.params['retention']
)
changed = True
elif module.params['state'] == 'present':
bucket.create(module.params['description'], module.params['retention'])
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

112
library/influxdb_org.py Normal file
View file

@ -0,0 +1,112 @@
#!/usr/bin/python3
from ansible.module_utils.basic import AnsibleModule
import requests
import json
class InfluxdbOrg:
def __init__(self, api_url, api_token, name):
self.api_url = api_url
self.headers = {'Authorization': 'Token {}'.format(api_token)}
self.name = name
def exists(self):
url = '{}/api/v2/orgs'.format(self.api_url)
r = requests.get(url, headers=self.headers, params={'org': self.name})
if r.status_code == 404:
return False
elif r.status_code == 200:
data = json.loads(r.text)
for org in data['orgs']:
if org['name'] == self.name:
self.id = org['id']
self.description = org['description']
return True
return False
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def has_changed(self, description):
if self.description == description:
return False
return True
def create(self, description):
url = '{}/api/v2/orgs'.format(self.api_url)
r = requests.post(
url,
headers=self.headers,
json={'name': self.name, 'description': description}
)
if r.status_code != 201:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def delete(self):
url = '{}/api/v2/orgs/{}'.format(self.api_url, self.id)
r = requests.delete(url, headers=self.headers)
if r.status_code != 204:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def update(self, description):
url = '{}/api/v2/orgs/{}'.format(self.api_url, self.id)
r = requests.patch(
url,
headers=self.headers,
json={'name': self.name, 'description': description}
)
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def main():
fields = {
'name': {'type': 'str', 'required': True},
'description': {'type': 'str', 'default': ''},
'api_url': {'type': 'str', 'default': 'http://127.0.0.1:8086'},
'api_token': {'type': 'str', 'required': True},
'state': {'type': 'str',
'default': 'present',
'choice': ['present', 'absent']},
}
module = AnsibleModule(argument_spec=fields)
org = InfluxdbOrg(
module.params['api_url'],
module.params['api_token'],
module.params['name'],
)
changed = False
if org.exists():
if module.params['state'] == 'absent':
org.delete()
changed = True
elif org.has_changed(module.params['description']):
org.update(module.params['description'])
changed = True
elif module.params['state'] == 'present':
org.create(module.params['description'])
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

View file

@ -1,94 +0,0 @@
#!/usr/bin/python
from ansible.module_utils.basic import *
import requests
import json
class InfluxdbPrivilege:
def __init__(self, user, database, privilege, api_host, api_port, api_user, api_password):
self.user = user
self.database = database
self.privilege = privilege
self.api_host = api_host
self.api_port = api_port
self.api_user = api_user
self.api_password = api_password
self.change = False
self.get_info()
def request(self, query):
url = 'http://{}:{}/query?q={}'.format(self.api_host, self.api_port, requests.utils.quote(query))
if self.api_user is not None:
r = requests.get(url, auth=(self.api_user, self.api_password))
else:
r = requests.get(url)
if r.status_code != 200:
raise Exception('Influxdb', 'Bad status code {}: {}'.format(r.status_code, r.text))
return json.loads(r.text)
def get_info(self):
privileges = self.request(
'SHOW GRANTS FOR {}'.format(self.user),
)['results'][0]['series'][0]
if 'values' in privileges:
for privilege in privileges['values']:
if self.database == privilege[0]:
self.exist = True
if self.privilege != privilege[1]:
self.change = True
return
self.exist = False
def grant(self):
self.request(
'GRANT {} ON {} TO {}'.format(self.privilege, self.database, self.user)
)
def revoke(self):
self.request(
'REVOKE {} ON {} FROM {}'.format(self.privilege, self.database, self.user)
)
def main():
fields = {
'user': { 'type': 'str', 'required': True },
'database': { 'type': 'str', 'required': True },
'privilege': { 'type': 'str', 'required': True, 'choices': ['ALL', 'WRITE', 'READ'] },
'api_user': { 'type': 'str' },
'api_password': { 'type': 'str' },
'api_host': { 'type': 'str', 'default': '127.0.0.1' },
'api_port': { 'type': 'int', 'default': 8086 },
'state': { 'type': 'str', 'default': 'present', 'choices': ['present', 'absent'] }
}
module = AnsibleModule(argument_spec=fields)
changed = False
influxdb_privilege = InfluxdbPrivilege(
module.params['user'],
module.params['database'],
module.params['privilege'],
module.params['api_host'],
module.params['api_port'],
module.params['api_user'],
module.params['api_password']
)
if module.params['state'] == 'present':
if not influxdb_privilege.exist or influxdb_privilege.change:
influxdb_privilege.grant()
changed = True
else:
if influxdb_privilege.exist:
influxdb_privilege.revoke()
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

69
library/influxdb_setup.py Normal file
View file

@ -0,0 +1,69 @@
#!/usr/bin/python3
from ansible.module_utils.basic import AnsibleModule
import requests
import json
class InfluxdbSetup:
def __init__(self, api_url):
self.api_url = api_url
def already(self):
url = '{}/api/v2/setup'.format(self.api_url)
r = requests.get(url)
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
data = json.loads(r.text)
if 'allowed' not in data or data['allowed'] is True:
return False
return True
def run(self, username, org, bucket, token):
url = '{}/api/v2/setup'.format(self.api_url)
r = requests.post(url, json={
'username': username,
'org': org,
'bucket': bucket,
'token': token,
})
if r.status_code != 201:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def main():
fields = {
'username': {'type': 'str', 'required': True},
'org': {'type': 'str', 'required': True},
'bucket': {'type': 'str', 'required': True},
'token': {'type': 'str', 'required': True},
'api_url': {'type': 'str', 'default': 'http://127.0.0.1:8086'},
}
module = AnsibleModule(argument_spec=fields)
setup = InfluxdbSetup(
module.params['api_url'],
)
if setup.already() is True:
module.exit_json(changed=False)
setup.run(
module.params['username'],
module.params['org'],
module.params['bucket'],
module.params['token'],
)
module.exit_json(changed=True)
if __name__ == '__main__':
main()

124
library/influxdb_user.py Normal file
View file

@ -0,0 +1,124 @@
#!/usr/bin/python3
from ansible.module_utils.basic import AnsibleModule
import requests
import json
class InfluxdbUser:
def __init__(self, api_url, api_token, name):
self.api_url = api_url
self.headers = {'Authorization': 'Token {}'.format(api_token)}
self.name = name
def exists(self):
url = '{}/api/v2/users'.format(self.api_url)
r = requests.get(
url,
headers=self.headers,
params={'name': self.name}
)
if r.status_code == 404:
return False
elif r.status_code == 200:
data = json.loads(r.text)
for user in data['users']:
if user['name'] == self.name:
self.id = user['id']
self.status = user['status']
return True
return False
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def has_changed(self, status):
if self.status != status:
return True
return False
def create(self, status):
url = '{}/api/v2/users'.format(self.api_url)
r = requests.post(
url,
headers=self.headers,
json={
'name': self.name,
'status': status
}
)
if r.status_code != 201:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def delete(self):
url = '{}/api/v2/users/{}'.format(self.api_url, self.id)
r = requests.delete(url, headers=self.headers)
if r.status_code != 204:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def update(self, status):
url = '{}/api/v2/users/{}'.format(self.api_url, self.id)
r = requests.patch(
url,
headers=self.headers,
json={
'name': self.name,
'status': status
}
)
if r.status_code != 200:
raise Exception(
'Influxdb',
'Bad status code {}: {}'.format(r.status_code, r.text)
)
def main():
fields = {
'name': {'type': 'str', 'required': True},
'status': {'type': 'str',
'default': 'active',
'choice': ['active', 'inactive']},
'api_url': {'type': 'str', 'default': 'http://127.0.0.1:8086'},
'api_token': {'type': 'str', 'required': True},
'state': {'type': 'str',
'default': 'present',
'choice': ['present', 'absent']},
}
module = AnsibleModule(argument_spec=fields)
user = InfluxdbUser(
module.params['api_url'],
module.params['api_token'],
module.params['name'],
)
changed = False
if user.exists():
if module.params['state'] == 'absent':
user.delete()
changed = True
elif user.has_changed(module.params['status']):
user.update(module.params['status'])
changed = True
elif module.params['state'] == 'present':
user.create(module.params['status'])
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

View file

@ -1,16 +1,17 @@
--- ---
galaxy_info: galaxy_info:
role_name: Influxdb role_name: influxdb
namespace: nishiki
author: Adrien Waksberg author: Adrien Waksberg
company: Adrien Waksberg company: Adrien Waksberg
description: Install and configure InfluxDB description: Install and configure InfluxDB 2
license: Apache2 license: Apache2
min_ansible_version: 2.7 min_ansible_version: "2.10"
platforms: platforms:
- name: Debian - name: Debian
versions: versions:
- stretch - bookworm
galaxy_tags: galaxy_tags:
- database - database

View file

@ -0,0 +1,40 @@
---
- name: Converge
hosts: all
roles:
- ansible-role-influxdb
vars:
influxdb_init_username: admin
influxdb_init_org: init_org
influxdb_init_bucket: test
influxdb_api_token: SuP3rS3cr3t
influxdb_orgs:
neworg:
description: it's a test
init_org:
state: absent
influxdb_buckets:
mybucket:
description: KFC
retention: 3600
org: neworg
influxdb_users:
telegraf:
user_inactive:
status: inactive
influxdb_authorizations:
- user: telegraf
description: write telegraf
org: neworg
status: active
permissions:
- action: write
resource:
type: buckets
org: neworg
name: mybucket
pre_tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true

View file

@ -0,0 +1,19 @@
---
driver:
name: docker
platforms:
- name: debian12
image: code.waks.be/nishiki/molecule:debian12
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
command: /bin/systemd
capabilities:
- SYS_ADMIN
lint: |
set -e
yamllint .
ansible-lint .
verifier:
name: testinfra

View file

@ -0,0 +1,48 @@
import testinfra.utils.ansible_runner
CURL = ('curl --header "Authorization: Token SuP3rS3cr3t"'
' http://localhost:8086/api/v2')
def test_packages(host):
package = host.package('influxdb2')
assert package.is_installed
def test_service(host):
service = host.service('influxdb')
assert service.is_running
assert service.is_enabled
def test_sockets(host):
socket = host.socket('tcp://0.0.0.0:8086')
assert socket.is_listening
def test_users(host):
cmd = host.run('{}/users -X GET'.format(CURL))
assert cmd.succeeded
assert '"name": "admin"' in cmd.stdout
assert '"name": "telegraf"' in cmd.stdout
def test_org(host):
cmd = host.run('{}/orgs -X GET'.format(CURL))
assert cmd.succeeded
assert '"name": "neworg"' in cmd.stdout
assert '"name": "init_org"' not in cmd.stdout
def test_bucket(host):
cmd = host.run('{}/buckets -X GET'.format(CURL))
assert cmd.succeeded
assert '"name": "mybucket"' in cmd.stdout
assert '"everySeconds": 3600' in cmd.stdout
assert '"name": "test"' not in cmd.stdout
def test_auth(host):
cmd = host.run('{}/authorizations -X GET'.format(CURL))
assert cmd.succeeded
assert '"description": "write telegraf"' in cmd.stdout

View file

@ -1,68 +0,0 @@
---
- name: copy config file
template:
src: influxdb.conf.j2
dest: /etc/influxdb/influxdb.conf
owner: root
group: root
mode: 0644
notify: restart influxdb
tags: influxdb
- name: start and enable service
systemd:
name: influxdb
state: started
enabled: yes
tags: influxdb
- name: wait http api is up
wait_for:
port: 8086
timeout: 10
tags: influxdb
- name: create users
influxdb_user:
user_name: '{{ item.name }}'
user_password: '{{ item.password }}'
admin: '{{ item.admin|default(false) }}'
username: '{{ influxdb_api_user }}'
password: '{{ influxdb_api_password }}'
loop: '{{ influxdb_users }}'
when: item.state is not defined or item.state != 'absent'
no_log: true
tags: influxdb
- name: delete users
influxdb_user:
user_name: '{{ item.name }}'
username: '{{ influxdb_api_user }}'
password: '{{ influxdb_api_password }}'
state: absent
loop: '{{ influxdb_users }}'
when: item.state is defined and item.state == 'absent'
no_log: true
tags: influxdb
- name: create databases
influxdb_database:
database_name: '{{ item }}'
username: '{{ influxdb_api_user }}'
password: '{{ influxdb_api_password }}'
loop: '{{ influxdb_databases }}'
no_log: true
tags: influxdb
- name: create privileges
influxdb_privilege:
user: '{{ item.user }}'
database: '{{ item.database }}'
privilege: '{{ item.privilege }}'
api_user: '{{ influxdb_api_user }}'
api_password: '{{ influxdb_api_password }}'
api_port: '{{ influxdb_api_port }}'
state: '{{ item.state|default("present") }}'
loop: '{{ influxdb_privileges }}'
no_log: true
tags: influxdb

View file

@ -1,42 +0,0 @@
---
- name: install depencies packages
apt:
name: '{{ packages }}'
vars:
packages:
- apt-transport-https
- collectd-core
- python-pip
register: result
until: result is succeeded
retries: 2
tags: influxdb
- name: add key for influxdb repository
apt_key:
url: https://repos.influxdata.com/influxdb.key
register: result
until: result is succeeded
retries: 2
tags: influxdb
- name: add influxdb repository
apt_repository:
repo: 'deb https://repos.influxdata.com/debian {{ ansible_distribution_release }} stable'
tags: influxdb
- name: install influxdb package
apt:
name: influxdb
register: result
until: result is succeeded
retries: 2
tags: influxdb
- name: install python-influxdb
pip:
name: influxdb
register: result
until: result is succeeded
retries: 2
tags: influxdb

View file

@ -1,3 +1,103 @@
--- ---
- import_tasks: install.yml - name: Install depencies packages
- import_tasks: config.yml ansible.builtin.package:
name:
- apt-transport-https
- python3-requests
tags: influxdb
- name: Add key for influxdb repository
ansible.builtin.get_url:
url: https://repos.influxdata.com/influxdata-archive_compat.key
dest: /etc/apt/keyrings/influx.asc
checksum: sha256:393e8779c89ac8d958f81f942f9ad7fb82a25e133faddaf92e15b16e6ac9ce4c
owner: root
group: root
mode: "0644"
tags: influxdb
- name: Add influxdb repository
ansible.builtin.apt_repository:
repo: "deb [signed-by=/etc/apt/keyrings/influx.asc] https://repos.influxdata.com/debian stable main"
tags: influxdb
- name: Install package
ansible.builtin.package:
name:
- influxdb2
tags: influxdb
- name: Start and enable service
ansible.builtin.service:
name: influxdb
state: started
enabled: true
tags: influxdb
- name: Wait http api is up
ansible.builtin.wait_for:
port: 8086
timeout: 10
tags: influxdb
- name: Setup server
influxdb_setup:
api_url: http://127.0.0.1:8086
username: "{{ influxdb_init_username }}"
org: "{{ influxdb_init_org }}"
bucket: "{{ influxdb_init_bucket }}"
token: "{{ influxdb_api_token }}"
tags: influxdb
- name: Manage organisations
influxdb_org:
name: "{{ item.key }}"
description: "{{ item.value.description | default(omit) }}"
api_url: http://127.0.0.1:8086
api_token: "{{ influxdb_api_token }}"
state: "{{ item.value.state | default('present') }}"
loop: "{{ influxdb_orgs | dict2items }}"
loop_control:
label: "{{ item.key }}"
tags: influxdb
- name: Manage buckets
influxdb_bucket:
name: "{{ item.key }}"
org: "{{ item.value.org }}"
description: "{{ item.value.description | default(omit) }}"
retention: "{{ item.value.retention | default(omit) }}"
api_url: http://127.0.0.1:8086
api_token: "{{ influxdb_api_token }}"
state: "{{ item.value.state | default('present') }}"
loop: "{{ influxdb_buckets | dict2items }}"
loop_control:
label: "{{ item.key }}"
tags: influxdb
- name: Manage users
influxdb_user:
name: "{{ item.key }}"
status: "{{ item.value.status | default('active') }}"
api_url: http://127.0.0.1:8086
api_token: "{{ influxdb_api_token }}"
state: "{{ item.value.state | default('present') }}"
loop: "{{ influxdb_users | dict2items }}"
loop_control:
label: "{{ item.key }}"
tags: influxdb
- name: Manage authorizations
influxdb_auth:
org: "{{ item.org }}"
user: "{{ item.user }}"
description: "{{ item.description | default(omit) }}"
permissions: "{{ item.permissions }}"
status: "{{ item.status | default('active') }}"
api_url: http://127.0.0.1:8086
api_token: "{{ influxdb_api_token }}"
state: "{{ item.state | default('present') }}"
loop: "{{ influxdb_authorizations }}"
loop_control:
label: "{{ item.org }}/{{ item.user }}"
tags: influxdb

View file

@ -1,8 +1,8 @@
# {{ ansible_managed }} # {{ ansible_managed }}
{% for section, options in influxdb_full_config.iteritems() %} {% for section, options in influxdb_full_config.items() %}
[{{ section }}] [{{ section }}]
{% for option, value in options.iteritems() %} {% for option, value in options.items() %}
{{ option }} = {% if value is sameas true %}true {{ option }} = {% if value is sameas true %}true
{% elif value is sameas false %}false {% elif value is sameas false %}false
{% elif value is string %}"{{ value }}" {% elif value is string %}"{{ value }}"

View file

@ -1,28 +0,0 @@
E, [2019-01-17T09:43:25.055727 #24689] ERROR -- Kitchen: ------Exception-------
E, [2019-01-17T09:43:25.055802 #24689] ERROR -- Kitchen: Class: Kitchen::UserError
E, [2019-01-17T09:43:25.055823 #24689] ERROR -- Kitchen: Message: Kitchen YAML file /home/awaksberg/git/ansible-role-influxdb/test/integration/default/.kitchen.yml does not exist.
E, [2019-01-17T09:43:25.055835 #24689] ERROR -- Kitchen: ----------------------
E, [2019-01-17T09:43:25.055845 #24689] ERROR -- Kitchen: ------Backtrace-------
E, [2019-01-17T09:43:25.055854 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/loader/yaml.rb:65:in `read'
E, [2019-01-17T09:43:25.055863 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/config.rb:152:in `data'
E, [2019-01-17T09:43:25.055872 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/config.rb:131:in `suites'
E, [2019-01-17T09:43:25.055881 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/config.rb:182:in `filter_instances'
E, [2019-01-17T09:43:25.055891 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/config.rb:141:in `build_instances'
E, [2019-01-17T09:43:25.055900 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/config.rb:117:in `instances'
E, [2019-01-17T09:43:25.055909 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/command.rb:112:in `filtered_instances'
E, [2019-01-17T09:43:25.055918 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/command.rb:142:in `parse_subcommand'
E, [2019-01-17T09:43:25.055927 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/command/list.rb:30:in `call'
E, [2019-01-17T09:43:25.055936 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/cli.rb:52:in `perform'
E, [2019-01-17T09:43:25.055947 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/cli.rb:120:in `list'
E, [2019-01-17T09:43:25.055957 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
E, [2019-01-17T09:43:25.055966 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
E, [2019-01-17T09:43:25.055975 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
E, [2019-01-17T09:43:25.055984 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/thor-0.20.3/lib/thor/base.rb:466:in `start'
E, [2019-01-17T09:43:25.055993 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/bin/kitchen:13:in `block in <top (required)>'
E, [2019-01-17T09:43:25.056002 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/lib/kitchen/errors.rb:171:in `with_friendly_errors'
E, [2019-01-17T09:43:25.056011 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/gems/test-kitchen-1.23.5/bin/kitchen:13:in `<top (required)>'
E, [2019-01-17T09:43:25.056020 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/bin/kitchen:23:in `load'
E, [2019-01-17T09:43:25.056029 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/bin/kitchen:23:in `<main>'
E, [2019-01-17T09:43:25.056039 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/bin/ruby_executable_hooks:24:in `eval'
E, [2019-01-17T09:43:25.056048 #24689] ERROR -- Kitchen: /home/awaksberg/.rvm/gems/ruby-2.5.1/bin/ruby_executable_hooks:24:in `<main>'
E, [2019-01-17T09:43:25.056057 #24689] ERROR -- Kitchen: ----End Backtrace-----

View file

@ -1,32 +0,0 @@
---
- hosts: localhost
connection: local
vars:
influxdb_users:
- name: paul
password: test
admin: yes
- name: adrien
password: test2
- name: antoine
state: absent
influxdb_databases:
- new_database
influxdb_privileges:
- user: adrien
database: new_database
privilege: WRITE
influxdb_config:
'[collectd]':
enabled: true
port: 25826
database: collectd
typesdb: /usr/share/collectd/types.db
roles:
- ansible-role-influxdb
pre_tasks:
- name: install collectd package
apt:
name: collectd-core

View file

@ -1,50 +0,0 @@
require 'serverspec'
set :backend, :exec
puts
puts '================================'
puts %x(ansible --version)
puts '================================'
describe package('influxdb') do
it { should be_installed }
end
describe file('/etc/influxdb/influxdb.conf') do
it { should be_file }
it { should be_mode 644 }
it { should be_owned_by 'root' }
it { should be_grouped_into 'root' }
it { should contain 'enabled = true' }
end
describe service('influxdb') do
it { should be_enabled }
it { should be_running.under('systemd') }
end
describe port(8086) do
it { should be_listening.with('tcp6') }
end
describe port(25_826) do
it { should be_listening.with('udp6') }
end
describe command('influx -execute "SHOW USERS"') do
its(:exit_status) { should eq 0 }
its(:stdout) { should match(/paul\s+true/) }
its(:stdout) { should match(/adrien\s+false/) }
its(:stdout) { should_not match 'antoine' }
end
describe command('influx -execute "SHOW DATABASES"') do
its(:exit_status) { should eq 0 }
its(:stdout) { should match 'new_database' }
end
describe command('influx -execute "SHOW GRANTS FOR adrien"') do
its(:exit_status) { should eq 0 }
its(:stdout) { should match(/new_database\s+WRITE/) }
end

View file

@ -1 +0,0 @@
localhost