From a3473abf61d60aa2486e2d24ba23fbfea7f844f0 Mon Sep 17 00:00:00 2001 From: Corey Hammerton Date: Wed, 23 Sep 2015 11:16:20 -0400 Subject: [PATCH 1/4] puppetboard: Adding a more intuitive catalog view A new endpoint in the header, Catalogs, takes the user to a page with a node table similar to that in the nodes page. This table shows the node with a link to the node page, the catalog timestamp with a link to the catalog page and a small form with a select field to be used to compare the catalog of this row's node with that of another node. --- puppetboard/app.py | 81 +++++++++++++++++++- puppetboard/forms.py | 10 ++- puppetboard/templates/catalog_compare.html | 87 ++++++++++++++++++++++ puppetboard/templates/catalogs.html | 40 ++++++++++ puppetboard/templates/layout.html | 1 + 5 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 puppetboard/templates/catalog_compare.html create mode 100644 puppetboard/templates/catalogs.html diff --git a/puppetboard/app.py b/puppetboard/app.py index 7662f10..de0d54a 100644 --- a/puppetboard/app.py +++ b/puppetboard/app.py @@ -18,7 +18,7 @@ from flask_wtf.csrf import CsrfProtect from pypuppetdb import connect -from puppetboard.forms import QueryForm +from puppetboard.forms import (CatalogForm, QueryForm) from puppetboard.utils import ( get_or_abort, yield_or_stop, limit_reports, jsonprint @@ -404,6 +404,45 @@ def metric(metric): name=name, metric=sorted(metric.items())) +@app.route('/catalogs') +def catalogs(): + if app.config['ENABLE_CATALOG']: + nodenames = [] + catalog_list = [] + nodes = get_or_abort(puppetdb.nodes, + query='["null?", "catalog_timestamp", false]', + with_status=False, + order_by='[{"field": "certname", "order": "asc"}]') + nodes, temp = tee(nodes) + + for node in temp: + nodenames.append(node.name) + + for node in nodes: + table_row = { + 'name': node.name, + 'catalog_timestamp': node.catalog_timestamp + } + + if len(nodenames) > 1: + form = CatalogForm() + + form.compare.data = node.name + form.against.choices = [(x, x) for x in nodenames + if x != node.name] + table_row['form'] = form + else: + table_row['form'] = None + + catalog_list.append(table_row) + + return render_template( + 'catalogs.html', + nodes=catalog_list) + else: + log.warn('Access to catalogs endpoint disabled by administrator') + abort(403) + @app.route('/catalog/') def catalog_node(node_name): """Fetches from PuppetDB the compiled catalog of a given node.""" @@ -413,3 +452,43 @@ def catalog_node(node_name): else: log.warn('Access to catalog interface disabled by administrator') abort(403) + +@app.route('/catalog/submit', methods=['POST']) +def catalog_submit(): + """Receives the submitted form data from the catalogs page and directs + the users to the comparison page. Directs users back to the catalogs + page if no form submission data is found. + """ + if app.config['ENABLE_CATALOG']: + form = CatalogForm(request.form) + + form.against.choices = [(form.against.data, form.against.data)] + if form.validate_on_submit(): + compare = form.compare.data + against = form.against.data + return redirect( + url_for('catalog_compare', + compare=compare, + against=against)) + return redirect(url_for('catalogs')) + else: + log.warn('Access to catalog interface disabled by administrator') + abort(403) + +@app.route('/catalogs/compare/...') +def catalog_compare(compare, against): + """Compares the catalog of one node, parameter compare, with that of + with that of another node, parameter against. + """ + if app.config['ENABLE_CATALOG']: + compare_cat = get_or_abort(puppetdb.catalog, + node=compare) + against_cat = get_or_abort(puppetdb.catalog, + node=against) + + return render_template('catalog_compare.html', + compare=compare_cat, + against=against_cat) + else: + log.warn('Access to catalog interface disabled by administrator') + abort(403) diff --git a/puppetboard/forms.py b/puppetboard/forms.py index f4667c3..5be3b1f 100644 --- a/puppetboard/forms.py +++ b/puppetboard/forms.py @@ -2,7 +2,10 @@ from __future__ import unicode_literals from __future__ import absolute_import from flask.ext.wtf import Form -from wtforms import RadioField, TextAreaField, validators +from wtforms import ( + HiddenField, RadioField, SelectField, + TextAreaField, validators +) class QueryForm(Form): @@ -18,3 +21,8 @@ class QueryForm(Form): ('reports', 'Reports'), ('events', 'Events'), ]) + +class CatalogForm(Form): + """The form used to compare the catalogs of different nodes.""" + compare = HiddenField('compare') + against = SelectField(u'against') diff --git a/puppetboard/templates/catalog_compare.html b/puppetboard/templates/catalog_compare.html new file mode 100644 index 0000000..705210a --- /dev/null +++ b/puppetboard/templates/catalog_compare.html @@ -0,0 +1,87 @@ +{% extends 'layout.html' %} +{% block content %} + + + + + + + + + + + + + + + + + + + +

Comparing

Against

{{compare.node}}{{against.node}}
+ + + + + + {% for resource in compare.get_resources() %} + + + + {% endfor %} + +
Resources
{{resource.type_}}[{{resource.name}}]
+
+ + + + + + {% for resource in against.get_resources() %} + + + + {% endfor %} + +
Resources
{{resource.type_}}[{{resource.name}}]
+
+ + + + + + + + + + {% for edge in compare.get_edges() %} + + + + + + {% endfor %} + +
Edges->Target
{{edge.source}}{{edge.relationship}}{{edge.target}}
+
+ + + + + + + + + + {% for edge in against.get_edges() %} + + + + + + {% endfor %} + +
Edge->Target
{{edge.source}}{{edge.relationship}}{{edge.target}}
+
+{% endblock content %} diff --git a/puppetboard/templates/catalogs.html b/puppetboard/templates/catalogs.html new file mode 100644 index 0000000..52dca2b --- /dev/null +++ b/puppetboard/templates/catalogs.html @@ -0,0 +1,40 @@ +{% extends 'layout.html' %} +{% import '_macros.html' as macros %} +{% block content %} +
+ +
+ + + + + + + + + + + {% for node in nodes %} + + + + + + + {% endfor %} + +
HostnameCompile TimeCompare With
{{node.name}}{{node.catalog_timestamp}} + {% if node.form %} +
+
+ {{node.form.csrf_token}} +
+ {{node.form.compare}} + {{node.form.against}} + +
+
+
+ {% endif %} +
+{% endblock content %} diff --git a/puppetboard/templates/layout.html b/puppetboard/templates/layout.html index 910bab3..6e4a3e4 100644 --- a/puppetboard/templates/layout.html +++ b/puppetboard/templates/layout.html @@ -34,6 +34,7 @@ ('reports', 'Reports'), ('metrics', 'Metrics'), ('inventory', 'Inventory'), + ('catalogs', 'Catalogs'), ('query', 'Query'), ] %} Date: Tue, 6 Oct 2015 10:42:32 +0200 Subject: [PATCH 2/4] Use pypuppetdb with api version 4 --- puppetboard/app.py | 9 ++++----- requirements.txt | 2 +- setup.py | 3 ++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/puppetboard/app.py b/puppetboard/app.py index 92ea0e5..29fe40a 100644 --- a/puppetboard/app.py +++ b/puppetboard/app.py @@ -37,7 +37,6 @@ app.secret_key = app.config['SECRET_KEY'] app.jinja_env.filters['jsonprint'] = jsonprint puppetdb = connect( - api_version=3, host=app.config['PUPPETDB_HOST'], port=app.config['PUPPETDB_PORT'], ssl_verify=app.config['PUPPETDB_SSL_VERIFY'], @@ -102,7 +101,7 @@ def index(): """ # TODO: Would be great if we could parallelize this somehow, doing these # requests in sequence is rather pointless. - prefix = 'com.puppetlabs.puppetdb.query.population' + prefix = 'puppetlabs.puppetdb.query.population' num_nodes = get_or_abort( puppetdb.metric, "{0}{1}".format(prefix, ':type=default,name=num-nodes')) @@ -272,7 +271,7 @@ def reports_node(node_name): a table displaying those reports.""" reports = limit_reports( yield_or_stop( - puppetdb.reports('["=", "certname", "{0}"]'.format(node_name))), + puppetdb.reports(query='["=", "certname", "{0}"]'.format(node_name))), app.config['REPORTS_COUNT']) return render_template( 'reports_node.html', @@ -307,11 +306,11 @@ def report(node_name, report_id): configuration_version. This allows for better integration into puppet-hipchat. """ - reports = puppetdb.reports('["=", "certname", "{0}"]'.format(node_name)) + reports = puppetdb.reports(query='["=", "certname", "{0}"]'.format(node_name)) for report in reports: if report.hash_ == report_id or report.version == report_id: - events = puppetdb.events('["=", "report", "{0}"]'.format( + events = puppetdb.events(query='["=", "report", "{0}"]'.format( report.hash_)) return render_template( 'report.html', diff --git a/requirements.txt b/requirements.txt index f9f044a..7ae3fa7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,5 @@ MarkupSafe==0.19 WTForms==1.0.5 Werkzeug==0.9.4 itsdangerous==0.23 -pypuppetdb==0.1.1 +git+https://github.com/raphink/pypuppetdb@v4-api#egg=pypuppetdb-0.1.0 requests==2.2.1 diff --git a/setup.py b/setup.py index c7cf0ee..f75874c 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,8 @@ setup( "Flask >= 0.10.1", "Flask-WTF >= 0.9.4, <= 0.9.5", "WTForms < 2.0", - "pypuppetdb >= 0.1.0, < 0.2.0", + dependency_links=[ + "git+https://github.com/raphink/pypuppetdb@v4-api#egg=pypuppetdb", ], keywords="puppet puppetdb puppetboard", classifiers=[ From b539fc9475498e7a5b3a5cf8c33078c5377c8e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Can=C3=A9vet?= Date: Sun, 25 Oct 2015 09:52:42 +0100 Subject: [PATCH 3/4] Fix dependencies now that pypuppetdb 0.2.0 is released --- requirements.txt | 2 +- setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7ae3fa7..cc8a991 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,5 @@ MarkupSafe==0.19 WTForms==1.0.5 Werkzeug==0.9.4 itsdangerous==0.23 -git+https://github.com/raphink/pypuppetdb@v4-api#egg=pypuppetdb-0.1.0 +pypuppetdb==0.2.0 requests==2.2.1 diff --git a/setup.py b/setup.py index f75874c..bb23be7 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,7 @@ setup( "Flask >= 0.10.1", "Flask-WTF >= 0.9.4, <= 0.9.5", "WTForms < 2.0", - dependency_links=[ - "git+https://github.com/raphink/pypuppetdb@v4-api#egg=pypuppetdb", + "pypuppetdb >= 0.2.0, < 0.3.0", ], keywords="puppet puppetdb puppetboard", classifiers=[ From e0866a12ea8f1db553587080e683240e8019d083 Mon Sep 17 00:00:00 2001 From: Corey Hammerton Date: Wed, 28 Oct 2015 19:54:16 -0400 Subject: [PATCH 4/4] puppetboard/catalog: Making the catalog tables searchable Also standardizing the form declarations --- puppetboard/forms.py | 2 +- puppetboard/templates/catalog.html | 2 +- puppetboard/templates/catalog_compare.html | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/puppetboard/forms.py b/puppetboard/forms.py index 5be3b1f..17243c2 100644 --- a/puppetboard/forms.py +++ b/puppetboard/forms.py @@ -25,4 +25,4 @@ class QueryForm(Form): class CatalogForm(Form): """The form used to compare the catalogs of different nodes.""" compare = HiddenField('compare') - against = SelectField(u'against') + against = SelectField('against') diff --git a/puppetboard/templates/catalog.html b/puppetboard/templates/catalog.html index 3a43248..782606c 100644 --- a/puppetboard/templates/catalog.html +++ b/puppetboard/templates/catalog.html @@ -48,7 +48,7 @@ Target - + {% for edge in catalog.get_edges() %} {{edge.source}} diff --git a/puppetboard/templates/catalog_compare.html b/puppetboard/templates/catalog_compare.html index 705210a..a70d8c2 100644 --- a/puppetboard/templates/catalog_compare.html +++ b/puppetboard/templates/catalog_compare.html @@ -1,5 +1,8 @@ {% extends 'layout.html' %} {% block content %} +
+ +
@@ -16,7 +19,7 @@ - + {% for resource in compare.get_resources() %} @@ -30,7 +33,7 @@ - + {% for resource in against.get_resources() %} @@ -50,7 +53,7 @@ - + {% for edge in compare.get_edges() %} @@ -70,7 +73,7 @@ - + {% for edge in against.get_edges() %}
Resources
{{resource.type_}}[{{resource.name}}]
Resources
{{resource.type_}}[{{resource.name}}]Target
{{edge.source}}Target
{{edge.source}}