Merge pull request #393 from mterzo/clean_error

Handle server exception when unable to connect early.
This commit is contained in:
Tim Meusel
2017-07-08 09:25:04 +02:00
committed by GitHub
8 changed files with 157 additions and 89 deletions

View File

@@ -10,7 +10,7 @@ install:
- pip install -r requirements-test.txt - pip install -r requirements-test.txt
- pip install -q coveralls --use-wheel - pip install -q coveralls --use-wheel
script: script:
- pytest - pytest --pep8
- if [ "${TRAVIS_PYTHON_VERSION}" != "2.6" ]; then - if [ "${TRAVIS_PYTHON_VERSION}" != "2.6" ]; then
pip install bandit; pip install bandit;
bandit -r puppetboard; bandit -r puppetboard;

View File

@@ -16,21 +16,22 @@ from flask import (
request, session, jsonify request, session, jsonify
) )
from pypuppetdb import connect
from pypuppetdb.QueryBuilder import * from pypuppetdb.QueryBuilder import *
from puppetboard.forms import QueryForm from puppetboard.forms import QueryForm
from puppetboard.utils import ( from puppetboard.utils import (get_or_abort, yield_or_stop,
get_or_abort, yield_or_stop, get_db_version, get_db_version)
jsonprint, prettyprint
)
from puppetboard.dailychart import get_daily_reports_chart from puppetboard.dailychart import get_daily_reports_chart
import werkzeug.exceptions as ex import werkzeug.exceptions as ex
import CommonMark import CommonMark
from puppetboard.core import get_app, get_puppetdb, environments
import puppetboard.errors
from . import __version__ from . import __version__
REPORTS_COLUMNS = [ REPORTS_COLUMNS = [
{'attr': 'end', 'filter': 'end_time', {'attr': 'end', 'filter': 'end_time',
'name': 'End time', 'type': 'datetime'}, 'name': 'End time', 'type': 'datetime'},
@@ -48,31 +49,15 @@ CATALOGS_COLUMNS = [
{'attr': 'form', 'name': 'Compare'}, {'attr': 'form', 'name': 'Compare'},
] ]
app = Flask(__name__) app = get_app()
app.config.from_object('puppetboard.default_settings')
graph_facts = app.config['GRAPH_FACTS'] graph_facts = app.config['GRAPH_FACTS']
app.config.from_envvar('PUPPETBOARD_SETTINGS', silent=True)
graph_facts += app.config['GRAPH_FACTS']
app.secret_key = app.config['SECRET_KEY']
app.jinja_env.filters['jsonprint'] = jsonprint
app.jinja_env.filters['prettyprint'] = prettyprint
puppetdb = connect(
host=app.config['PUPPETDB_HOST'],
port=app.config['PUPPETDB_PORT'],
ssl_verify=app.config['PUPPETDB_SSL_VERIFY'],
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) numeric_level = getattr(logging, app.config['LOGLEVEL'].upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % app.config['LOGLEVEL'])
logging.basicConfig(level=numeric_level) logging.basicConfig(level=numeric_level)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
puppetdb = get_puppetdb()
@app.template_global() @app.template_global()
def version(): def version():
@@ -87,29 +72,10 @@ def stream_template(template_name, **context):
return rv return rv
def url_for_field(field, value):
args = request.view_args.copy()
args.update(request.args.copy())
args[field] = value
return url_for(request.endpoint, **args)
def environments():
envs = get_or_abort(puppetdb.environments)
x = []
for env in envs:
x.append(env['name'])
return x
def check_env(env, envs): def check_env(env, envs):
if env != '*' and env not in envs: if env != '*' and env not in envs:
abort(404) abort(404)
app.jinja_env.globals['url_for_field'] = url_for_field
@app.context_processor @app.context_processor
def utility_processor(): def utility_processor():
@@ -119,38 +85,6 @@ def utility_processor():
return dict(now=now) return dict(now=now)
@app.errorhandler(400)
def bad_request(e):
envs = environments()
return render_template('400.html', envs=envs), 400
@app.errorhandler(403)
def forbidden(e):
envs = environments()
return render_template('403.html', envs=envs), 403
@app.errorhandler(404)
def not_found(e):
envs = environments()
return render_template('404.html', envs=envs), 404
@app.errorhandler(412)
def precond_failed(e):
"""We're slightly abusing 412 to handle missing features
depending on the API version."""
envs = environments()
return render_template('412.html', envs=envs), 412
@app.errorhandler(500)
def server_error(e):
envs = environments()
return render_template('500.html', envs=envs), 500
@app.route('/', defaults={'env': app.config['DEFAULT_ENVIRONMENT']}) @app.route('/', defaults={'env': app.config['DEFAULT_ENVIRONMENT']})
@app.route('/<env>/') @app.route('/<env>/')
def index(env): def index(env):

63
puppetboard/core.py Normal file
View File

@@ -0,0 +1,63 @@
from __future__ import unicode_literals
from __future__ import absolute_import
import logging
from flask import Flask
from pypuppetdb import connect
from puppetboard.utils import (jsonprint, prettyprint, url_for_field,
get_or_abort)
from . import __version__
APP = None
PUPPETDB = None
def get_app():
global APP
if APP is None:
app = Flask(__name__)
app.config.from_object('puppetboard.default_settings')
app.config.from_envvar('PUPPETBOARD_SETTINGS', silent=True)
app.secret_key = app.config['SECRET_KEY']
numeric_level = getattr(logging, app.config['LOGLEVEL'].upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % app.config['LOGLEVEL'])
app.jinja_env.filters['jsonprint'] = jsonprint
app.jinja_env.filters['prettyprint'] = prettyprint
app.jinja_env.globals['url_for_field'] = url_for_field
APP = app
return APP
def get_puppetdb():
global PUPPETDB
if PUPPETDB is None:
app = get_app()
puppetdb = connect(host=app.config['PUPPETDB_HOST'],
port=app.config['PUPPETDB_PORT'],
ssl_verify=app.config['PUPPETDB_SSL_VERIFY'],
ssl_key=app.config['PUPPETDB_KEY'],
ssl_cert=app.config['PUPPETDB_CERT'],
timeout=app.config['PUPPETDB_TIMEOUT'],)
PUPPETDB = puppetdb
return PUPPETDB
def environments():
puppetdb = get_puppetdb()
envs = get_or_abort(puppetdb.environments)
x = []
for env in envs:
x.append(env['name'])
return x

45
puppetboard/errors.py Normal file
View File

@@ -0,0 +1,45 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from puppetboard.core import get_app, environments
from werkzeug.exceptions import InternalServerError
from flask import render_template
from . import __version__
app = get_app()
@app.errorhandler(400)
def bad_request(e):
envs = environments()
return render_template('400.html', envs=envs), 400
@app.errorhandler(403)
def forbidden(e):
envs = environments()
return render_template('403.html', envs=envs), 403
@app.errorhandler(404)
def not_found(e):
envs = environments()
return render_template('404.html', envs=envs), 404
@app.errorhandler(412)
def precond_failed(e):
"""We're slightly abusing 412 to handle missing features
depending on the API version."""
envs = environments()
return render_template('412.html', envs=envs), 412
@app.errorhandler(500)
def server_error(e):
envs = []
try:
envs = environments()
except InternalServerError as e:
pass
return render_template('500.html', envs=envs), 500

View File

@@ -8,8 +8,7 @@ from math import ceil
from requests.exceptions import HTTPError, ConnectionError from requests.exceptions import HTTPError, ConnectionError
from pypuppetdb.errors import EmptyResponseError from pypuppetdb.errors import EmptyResponseError
from flask import abort from flask import abort, request, url_for
# Python 3 compatibility # Python 3 compatibility
try: try:
@@ -20,6 +19,13 @@ except NameError:
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def url_for_field(field, value):
args = request.view_args.copy()
args.update(request.args.copy())
args[field] = value
return url_for(request.endpoint, **args)
def jsonprint(value): def jsonprint(value):
return json.dumps(value, indent=2, separators=(',', ': ')) return json.dumps(value, indent=2, separators=(',', ': '))

View File

@@ -820,4 +820,4 @@ def test_node_facts_json(client, mocker,
for line in result_json['data']: for line in result_json['data']:
assert len(line) == 2 assert len(line) == 2
assert 'chart' not in result_json assert 'chart' not in result_json

View File

@@ -1,7 +1,9 @@
import pytest import pytest
from flask import Flask, current_app from flask import Flask, current_app
from werkzeug.exceptions import InternalServerError
from puppetboard import app from puppetboard import app
from puppetboard.errors import (bad_request, forbidden, not_found,
precond_failed, server_error)
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@@ -16,9 +18,17 @@ def mock_puppetdb_environments(mocker):
return_value=environemnts) return_value=environemnts)
@pytest.fixture
def mock_server_error(mocker):
def raiseInternalServerError():
raise InternalServerError('Hello world')
return mocker.patch('puppetboard.core.environments',
side_effect=raiseInternalServerError)
def test_error_bad_request(mock_puppetdb_environments): def test_error_bad_request(mock_puppetdb_environments):
with app.app.test_request_context(): with app.app.test_request_context():
(output, error_code) = app.bad_request(None) (output, error_code) = bad_request(None)
soup = BeautifulSoup(output, 'html.parser') soup = BeautifulSoup(output, 'html.parser')
assert 'The request sent to PuppetDB was invalid' in soup.p.text assert 'The request sent to PuppetDB was invalid' in soup.p.text
@@ -27,7 +37,7 @@ def test_error_bad_request(mock_puppetdb_environments):
def test_error_forbidden(mock_puppetdb_environments): def test_error_forbidden(mock_puppetdb_environments):
with app.app.test_request_context(): with app.app.test_request_context():
(output, error_code) = app.forbidden(None) (output, error_code) = forbidden(None)
soup = BeautifulSoup(output, 'html.parser') soup = BeautifulSoup(output, 'html.parser')
long_string = "%s %s" % ('What you were looking for has', long_string = "%s %s" % ('What you were looking for has',
@@ -38,7 +48,7 @@ def test_error_forbidden(mock_puppetdb_environments):
def test_error_not_found(mock_puppetdb_environments): def test_error_not_found(mock_puppetdb_environments):
with app.app.test_request_context(): with app.app.test_request_context():
(output, error_code) = app.not_found(None) (output, error_code) = not_found(None)
soup = BeautifulSoup(output, 'html.parser') soup = BeautifulSoup(output, 'html.parser')
long_string = "%s %s" % ('What you were looking for could not', long_string = "%s %s" % ('What you were looking for could not',
@@ -49,7 +59,7 @@ def test_error_not_found(mock_puppetdb_environments):
def test_error_precond(mock_puppetdb_environments): def test_error_precond(mock_puppetdb_environments):
with app.app.test_request_context(): with app.app.test_request_context():
(output, error_code) = app.precond_failed(None) (output, error_code) = precond_failed(None)
soup = BeautifulSoup(output, 'html.parser') soup = BeautifulSoup(output, 'html.parser')
long_string = "%s %s" % ('You\'ve configured Puppetboard with an API', long_string = "%s %s" % ('You\'ve configured Puppetboard with an API',
@@ -60,8 +70,16 @@ def test_error_precond(mock_puppetdb_environments):
def test_error_server(mock_puppetdb_environments): def test_error_server(mock_puppetdb_environments):
with app.app.test_request_context(): with app.app.test_request_context():
(output, error_code) = app.server_error(None) (output, error_code) = server_error(None)
soup = BeautifulSoup(output, 'html.parser') soup = BeautifulSoup(output, 'html.parser')
assert 'Internal Server Error' in soup.h2.text assert 'Internal Server Error' in soup.h2.text
assert error_code == 500 assert error_code == 500
def test_early_error_server(mock_server_error):
with app.app.test_request_context():
(output, error_code) = server_error(None)
soup = BeautifulSoup(output, 'html.parser')
assert 'Internal Server Error' in soup.h2.text
assert error_code == 500

View File

@@ -1,7 +1,7 @@
import pytest import pytest
import os import os
from puppetboard import docker_settings from puppetboard import docker_settings
from puppetboard import app import puppetboard.core
try: try:
import future.utils import future.utils
@@ -100,12 +100,14 @@ def test_graph_facts_custom(cleanUpEnv):
assert 'extra' in facts assert 'extra' in facts
def test_bad_log_value(cleanUpEnv): def test_bad_log_value(cleanUpEnv, mocker):
os.environ['LOGLEVEL'] = 'g' os.environ['LOGLEVEL'] = 'g'
os.environ['PUPPETBOARD_SETTINGS'] = '../puppetboard/docker_settings.py' os.environ['PUPPETBOARD_SETTINGS'] = '../puppetboard/docker_settings.py'
reload(docker_settings) reload(docker_settings)
puppetboard.core.APP = None
with pytest.raises(ValueError) as error: with pytest.raises(ValueError) as error:
reload(app) puppetboard.core.get_app()
def test_default_table_selctor(cleanUpEnv): def test_default_table_selctor(cleanUpEnv):