Manage Kibana objects
This patch adds a salt state module to manage Kibana objects. It also
adds the client to install these objects.
diff --git a/README.rst b/README.rst
index 89c289f..2a54f23 100644
--- a/README.rst
+++ b/README.rst
@@ -39,6 +39,38 @@
port: 9200
+Client setup
+------------
+
+Client with host and port (Kibana use Elasticsearch to store its data):
+
+.. code-block:: yaml
+
+ kibana:
+ client:
+ enabled: true
+ server:
+ host: elasticsearch.host
+ port: 9200
+
+Client where you download a Kibana object that is stored in the directory
+*files/*:
+
+.. code-block:: yaml
+
+ kibana:
+ client:
+ enabled: true
+ server:
+ host: elasticsearch.host
+ port: 9200
+ object:
+ logs:
+ enabled: true
+ name: Logs
+ template: kibana/files/objects/dashboard_logs.json
+ type: 'dashboard'
+
Read more
=========
diff --git a/_states/kibana_object.py b/_states/kibana_object.py
new file mode 100644
index 0000000..dd40da7
--- /dev/null
+++ b/_states/kibana_object.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+'''
+Manage Kibana objects.
+
+.. code-block:: yaml
+
+ kibana:
+ kibana_url: 'https://es.host.com:9200'
+ kibana_index: '.kibana'
+
+.. code-block:: yaml
+
+ Ensure minimum dashboard is managed:
+ kibana_objects.present:
+ - name: 'Logs'
+ - kibana_content: <JSON object>
+ - kibana_type: 'dashboard'
+
+'''
+
+# Import Python libs
+import requests
+
+# Import Salt libs
+from salt.utils.dictdiffer import DictDiffer
+
+
+def __virtual__():
+ '''Always load the module.'''
+ return True
+
+
+def present(name, kibana_content=None, kibana_type=None):
+ '''
+ Ensure the Kibana object exists in the database.
+
+ name
+ Name of the object
+
+ kibana_content
+ Content in JSON
+
+ kibana_type
+ String
+ '''
+ ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}
+
+ if not kibana_content:
+ ret['result'] = False
+ ret['comment'] = 'Content is not set'
+ return ret
+
+ profile = __salt__['config.option']('kibana')
+
+ url, index = _set_parameters(name, kibana_type, profile)
+ if not url:
+ ret['result'] = False
+ ret['comment'] = index
+ return ret
+
+ try:
+ headers = {'Content-type': 'application/json'}
+ response = requests.get(url, headers=headers)
+ if response.ok:
+ delta = DictDiffer(response.json(), kibana_content)
+ ret['changes'] = {
+ 'old': "{}".format(delta.removed()),
+ 'new': "{}".format(delta.added()),
+ 'updated': "{}".format(delta.changed())
+ }
+ if not ret['changes']['old'] and not ret['changes']['new'] and not ret['changes']['updated']:
+ ret['comment'] = "Object {} is already present".format(name)
+ return ret
+ response = requests.put(url, headers=headers, json=kibana_content)
+ except requests.exceptions.RequestException as exc:
+ ret['result'] = False
+ ret['comment'] = ("Failed to create Kibana object {0}\n"
+ "Got exception: {1}").format(name, exc)
+ else:
+ if response.ok:
+ if ret['changes']['old'] or ret['changes']['new'] or ret['changes']['updated']:
+ ret['comment'] = 'Kibana object {0} has been updated'.format(name)
+ else:
+ ret['comment'] = 'Kibana object {0} has been created'.format(name)
+ ret['changes']['new'] = 'Kibana objects created'
+ else:
+ ret['result'] = False
+ ret['comment'] = ("Failed to post Kibana object {0}\n"
+ "Response: {1}").format(name, response)
+
+ return ret
+
+
+def absent(name, kibana_type=None):
+ '''
+ Ensure the Kibana object is not present in the database.
+
+ name
+ Name of the object
+
+ kibana_type
+ String
+ '''
+ ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}
+
+ profile = __salt__['config.option']('kibana')
+
+ url, index = _set_parameters(name, kibana_type, profile)
+ if not url:
+ ret['result'] = False
+ ret['comment'] = index
+ return ret
+
+ try:
+ response = requests.delete(url)
+ except requests.exceptions.RequestException as exc:
+ ret['result'] = False
+ ret['comment'] = ("Failed to delete Kibana object {0}\n"
+ "Got exception: {1}").format(name, exc)
+ else:
+ if response.ok:
+ ret['comment'] = "Kibana object {0} has been deleted".format(name)
+ elif response.status_code == 404:
+ ret['comment'] = "Kibana object {0} was not present".format(name)
+ else:
+ ret['result'] = False
+ ret['comment'] = ("Failed to delete Kibana object {0}\n"
+ "Response: {1}").format(name, response)
+
+ return ret
+
+
+def _set_parameters(name, kibana_type, profile):
+ '''
+ Retrieve parameters from profile.
+ '''
+
+ if not kibana_type:
+ return False, 'Type is not set'
+
+ url = profile.get('kibana_url')
+ if not url:
+ return False, 'Cannot get URL needed by Kibana client'
+
+ index = profile.get('kibana_index')
+ if not index:
+ return False, 'Cannot get the index needed by Kibana client'
+
+ url = "http://{0}/{1}/{2}/{3}".format(url, index, kibana_type, name)
+ return url, index
diff --git a/kibana/client.sls b/kibana/client.sls
new file mode 100644
index 0000000..95f0063
--- /dev/null
+++ b/kibana/client.sls
@@ -0,0 +1,24 @@
+{%- from "kibana/map.jinja" import client with context %}
+{%- if client.get('enabled', False) %}
+
+/etc/salt/minion.d/_kibana.conf:
+ file.managed:
+ - source: salt://kibana/files/_kibana.conf
+ - template: jinja
+ - user: root
+ - group: root
+
+{%- for object_name, object in client.get('object', {}).iteritems() %}
+kibana_object_{{ object_name }}:
+ {%- if object.get('enabled', False) %}
+ {% import_json object.template as content %}
+ kibana_object.present:
+ - kibana_content: {{ content|json }}
+ {%- else %}
+ kibana_object.absent:
+ {%- endif %}
+ - name: {{ object_name }}
+ - kibana_type: {{ object.type }}
+{%- endfor %}
+
+{%- endif %}
diff --git a/kibana/files/_kibana.conf b/kibana/files/_kibana.conf
new file mode 100644
index 0000000..410e2b2
--- /dev/null
+++ b/kibana/files/_kibana.conf
@@ -0,0 +1,5 @@
+{%- from "kibana/map.jinja" import client with context %}
+
+kibana:
+ kibana_url: {{ client.server.host }}:{{ client.server.port }}
+ kibana_index: {{ client.server.index }}
diff --git a/kibana/map.jinja b/kibana/map.jinja
index cedacb3..0a6540a 100644
--- a/kibana/map.jinja
+++ b/kibana/map.jinja
@@ -6,3 +6,12 @@
'configpath': '/opt/kibana/config/kibana.yml',
},
}, merge=salt['pillar.get']('kibana:server')) %}
+
+{%- load_yaml as client_defaults %}
+default:
+ server:
+ host: 127.0.0.1
+ port: 9200
+ index: '.kibana'
+{%- endload %}
+{%- set client = salt['grains.filter_by'](client_defaults, merge=salt['pillar.get']('kibana:client')) %}
diff --git a/metadata/service/client.yml b/metadata/service/client.yml
new file mode 100644
index 0000000..1291f37
--- /dev/null
+++ b/metadata/service/client.yml
@@ -0,0 +1,6 @@
+applications:
+- kibana.client
+parameters:
+ kibana:
+ client:
+ enabled: true