diff --git a/CHANGELOG.md b/CHANGELOG.md index c3754c5..73a8fb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Which is based on [Keep A Changelog](http://keepachangelog.com/) ### Added - new option to set heap size +- manage index templates ## v1.0.0 - 2019-09-05 diff --git a/README.md b/README.md index 9ad7bb2..2636b11 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,23 @@ Install and configure Elasticsearch path.logs: /var/log/elasticsearch ``` +* `elasticsearch_index_templates` - hash with the index templates configuration + +``` + logstash: + index_patterns: + - 'logstash-*' + settings: + index: + number_of_replicas: 3 + mappings: + metric: + type: short + date: + type: date + format: YYYY-MM-dd +``` + ## How to use ``` diff --git a/defaults/main.yml b/defaults/main.yml index 3a165a1..ea82f64 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -5,3 +5,4 @@ elasticsearch_default_config: path.data: /var/lib/elasticsearch path.logs: /var/log/elasticsearch elasticsearch_full_config: '{{ elasticsearch_default_config|combine(elasticsearch_config) }}' +elasticsearch_index_templates: {} diff --git a/library/elasticsearch_template.py b/library/elasticsearch_template.py new file mode 100644 index 0000000..fd339ee --- /dev/null +++ b/library/elasticsearch_template.py @@ -0,0 +1,112 @@ +#!/usr/bin/python + +from ansible.module_utils.basic import * +from ansible.module_utils.elasticsearch_api import * + +class ElasticsearchTemplate: + def __init__(self, api, name): + self.api = api + self.name = name + self.exist = False + self.data = {} + + def get_data(self): + status_code, data = self.api.get('_template/{}'.format(self.name)) + if status_code == 200: + self.exist = True + self.data = data[self.name] + + def dict_has_changed(self, new_data, data): + for option, value in new_data.items(): + if not option in data: + if value: + return True + + elif type(value) is dict: + if not type(data[option]) is dict: + return True + for k, v in value.items(): + if not k in data[option]: + return True + if str(data[option][k]) != str(v): + return True + + elif type(value) is list: + if not type(data[option]) is list: + return True + if data[option].sort() != value.sort(): + return True + + elif data[option] != value: + return True + + return False + + def has_changed(self, options): + if options['index_patterns'].sort() != self.data['index_patterns'].sort(): + return True + + if options['settings']: + if self.dict_has_changed(options['settings'], self.data['settings']): + return True + elif self.data['settings']: + return True + + if options['mappings']['properties']: + if not self.data['mappings']: + return True + elif self.dict_has_changed(options['mappings']['properties'], self.data['mappings']['properties']): + return True + else: + if self.data['mappings']: + return True + + return False + + def create(self, options): + self.api.put( + '_template/{}'.format(self.name), + options + ) + +def main(): + fields = { + 'name': { 'type': 'str', 'required': True }, + 'index_patterns': { 'type': 'list', 'default': [] }, + 'settings': { 'type': 'dict', 'default': {} }, + 'mappings': { 'type': 'dict', 'default': {} }, + 'api_url': { 'type': 'str', 'default': 'http://127.0.0.1:9200' }, + 'api_user': { 'type': 'str', 'default': None }, + 'api_password': { 'type': 'str', 'default': None }, + } + module = AnsibleModule(argument_spec=fields) + changed = False + + options = { + 'index_patterns': module.params['index_patterns'], + 'settings': module.params['settings'], + 'mappings': { + 'properties': module.params['mappings'], + }, + } + + api = ElasticsearchApi( + module.params['api_url'], + module.params['api_user'], + module.params['api_password'] + ) + + template = ElasticsearchTemplate( + api, + module.params['name'], + ) + template.get_data() + + if not template.exist or template.has_changed(options): + template.create(options) + changed = True + + module.exit_json(changed=changed) + +if __name__ == '__main__': + main() diff --git a/module_utils/elasticsearch_api.py b/module_utils/elasticsearch_api.py new file mode 100644 index 0000000..e8d858c --- /dev/null +++ b/module_utils/elasticsearch_api.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +import requests +import base64 + +class ElasticsearchApi: + def __init__(self, url, user, password): + self.url = url + self.headers = {} + if user and password: + token = base64.b64encode('{}:{}',) + self.headers = { 'Authorization': 'Basic ' + base64.b64encode({},) } + + def get(self, path): + r = requests.get( + '{}/{}'.format(self.url, path), + headers=self.headers + ) + + if r.status_code == 500: + raise Exception('Server return 500 error: {}'.format(r.text)) + elif r.status_code == 401: + raise Exception('Authentification has failed') + + return r.status_code, r.json() + + def put(self, path, data): + r = requests.put( + '{}/{}'.format(self.url, path), + headers=self.headers, + json=data + ) + + if r.status_code == 500: + raise Exception('Server return 500 error: {}'.format(r.text)) + elif r.status_code == 401: + raise Exception('Authentification has failed') + elif r.status_code != 200: + raise Exception('Server return an unknown error: {}'.format(r.text)) diff --git a/tasks/main.yml b/tasks/main.yml index e33e9fc..00a7d28 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,7 +1,11 @@ --- - name: install dependencies packages apt: - name: apt-transport-https + name: '{{ packages }}' + vars: + packages: + - apt-transport-https + - python-requests retries: 2 register: result until: result is succeeded @@ -55,3 +59,20 @@ state: started enabled: true tags: elasticsearch + +- name: wait for api is available + wait_for: + port: 9200 + timeout: 10 + tags: elasticsearch + +- name: copy index templates + elasticsearch_template: + name: '{{ item.key }}' + index_patterns: '{{ item.value.index_patterns }}' + settings: '{{ item.value.settings|default({}) }}' + mappings: '{{ item.value.mappings|default({}) }}' + no_log: true + loop: '{{ elasticsearch_index_templates|dict2items }}' + run_once: true + tags: elasticsearch diff --git a/test/integration/default/default.yml b/test/integration/default/default.yml index f4717eb..986bd4d 100644 --- a/test/integration/default/default.yml +++ b/test/integration/default/default.yml @@ -3,6 +3,16 @@ connection: local vars: elasticsearch_heap_size: 512m + elasticsearch_index_templates: + test: + index_patterns: + - 'hello*' + settings: + index: + number_of_replicas: 1 + mappings: + metrics: + type: short roles: - ansible-role-elasticsearch diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb index 124e3d2..78dee18 100644 --- a/test/integration/default/serverspec/default_spec.rb +++ b/test/integration/default/serverspec/default_spec.rb @@ -33,3 +33,8 @@ end describe command('ps faux | grep elasticsearch') do its(:stdout) { should contain('-Xms512m -Xmx512m') } end + +describe command('curl -v http://127.0.0.1:9200/_template/test') do + its(:exit_status) { should eq 0 } + its(:stdout) { should contain('"number_of_replicas":"1"') } +end