Add a command for generating a static version of the ARA webapp

Closes: #3
This commit is contained in:
David Moreau-Simard
2016-05-26 12:46:21 -04:00
parent 37aa9d8ddd
commit 6e207ccd53
10 changed files with 155 additions and 9 deletions

View File

@@ -1,5 +1,8 @@
language: python
python:
- "2.7"
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y tree
install: "pip install tox"
script: bash run_tests.sh

47
ara/cli/static.py Normal file
View File

@@ -0,0 +1,47 @@
# Copyright Red Hat, Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import logging
from cliff.command import Command
from ara import app, static
class Generate(Command):
"""Generates a static tree of the web application"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(Generate, self).get_parser(prog_name)
parser.add_argument(
'--path', '-p',
metavar='<path>',
required=True,
help='Path where the static files will be built in',
)
return parser
def take_action(self, parsed_args):
app.config['FREEZER_DESTINATION'] = parsed_args.path
try:
print('Generating static files at {}...'.format(parsed_args.path))
static.freezer.freeze()
except Exception as e:
# TODO: (dmsimard) do some proper exception handling
print('Could not successfully generate files: {}'.format(str(e)))
return False
print('Done.')
return True

View File

@@ -57,3 +57,6 @@ SQLALCHEMY_DATABASE_URI = get_config(config, 'ara', 'database',
SQLALCHEMY_ECHO = get_config(config, 'ara', 'sqldebug',
'ARA_SQL_DEBUG',
DEFAULT_ARA_SQL_DEBUG)
# Static generation
FREEZER_RELATIVE_URLS = True
FREEZER_DEFAULT_MIMETYPE = 'text/html'

18
ara/static.py Normal file
View File

@@ -0,0 +1,18 @@
# Copyright 2016 Red Hat, Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from flask_frozen import Freezer
from ara import app
freezer = Freezer(app)

View File

@@ -14,7 +14,7 @@ def playbook_summary():
stats=stats)
@playbook.route('/<playbook>')
@playbook.route('/<playbook>/')
def show_playbook(playbook):
playbook = models.Playbook.query.get(playbook)
if playbook is None:
@@ -34,8 +34,10 @@ def show_playbook(playbook):
tasks=tasks)
@playbook.route('/<playbook>/results')
def playbook_results(playbook):
@playbook.route('/<playbook>/results/')
@playbook.route('/<playbook>/results/<host>/')
@playbook.route('/<playbook>/results/<host>/<status>')
def playbook_results(playbook, host=None, status=None):
playbook = models.Playbook.query.get(playbook)
if playbook is None:
abort(404)
@@ -47,15 +49,17 @@ def playbook_results(playbook):
.filter(models.Playbook.id == playbook.id)
.order_by(models.TaskResult.time_start))
if request.args.get('host'):
hosts = [str(host) for host in request.args.get('host').split(',')]
host = host or request.args.get('host')
if host is not None:
hosts = [str(h) for h in host.split(',')]
task_results = (task_results
.filter(models.Host.name.in_(hosts)))
# LKS: We're filtering this with Python rather than SQL. This
# may become relevant if we implement result paging.
if request.args.get('status'):
status = request.args.get('status').split(',')
status = status or request.args.get('status')
if status is not None:
status = status.split(',')
task_results = (res for res in task_results
if res.derived_status in status)

View File

@@ -172,3 +172,70 @@ bundled with ARA::
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
.. _any other Flask application: http://flask.pocoo.org/docs/0.10/deploying/uwsgi/
Generating a static version of the web application
--------------------------------------------------
ARA is able to generate a static html version of it's dynamic, database-driven
web application.
This can be useful if you need to browse the results of playbook runs without
having to rely on the database backend configured.
For example, in the context of continuous integration, you could run an Ansible
job with ARA, generate a static version and then recover the resulting build as
artifacts of the jobs, allowing you to browse the results in-place.
The ARA CLI client provides a command to generate a static version::
$ ara help generate
usage: ara generate [-h] --path <path>
Generates a static tree of the web application
optional arguments:
-h, --help show this help message and exit
--path <path>, -p <path>
Path where the static files will be built in
$ ara generate --path /tmp/build/
Generating static files at /tmp/build/...
Done.
$ tree /tmp/build/
/tmp/build/
├── host
│   ├── anotherhost
│   ├── index.html
│   └── localhost
├── index.html
├── play
│   └── play
│   └── 6ec9ef1d-dd73-4378-8347-1242f6be8f1e
├── playbook
│   ├── bf81a7db-b549-49d9-b10e-19918225ec60
│   │   ├── index.html
│   │   └── results
│   │   ├── anotherhost
│   │   │   ├── index.html
│   │   │   └── ok
│   │   └── localhost
│   │   ├── index.html
│   │   └── ok
│   └── index.html
├── result
│   ├── 136100f7-fba7-44ba-83fc-1194509ad2dd
│   ├── 37532523-b2ec-4931-bb73-3c7e5c6fa7bf
│   ├── 3cef2a10-8f41-4f01-bc49-12bed179d7e9
│   └── e3b7e172-c6e4-4ee4-b4bc-9a51ff84decb
├── static
│   ├── css
│   │   ├── ara.css
│   │   ├── bootstrap.min.css
│   │   └── bootstrap-theme.min.css
│   └── js
│   ├── bootstrap.min.js
│   └── jquery-2.2.3.min.js
└── task
├── 570fe763-69bb-4141-80d4-578189c5938b
└── 946e1bc6-28b9-4f2f-ad4f-75b3c6c9032d
13 directories, 22 files

View File

@@ -1,6 +1,7 @@
Flask
Flask-SQLAlchemy
Flask-Script
Frozen-Flask
pbr>=1.6
decorator
cliff

View File

@@ -60,3 +60,4 @@ ara.cli =
host show = ara.cli.host:HostShow
stats list = ara.cli.stats:StatsList
stats show = ara.cli.stats:StatsShow
generate = ara.cli.static:Generate

View File

@@ -93,14 +93,14 @@ class TestApp(TestCase):
@pytest.mark.complete
def test_show_playbook(self):
self.ansible_run()
res = self.client.get('/playbook/{}'.format(
res = self.client.get('/playbook/{}/'.format(
self.ctx['playbook'].id))
self.assertEqual(res.status_code, 200)
@pytest.mark.incomplete
def test_show_playbook_incomplete(self):
self.ansible_run(complete=False)
res = self.client.get('/playbook/{}'.format(
res = self.client.get('/playbook/{}/'.format(
self.ctx['playbook'].id))
self.assertEqual(res.status_code, 200)

View File

@@ -25,6 +25,7 @@ whitelist_externals = bash
rm
commands =
rm -f "{toxinidir}/tests/integration/ansible.sqlite"
rm -rf "{toxinidir}/tests/integration/build"
ansible-playbook -vv {toxinidir}/tests/integration/playbook.yml
bash -c "ara host show $(ara host list -c ID -f value |head -n1)"
bash -c "ara play show $(ara play list -c ID -f value |head -n1)"
@@ -32,6 +33,7 @@ commands =
bash -c "ara result show $(ara result list -c ID -f value |head -n1) --long"
bash -c "ara stats show $(ara stats list -c ID -f value |head -n1)"
bash -c "ara task show $(ara task list -c ID -f value |head -n1)"
bash -c "ara generate --path {toxinidir}/tests/integration/build && tree {toxinidir}/tests/integration/build"
setenv =
ANSIBLE_CALLBACK_PLUGINS = {toxinidir}/ara/callback
ARA_DATABASE = sqlite:////{toxinidir}/tests/integration/ansible.sqlite