Add ara_record module to record key/value pairs

ara_record is a new Ansible module specific to ARA.
It is meant for users to register any key value pair throughout
their playbook execution so that it is available through CLI or the
web interface.

Change-Id: I480011a4037ecdc561183b7b7cb334011d1f4b10
This commit is contained in:
David Moreau-Simard
2016-10-28 18:53:18 -04:00
committed by David Moreau Simard
parent 08f8e3d1b3
commit f4353a941e
10 changed files with 293 additions and 0 deletions

View File

@@ -17,6 +17,7 @@ import os
from ansible.constants import get_config, load_config_file
DEFAULT_ARA_DIR = os.path.expanduser('~/.ara')
DEFAULT_ARA_TMPDIR = os.path.expanduser('~/.ansible/tmp')
DEFAULT_DATABASE_PATH = os.path.join(DEFAULT_ARA_DIR, 'ansible.sqlite')
DEFAULT_DATABASE = 'sqlite:///{}'.format(DEFAULT_DATABASE_PATH)
DEFAULT_ARA_LOGFILE = os.path.join(DEFAULT_ARA_DIR, 'ara.log')
@@ -31,6 +32,9 @@ config, path = load_config_file()
ARA_DIR = get_config(
config, 'ara', 'dir', 'ARA_DIR',
DEFAULT_ARA_DIR)
ARA_TMP_DIR = get_config(
config, 'defaults', 'local_tmp', 'ANSIBLE_LOCAL_TEMP',
DEFAULT_ARA_TMPDIR, istmppath=True)
ARA_LOG_FILE = get_config(
config, 'ara', 'logfile', 'ARA_LOG_FILE',
DEFAULT_ARA_LOGFILE)

View File

@@ -0,0 +1,32 @@
"""ara_record data
Revision ID: e8e78fd08bf2
Revises: da9459a1f71c
Create Date: 2016-11-01 18:02:38.685998
"""
# flake8: noqa
# revision identifiers, used by Alembic.
revision = 'e8e78fd08bf2'
down_revision = 'da9459a1f71c'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table('data',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('playbook_id', sa.String(length=36), nullable=True),
sa.Column('key', sa.String(length=255), nullable=True),
sa.Column('value', sa.Text(length=16777215), nullable=True),
sa.ForeignKeyConstraint(['playbook_id'], ['playbooks.id'], ondelete='RESTRICT'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('playbook_id', 'key')
)
### end Alembic commands ###
def downgrade():
op.drop_table('data')
### end Alembic commands ###

View File

@@ -124,6 +124,7 @@ class Playbook(db.Model, TimedEntity):
`Playbook` entities have the following relationships:
- `data` -- a list of k/v pairs recorded in this playbook run.
- `plays` -- a list of plays encountered in this playbook run.
- `tasks` -- a list of tasks encountered in this playbook run.
- `stats` -- a list of statistic records, one for each host
@@ -137,6 +138,7 @@ class Playbook(db.Model, TimedEntity):
id = std_pkey()
path = db.Column(db.Text)
data = one_to_many('Data', backref='playbook')
files = one_to_many('File', backref='playbook')
plays = one_to_many('Play', backref='playbook')
tasks = one_to_many('Task', backref='playbook')
@@ -387,3 +389,26 @@ class Stats(db.Model):
def __repr__(self):
return '<Stats for %s>' % self.host.name
class Data(db.Model):
'''The `Data` object represents a recorded key/value pair provided by
the ara_record module.
A `Data` entity has the following relationships:
- `playbook` -- the playbook this key/value pair was recorded in
'''
__tablename__ = 'data'
__table_args__ = (
db.UniqueConstraint('playbook_id', 'key'),
)
id = std_pkey()
playbook_id = std_fkey('playbooks.id')
key = db.Column(db.String(255))
value = db.Column(db.Text(16777215))
def __repr__(self):
return '<Data %s:%s>' % (self.data.playbook_id, self.data.key)

View File

View File

@@ -0,0 +1,133 @@
# 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 json
import os
from ansible.plugins.action import ActionBase
try:
from ara import app, models
from ara.models import db
HAS_ARA = True
except ImportError:
HAS_ARA = False
DOCUMENTATION = '''
---
module: ara_record
short_description: Ansible module to record persistent data with ARA.
version_added: "2.0"
author: "RDO Community <rdo-list@redhat.com>"
description:
- Ansible module to record persistent data with ARA.
options:
key:
description:
- Name of the key to write data to
required: true
value:
description:
- Value of the key written to
required: true
requirements:
- "python >= 2.6"
- "ara >= 0.10.0"
'''
EXAMPLES = '''
# Write static data
- ara_record:
key: "foo"
value: "bar"
# Write dynamic data
- shell: cd dev && git rev-parse HEAD
register: git_version
delegate_to: localhost
- ara_record:
key: "git_version"
value: "{{ git_version.stdout }}"
'''
class ActionModule(ActionBase):
''' Record persistent data as key/value pairs in ARA '''
TRANSFERS_FILES = False
VALID_ARGS = frozenset(('key', 'value'))
def create_or_update_key(self, playbook_id, key, value):
try:
data = (models.Data.query
.filter_by(key=key)
.filter_by(playbook_id=playbook_id)
.one())
data.value = value
except models.NoResultFound:
data = models.Data(playbook_id=playbook_id,
key=key,
value=value)
db.session.add(data)
db.session.commit()
return data
def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
if not HAS_ARA:
result = {
"failed": True,
"msg": "ARA is required to run this module."
}
return result
for arg in self._task.args:
if arg not in self.VALID_ARGS:
result = {
"failed": True,
"msg": "'{0}' is not a valid option.".format(arg)
}
return result
result = super(ActionModule, self).run(tmp, task_vars)
key = self._task.args.get('key', None)
value = self._task.args.get('value', None)
required = ['key', 'value']
for parameter in required:
if not self._task.args.get(parameter):
result['failed'] = True
result['msg'] = "{} parameter is required".format(parameter)
return result
# Retrieve the persisted playbook_id from tmpfile
tmpfile = os.path.join(app.config['ARA_TMP_DIR'], 'ara.json')
with open(tmpfile) as file:
data = json.load(file)
playbook_id = data['playbook']['id']
try:
self.create_or_update_key(playbook_id, key, value)
result['msg'] = "Data recorded in ARA for this playbook."
except Exception as e:
result['failed'] = True
result['msg'] = "Data not recorded in ARA: {0}".format(str(e))
return result

View File

@@ -263,6 +263,16 @@ class CallbackModule(CallbackBase):
file_ = self.get_or_create_file(path)
file_.is_playbook = True
# We need to persist the playbook id so it can be used by the modules
data = {
'playbook': {
'id': self.playbook.id
}
}
tmpfile = os.path.join(app.config['ARA_TMP_DIR'], 'ara.json')
with open(tmpfile, 'w') as file:
file.write(json.dumps(data))
def v2_playbook_on_play_start(self, play):
self.close_task()
self.close_play()

View File

View File

@@ -0,0 +1,60 @@
# 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.
#
# This file is purposefully left empty due to an Ansible issue
# Details at: https://github.com/ansible/ansible/pull/18208
# TODO: Remove this file and update the documentation when the issue is fixed,
# released and present in all supported versions.
DOCUMENTATION = '''
---
module: ara_record
short_description: Ansible module to record data with ARA
version_added: "2.0"
author: "RDO Community <rdo-list@redhat.com>"
description:
- Ansible module to record data with ARA. This module should always be
executed wherever the playbook is run from.
options:
key:
description:
- Name of the key to write data to
required: true
value:
description:
- Value of the key written to
required: true
requirements:
- "python >= 2.6"
- "ara >= 0.10.0"
'''
EXAMPLES = '''
# Write static data
- ara_record:
key: "foo"
value: "bar"
# Write dynamic data
- shell: cd dev && git rev-parse HEAD
register: git_version
delegate_to: localhost
- ara_record:
key: "git_version"
value: "{{ git_version.stdout }}"
'''

View File

@@ -20,6 +20,9 @@ an example that covers most common locations:
[defaults]
callback_plugins = /usr/lib/python2.7/site-packages/ara/plugins/callbacks:$VIRTUAL_ENV/lib/python2.7/site-packages/ara/plugins/callbacks:/usr/local/lib/python2.7/dist-packages/ara/plugins/callbacks
# If you'd like to use the ara_record module, you'll also need the following:
action_plugins = /usr/lib/python2.7/site-packages/ara/plugins/actions:$VIRTUAL_ENV/lib/python2.7/site-packages/ara/plugins/actions:/usr/local/lib/python2.7/dist-packages/ara/plugins/actions
library = /usr/lib/python2.7/site-packages/ara/plugins/modules:$VIRTUAL_ENV/lib/python2.7/site-packages/ara/plugins/modules:/usr/local/lib/python2.7/dist-packages/ara/plugins/modules
.. _callback: https://github.com/openstack/ara/blob/master/ara/plugins/callbacks/log_ara.py
.. _ansible.cfg: http://docs.ansible.com/ansible/intro_configuration.html#configuration-file

View File

@@ -13,6 +13,32 @@ in the ``callback_plugins`` Ansible configuration.
After running an Ansible playbook, the database will be created if it doesn't
exist and will be used automatically.
Using the ara_record module
---------------------------
ARA comes with a built-in module called ``ara_record``.
This module can be used as an action for a task in your Ansible playbooks in
order to register whatever you'd like in a key/value format, for example::
- name: Test playbook
hosts: all
gather_facts: yes
tasks:
- name: Get git revision of playbooks
command: git rev-parse HEAD
register: git_version
- name: Record git revision
ara_record:
key: "git_revision"
value: "{{ git_version.stdout }}"
This data will be recorded inside ARA's database and associated with the
particular playbook run that was executed.
You can then query ARA, either through the CLI or the web interface to see the
recorded values.
Looking at the data
-------------------
Once you've run ansible-playbook at least once, the database will be populated