diff --git a/puppetboard/app.py b/puppetboard/app.py index 9455703..d443354 100644 --- a/puppetboard/app.py +++ b/puppetboard/app.py @@ -27,13 +27,13 @@ app.config.from_envvar('PUPPETBOARD_SETTINGS', silent=True) app.secret_key = os.urandom(24) puppetdb = connect( - api_version=3, - host=app.config['PUPPETDB_HOST'], - port=app.config['PUPPETDB_PORT'], - ssl=app.config['PUPPETDB_SSL'], - ssl_key=app.config['PUPPETDB_KEY'], - ssl_cert=app.config['PUPPETDB_CERT'], - timeout=app.config['PUPPETDB_TIMEOUT'],) + api_version=3, + host=app.config['PUPPETDB_HOST'], + port=app.config['PUPPETDB_PORT'], + ssl=app.config['PUPPETDB_SSL'], + ssl_key=app.config['PUPPETDB_KEY'], + ssl_cert=app.config['PUPPETDB_CERT'], + timeout=app.config['PUPPETDB_TIMEOUT'],) numeric_level = getattr(logging, app.config['LOGLEVEL'].upper(), None) if not isinstance(numeric_level, int): @@ -41,6 +41,7 @@ if not isinstance(numeric_level, int): logging.basicConfig(level=numeric_level) log = logging.getLogger(__name__) + def stream_template(template_name, **context): app.update_template_context(context) t = app.jinja_env.get_template(template_name) @@ -48,64 +49,92 @@ def stream_template(template_name, **context): rv.enable_buffering(5) return rv + @app.errorhandler(400) def bad_request(e): return render_template('400.html'), 400 + @app.errorhandler(403) def bad_request(e): return render_template('403.html'), 400 + @app.errorhandler(404) def not_found(e): return render_template('404.html'), 404 + @app.errorhandler(412) def precond_failed(e): """We're slightly abusing 412 to handle missing features depending on the API version.""" return render_template('412.html'), 412 + @app.errorhandler(500) def server_error(e): return render_template('500.html'), 500 + @app.route('/') def index(): - """This view generates the index page and displays a set of metrics and latest reports on - nodes fetched from PuppetDB. + """This view generates the index page and displays a set of metrics and + latest reports on nodes fetched from PuppetDB. """ # TODO: Would be great if we could parallelize this somehow, doing these # requests in sequence is rather pointless. - num_nodes = get_or_abort(puppetdb.metric, - 'com.puppetlabs.puppetdb.query.population:type=default,name=num-nodes') - num_resources = get_or_abort(puppetdb.metric, - 'com.puppetlabs.puppetdb.query.population:type=default,name=num-resources') - avg_resources_node = get_or_abort(puppetdb.metric, - 'com.puppetlabs.puppetdb.query.population:type=default,name=avg-resources-per-node') + prefix = 'com.puppetlabs.puppetdb.query.population' + num_nodes = get_or_abort( + puppetdb.metric, + "{0}{1}".format(prefix, ':type=default,name=num-nodes')) + num_resources = get_or_abort( + puppetdb.metric, + "{0}{1}".format(prefix, ':type=default,name=num-resources')) + avg_resources_node = get_or_abort( + puppetdb.metric, + "{0}{1}".format(prefix, ':type=default,name=avg-resources-per-node')) metrics = { - 'num_nodes': num_nodes['Value'], - 'num_resources': num_resources['Value'], - 'avg_resources_node': "{0:10.6f}".format(avg_resources_node['Value']), - } + 'num_nodes': num_nodes['Value'], + 'num_resources': num_resources['Value'], + 'avg_resources_node': "{0:10.6f}".format(avg_resources_node['Value']), + } - latest_event_count = puppetdb._query('aggregate-event-counts', query='["=", "latest-report?", true]', summarize_by='certname') - latest_event_count['noopskip'] = latest_event_count['noops']+latest_event_count['skips'] + latest_event_count = puppetdb._query( + 'aggregate-event-counts', + query='["=", "latest-report?", true]', + summarize_by='certname') - latest_events = puppetdb._query('event-counts', query='["=", "latest-report?", true]', summarize_by='certname') + latest_event_count['noopskip'] = ( + latest_event_count['noops'] + latest_event_count['skips']) + + latest_events = puppetdb._query( + 'event-counts', + query='["=", "latest-report?", true]', + summarize_by='certname') unreported = [] + unresponsive_window = datetime.utcnow() - ( + timedelta(hours=app.config['UNRESPONSIVE_HOURS'])) for node in puppetdb.nodes(): try: node_last_seen = node.report_timestamp.replace(tzinfo=None) - if node_last_seen < (datetime.utcnow()-timedelta(hours=app.config['UNRESPONSIVE_HOURS'])): - delta = (datetime.utcnow()-node_last_seen) - node.noresponse = str(delta.days) + "d " + str(int(delta.seconds/3600)) +"h " + str(int((delta.seconds%3600)/60))+ "m" + if node_last_seen < unresponsive_window: + delta = (datetime.utcnow() - node_last_seen) + node.noresponse = str(delta.days) + "d " + node.noresponse += str(int(delta.seconds / 3600)) + "h " + node.noresponse += str(int((delta.seconds % 3600) / 60)) + "m" unreported.append(node) except AttributeError: unreported.append(node) - return render_template('index.html', metrics=metrics, latest_event_count=latest_event_count, latest_events=latest_events, unreported=unreported) + return render_template( + 'index.html', + metrics=metrics, + latest_event_count=latest_event_count, + latest_events=latest_events, + unreported=unreported) + @app.route('/nodes') def nodes(): @@ -118,19 +147,26 @@ def nodes(): works. Once pagination is in place we can change this but we'll need to provide a search feature instead. """ - latest_events = puppetdb._query('event-counts', query='["=", "latest-report?", true]', summarize_by='certname') + latest_events = puppetdb._query( + 'event-counts', + query='["=", "latest-report?", true]', + summarize_by='certname') nodes = [] for node in yield_or_stop(puppetdb.nodes()): - # check if node name is contained in any of the event-counts (grouped by certname) - status = [s for s in latest_events if s['subject']['title'] == node.name] + # check if node name is contained in any of the + # event-counts (grouped by certname) + status = [ + s for s in latest_events if + s['subject']['title'] == node.name] if status: node.status = status[0] else: node.status = None nodes.append(node) - return Response(stream_with_context(stream_template('nodes.html', - nodes=nodes))) + return Response(stream_with_context( + stream_template('nodes.html', nodes=nodes))) + @app.route('/node/') def node(node_name): @@ -139,10 +175,14 @@ def node(node_name): heavy to do within a single request. """ node = get_or_abort(puppetdb.node, node_name) - facts = node.facts() + facts = node.facts() reports = ten_reports(node.reports()) - return render_template('node.html', node=node, facts=yield_or_stop(facts), - reports=yield_or_stop(reports)) + return render_template( + 'node.html', + node=node, + facts=yield_or_stop(facts), + reports=yield_or_stop(reports)) + @app.route('/reports') def reports(): @@ -150,14 +190,18 @@ def reports(): the last half our, something like that.""" return render_template('reports.html') + @app.route('/reports/') def reports_node(node): """Fetches all reports for a node and processes them eventually rendering a table displaying those reports.""" reports = ten_reports(yield_or_stop( puppetdb.reports('["=", "certname", "{0}"]'.format(node)))) - return render_template('reports_node.html', reports=reports, - nodename=node) + return render_template( + 'reports_node.html', + reports=reports, + nodename=node) + @app.route('/report/latest/') def report_latest(node_name): @@ -171,6 +215,7 @@ def report_latest(node_name): report = list(yield_or_stop(reports))[0] return redirect(url_for('report', node=node_name, report_id=report)) + @app.route('/report//') def report(node, report_id): """Displays a single report including all the events associated with that @@ -182,11 +227,14 @@ def report(node, report_id): if report.hash_ == report_id: events = puppetdb.events('["=", "report", "{0}"]'.format( report.hash_)) - return render_template('report.html', report=report, - events=yield_or_stop(events)) + return render_template( + 'report.html', + report=report, + events=yield_or_stop(events)) else: abort(404) + @app.route('/facts') def facts(): """Displays an alphabetical list of all facts currently known to @@ -202,17 +250,20 @@ def facts(): sorted_facts_dict = sorted(facts_dict.items()) return render_template('facts.html', facts_dict=sorted_facts_dict) + @app.route('/fact/') def fact(fact): """Fetches the specific fact from PuppetDB and displays its value per node for which this fact is known.""" # we can only consume the generator once, lists can be doubly consumed # om nom nom - localfacts = [ f for f in yield_or_stop(puppetdb.facts(name=fact)) ] - return Response(stream_with_context(stream_template('fact.html', + localfacts = [f for f in yield_or_stop(puppetdb.facts(name=fact))] + return Response(stream_with_context(stream_template( + 'fact.html', name=fact, facts=localfacts))) + @app.route('/query', methods=('GET', 'POST')) def query(): """Allows to execute raw, user created querries against PuppetDB. This is @@ -221,25 +272,32 @@ def query(): the JSON of the response or a message telling you what whent wrong / why nothing was returned.""" if app.config['ENABLE_QUERY']: - form = QueryForm() - if form.validate_on_submit(): - result = get_or_abort(puppetdb._query, form.endpoints.data, - query='[{0}]'.format(form.query.data)) - return render_template('query.html', form=form, result=result) - return render_template('query.html', form=form) + form = QueryForm() + if form.validate_on_submit(): + result = get_or_abort( + puppetdb._query, + form.endpoints.data, + query='[{0}]'.format(form.query.data)) + return render_template('query.html', form=form, result=result) + return render_template('query.html', form=form) else: log.warn('Access to query interface disabled by administrator..') abort(403) + @app.route('/metrics') def metrics(): - metrics = puppetdb._query('metrics', path='mbeans') - for key,value in metrics.iteritems(): - metrics[key]=value.split('/')[3] - return render_template('metrics.html', metrics=sorted(metrics.items())) + metrics = puppetdb._query('metrics', path='mbeans') + for key, value in metrics.iteritems(): + metrics[key] = value.split('/')[3] + return render_template('metrics.html', metrics=sorted(metrics.items())) + @app.route('/metric/') def metric(metric): - name = urllib.unquote(metric) - metric = puppetdb.metric(metric) - return render_template('metric.html', name=name, metric=sorted(metric.items())) + name = urllib.unquote(metric) + metric = puppetdb.metric(metric) + return render_template( + 'metric.html', + name=name, + metric=sorted(metric.items()))