From 2958bda057ac2a41f47adff832f22fe6dd7b58ec Mon Sep 17 00:00:00 2001 From: Guillaume Thouvenin Date: Tue, 8 Nov 2016 11:55:55 +0100 Subject: [PATCH] Add support for JSON dashboards This patch adds the support for JSON dashboards and also provides the support for remote dashboards. --- README.rst | 6 ++- _states/grafana3_dashboard.py | 81 ++++++++++++++++++++-------------- grafana/client.sls | 62 ++++++++++++++------------ grafana/collector.sls | 2 + metadata/service/collector.yml | 6 +++ 5 files changed, 92 insertions(+), 65 deletions(-) create mode 100644 metadata/service/collector.yml diff --git a/README.rst b/README.rst index fcbdef8..f806ffe 100644 --- a/README.rst +++ b/README.rst @@ -44,7 +44,9 @@ Server installed with PostgreSQL database user: grafana password: passwd -Server installed with default StackLight JSON dashboards +Server installed with default StackLight JSON dashboards. This will +be replaced by the possibility for a service to provide its own dashboard +using salt-mine. .. code-block:: yaml @@ -202,7 +204,7 @@ The default format of Grafana dashboards with lists for rows, panels and targets span: 6 editable: false type: graph - targets: + targets: - refId: A target: "support_prd.cfg01_iot_tcpcloud_eu.cpu.0.idle" datasource: graphite01 diff --git a/_states/grafana3_dashboard.py b/_states/grafana3_dashboard.py index 0087b2c..63090ab 100644 --- a/_states/grafana3_dashboard.py +++ b/_states/grafana3_dashboard.py @@ -65,6 +65,7 @@ def present(name, base_panels_from_pillar=None, base_rows_from_pillar=None, dashboard=None, + dashboard_format='yaml', profile='grafana'): ''' Ensure the grafana dashboard exists and is managed. @@ -84,19 +85,39 @@ def present(name, dashboard A dict that defines a dashboard that should be managed. + dashboard_format + You can use two formats for dashboards. You can use the JSON format + if you provide a complete dashboard in raw JSON or you can use the YAML + format (this is the default) and provide a description of the + dashboard in YAML. + profile A pillar key or dict that contains grafana information ''' ret = {'name': name, 'result': True, 'comment': '', 'changes': {}} - - base_dashboards_from_pillar = base_dashboards_from_pillar or [] - base_panels_from_pillar = base_panels_from_pillar or [] - base_rows_from_pillar = base_rows_from_pillar or [] dashboard = dashboard or {} if isinstance(profile, six.string_types): profile = __salt__['config.option'](profile) + if dashboard_format == 'json': + # In this case, a raw JSON of the full dashboard is provided. + response = _update(dashboard, profile) + + if response.get('status') == 'success': + ret['comment'] = 'Dashboard {0} created.'.format(name) + ret['changes']['new'] = 'Dashboard {0} created.'.format(name) + else: + ret['result'] = False + ret['comment'] = ("Failed to create dashboard {0}, " + "response={1}").format(name, response) + + return ret + + base_dashboards_from_pillar = base_dashboards_from_pillar or [] + base_panels_from_pillar = base_panels_from_pillar or [] + base_rows_from_pillar = base_rows_from_pillar or [] + # Add pillar keys for default configuration base_dashboards_from_pillar = ([_DEFAULT_DASHBOARD_PILLAR] + base_dashboards_from_pillar) @@ -439,18 +460,12 @@ def _delete(url, profile): '''Delete a specific dashboard.''' request_url = "{0}/api/dashboards/{1}".format(profile.get('grafana_url'), url) - if profile.get('grafana_token', False): - response = requests.delete( - request_url, - headers=_get_headers(profile), - timeout=profile.get('grafana_timeout'), - ) - else: - response = requests.delete( - request_url, - auth=_get_auth(profile), - timeout=profile.get('grafana_timeout'), - ) + response = requests.delete( + request_url, + auth=_get_auth(profile), + headers=_get_headers(profile), + timeout=profile.get('grafana_timeout'), + ) data = response.json() return data @@ -461,30 +476,28 @@ def _update(dashboard, profile): 'dashboard': dashboard, 'overwrite': True } - request_url = "{0}/api/dashboards/db".format(profile.get('grafana_url')) - if profile.get('grafana_token', False): - response = requests.post( - request_url, - headers=_get_headers(profile), - json=payload - ) - else: - response = requests.post( - request_url, - auth=_get_auth(profile), - json=payload - ) + response = requests.post( + "{0}/api/dashboards/db".format(profile.get('grafana_url')), + auth=_get_auth(profile), + headers=_get_headers(profile), + json=payload + ) return response.json() def _get_headers(profile): - return { - 'Accept': 'application/json', - 'Authorization': 'Bearer {0}'.format(profile['grafana_token']) - } + headers = {'Content-type': 'application/json'} + + if profile.get('grafana_token', False): + headers['Authorization'] = 'Bearer {0}'.format(profile['grafana_token']) + + return headers def _get_auth(profile): + if profile.get('grafana_token', False): + return None + return requests.auth.HTTPBasicAuth( profile['grafana_user'], profile['grafana_password'] @@ -568,4 +581,4 @@ def _stripped(d): for k, v in six.iteritems(d): if v: ret[k] = v - return ret \ No newline at end of file + return ret diff --git a/grafana/client.sls b/grafana/client.sls index 47cef61..1275f1b 100644 --- a/grafana/client.sls +++ b/grafana/client.sls @@ -36,52 +36,56 @@ grafana_client_datasource_{{ datasource_name }}: {%- if client.remote_data.engine == 'salt_mine' %} {%- for node_name, node_grains in salt['mine.get']('*', 'grains.items').iteritems() %} -{%- if node_grains.grafana is defined %} -{%- set raw_dict = salt['grains.filter_by']({'default': raw_dict}, merge=node_grains.grafana.get('dashboard', {})) %} -{%- endif %} + {%- if node_grains.grafana is defined %} + {%- set raw_dict = salt['grains.filter_by']({'default': raw_dict}, merge=node_grains.grafana.get('dashboard', {})) %} + {%- endif %} {%- endfor %} {%- endif %} {%- if client.dashboard is defined %} -{%- set raw_dict = salt['grains.filter_by']({'default': raw_dict}, merge=client.dashboard) %} + {%- set raw_dict = salt['grains.filter_by']({'default': raw_dict}, merge=client.dashboard) %} {%- endif %} {%- for dashboard_name, dashboard in raw_dict.iteritems() %} -{%- set rows = [] %} -{%- for row_name, row in dashboard.get('row', {}).iteritems() %} -{%- set panels = [] %} -{%- for panel_name, panel in row.get('panel', {}).iteritems() %} -{%- set targets = [] %} -{%- for target_name, target in panel.get('target', {}).iteritems() %} -{%- do targets.extend([target]) %} -{%- endfor %} -{%- do panel.update({'targets': targets}) %} -{%- do panels.extend([panel]) %} -{%- endfor %} -{%- do row.update({'panels': panels}) %} -{%- do rows.extend([row]) %} -{%- endfor %} -{%- do dashboard.update({'rows': rows}) %} -{%- do final_dict.update({dashboard_name: dashboard}) %} + {%- if dashboard.get('format', 'yaml')|lower == 'yaml' %} + # Dashboards in JSON format are considered as blob + {%- set rows = [] %} + {%- for row_name, row in dashboard.get('row', {}).iteritems() %} + {%- set panels = [] %} + {%- for panel_name, panel in row.get('panel', {}).iteritems() %} + {%- set targets = [] %} + {%- for target_name, target in panel.get('target', {}).iteritems() %} + {%- do targets.extend([target]) %} + {%- endfor %} + {%- do panel.update({'targets': targets}) %} + {%- do panels.extend([panel]) %} + {%- endfor %} + {%- do row.update({'panels': panels}) %} + {%- do rows.extend([row]) %} + {%- endfor %} + {%- do dashboard.update({'rows': rows}) %} + {%- endif %} + + {%- do final_dict.update({dashboard_name: dashboard}) %} {%- endfor %} {%- for dashboard_name, dashboard in final_dict.iteritems() %} - -{%- if dashboard.get('enabled', True) %} - + {%- if dashboard.get('enabled', True) %} grafana_client_dashboard_{{ dashboard_name }}: grafana3_dashboard.present: - name: {{ dashboard_name }} + {%- if dashboard.get('format', 'yaml')|lower == 'json' %} + {%- import_json dashboard.template as dash %} + - dashboard: {{ dash|json }} + - dashboard_format: json + {%- else %} - dashboard: {{ dashboard }} - -{%- else %} - + {%- endif %} + {%- else %} grafana_client_dashboard_{{ dashboard_name }}: grafana3_dashboard.absent: - name: {{ dashboard_name }} - -{%- endif %} - + {%- endif %} {%- endfor %} {%- endif %} diff --git a/grafana/collector.sls b/grafana/collector.sls index b1f76b1..4532547 100644 --- a/grafana/collector.sls +++ b/grafana/collector.sls @@ -13,6 +13,7 @@ grafana_grains_dir: {# Loading the other service support metadata for localhost #} {%- for service_name, service in pillar.iteritems() %} +{%- if service.get('_support', {}).get('grafana', {}).get('enabled', False) %} {%- macro load_grains_file(grains_fragment_file) %}{% include grains_fragment_file ignore missing %}{% endmacro %} @@ -20,6 +21,7 @@ grafana_grains_dir: {%- set grains_yaml = load_grains_file(grains_fragment_file)|load_yaml %} {%- set service_grains = salt['grains.filter_by']({'default': service_grains}, merge=grains_yaml) %} +{%- endif %} {%- endfor %} grafana_grain: diff --git a/metadata/service/collector.yml b/metadata/service/collector.yml new file mode 100644 index 0000000..f788dd0 --- /dev/null +++ b/metadata/service/collector.yml @@ -0,0 +1,6 @@ +applications: +- grafana +parameters: + grafana: + collector: + enabled: true