diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79776e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.kitchen/* diff --git a/.kitchen.yml b/.kitchen.yml new file mode 100644 index 0000000..7a16e14 --- /dev/null +++ b/.kitchen.yml @@ -0,0 +1,27 @@ +--- +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 diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..5465b58 --- /dev/null +++ b/.yamllint @@ -0,0 +1,12 @@ +--- +extends: default + +ignore: | + .kitchen/* + vendor/ + +rules: + line-length: + max: 120 + level: warning + truthy: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..016b6d1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# CHANGELOG + +This project adheres to [Semantic Versioning](http://semver.org/). +Which is based on [Keep A Changelog](http://keepachangelog.com/) + +## [Unreleased] + +- first version diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..0d96441 --- /dev/null +++ b/Gemfile @@ -0,0 +1,8 @@ +source 'https://rubygems.org' + +group :development do + gem 'kitchen-ansible' + gem 'kitchen-docker_cli' + gem 'rubocop', '0.50.0' + gem 'test-kitchen' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..723a976 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,93 @@ +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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..71326f8 --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# 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) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://git.yaegashi.fr/nishiki/ansible-role-influxdb/src/branch/master/LICENSE) + +Install and configure InfluxDB + +## Requirements + +* Ansible >= 2.7 +* Debian Stretch + +## Role variables + +* `influxdb_databases` - array with the databases name +* `influxdb_users` - array with the users informations + +``` +- name: test + password: secret + admin: true + state: present +``` + +* `influxdb_privileges` - array with the privileges + +``` +- user: test + database: metric + privilege: WRITE + state: present +``` + +* `influxdb_api_user` - set the api user if you have enabled http authentification +* `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/)) + +``` + meta: + dir: /var/lib/influxdb/meta + data: + dir: /var/lib/influxdb/data + wal-dir: /var/lib/influxdb/wal +``` + +## How to use + +``` +- hosts: server + roles: + - influxdb +``` + +## Development + +### Test syntax with yamllint + +* install `python` and `python-pip` +* install yamllint `pip install yamllint` +* run `yamllint .` + +### 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 + +``` +Copyright (c) 2019 Adrien Waksberg + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..e019396 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,15 @@ +--- +influxdb_databases: [] +influxdb_users: [] +influxdb_privileges: [] +influxdb_api_user: user +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) }}' diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..32777ab --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart influxdb + systemd: + name: influxdb + state: restarted diff --git a/library/influxdb_privilege.py b/library/influxdb_privilege.py new file mode 100644 index 0000000..f957988 --- /dev/null +++ b/library/influxdb_privilege.py @@ -0,0 +1,94 @@ +#!/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() diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..c127d06 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,20 @@ +--- +galaxy_info: + role_name: Influxdb + author: Adrien Waksberg + company: Adrien Waksberg + description: Install and configure InfluxDB + license: Apache2 + min_ansible_version: 2.7 + + platforms: + - name: Debian + versions: + - stretch + + galaxy_tags: + - database + - influxdb + - timeseries + +dependencies: [] diff --git a/tasks/config.yml b/tasks/config.yml new file mode 100644 index 0000000..99d36b7 --- /dev/null +++ b/tasks/config.yml @@ -0,0 +1,68 @@ +--- +- 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 diff --git a/tasks/install.yml b/tasks/install.yml new file mode 100644 index 0000000..a3212c6 --- /dev/null +++ b/tasks/install.yml @@ -0,0 +1,42 @@ +--- +- 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 diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..66e9a65 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- import_tasks: install.yml +- import_tasks: config.yml diff --git a/templates/influxdb.conf.j2 b/templates/influxdb.conf.j2 new file mode 100644 index 0000000..1522e51 --- /dev/null +++ b/templates/influxdb.conf.j2 @@ -0,0 +1,13 @@ +# {{ ansible_managed }} +{% for section, options in influxdb_full_config.iteritems() %} + +[{{ section }}] +{% for option, value in options.iteritems() %} +{{ option }} = {% if value is sameas true %}true +{% elif value is sameas false %}false +{% elif value is string %}"{{ value }}" +{% elif value is number %}{{ value }} +{% else %}["{{ value|join('", "') }}"] +{% endif %} +{% endfor %} +{% endfor %} diff --git a/test/integration/default/.kitchen/logs/kitchen.log b/test/integration/default/.kitchen/logs/kitchen.log new file mode 100644 index 0000000..9c3145c --- /dev/null +++ b/test/integration/default/.kitchen/logs/kitchen.log @@ -0,0 +1,28 @@ +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 ' +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 `' +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 `
' +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 `
' +E, [2019-01-17T09:43:25.056057 #24689] ERROR -- Kitchen: ----End Backtrace----- diff --git a/test/integration/default/default.yml b/test/integration/default/default.yml new file mode 100644 index 0000000..d992950 --- /dev/null +++ b/test/integration/default/default.yml @@ -0,0 +1,32 @@ +--- +- 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 diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb new file mode 100644 index 0000000..631558a --- /dev/null +++ b/test/integration/default/serverspec/default_spec.rb @@ -0,0 +1,50 @@ +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 diff --git a/test/integration/inventory b/test/integration/inventory new file mode 100644 index 0000000..2fbb50c --- /dev/null +++ b/test/integration/inventory @@ -0,0 +1 @@ +localhost