Add new Ansible plugins: ara_playbook and ara_api

These new action and lookup plugins makes it easier to query ARA from
inside a playbook.

In terms of practical use, this first iteration allows us to have an
integration test playbook that asserts some of ARA's features such as
playbook names and labels.

Change-Id: I38bea1062f0002886ecde70827f70d27248a1868
This commit is contained in:
David Moreau Simard 2019-11-19 11:02:52 -05:00
parent a8b8ff5c59
commit 8e5dcd976a
No known key found for this signature in database
GPG Key ID: 938880DAFC753E80
12 changed files with 309 additions and 7 deletions

View File

@ -0,0 +1,102 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# This file is part of ARA: Ansible Run Analysis.
#
# ARA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ARA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ARA. If not, see <http://www.gnu.org/licenses/>.
from ansible.playbook.play import Play
from ansible.plugins.action import ActionBase
from ara.clients import utils as client_utils
DOCUMENTATION = """
---
module: ara_playbook
short_description: Retrieves either a specific playbook from ARA or the one currently running
version_added: "2.9"
author: "David Moreau-Simard <dmsimard@redhat.com>"
description:
- Retrieves either a specific playbook from ARA or the one currently running
options:
playbook_id:
description:
- id of the playbook to retrieve
- if not set, the module will use the ongoing playbook's id
required: false
requirements:
- "python >= 3.5"
- "ara >= 1.4.0"
"""
EXAMPLES = """
- name: Get a specific playbook
ara_playbook:
playbook_id: 5
register: playbook_query
- name: Get current playbook by not specifying a playbook id
ara_playbook:
register: playbook_query
- name: Do something with the playbook
debug:
msg: "Playbook report: http://ara_api/playbook/{{ playbook_query.playbook.id | string }}.html"
"""
RETURN = """
playbook:
description: playbook object returned by the API
returned: on success
type: dict
"""
class ActionModule(ActionBase):
""" Retrieves either a specific playbook from ARA or the one currently running """
TRANSFERS_FILES = False
VALID_ARGS = frozenset(("playbook_id"))
def __init__(self, *args, **kwargs):
super(ActionModule, self).__init__(*args, **kwargs)
self.client = client_utils.active_client()
def run(self, tmp=None, task_vars=None):
if task_vars is None:
task_vars = dict()
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)
playbook_id = self._task.args.get("playbook_id", None)
if playbook_id is None:
# Retrieve the playbook id by working our way up from the task to find
# the play uuid. Once we have the play uuid, we can find the playbook.
parent = self._task
while not isinstance(parent._parent._play, Play):
parent = parent._parent
play = self.client.get("/api/v1/plays?uuid=%s" % parent._parent._play._uuid)
playbook_id = play["results"][0]["playbook"]
result["playbook"] = self.client.get("/api/v1/playbooks/%s" % playbook_id)
result["changed"] = False
result["msg"] = "Queried playbook %s from ARA" % playbook_id
return result

View File

@ -0,0 +1,63 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# This file is part of ARA Records Ansible.
#
# ARA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ARA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ARA. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
from ansible.plugins.lookup import LookupBase
from ara.clients import utils as client_utils
__metaclass__ = type
DOCUMENTATION = """
lookup: ara_api
author: David Moreau-Simard (@dmsimard)
version_added: "2.9"
short_description: Queries the ARA API for data
description:
- Queries the ARA API for data
options:
_terms:
description:
- The endpoint to query
type: list
elements: string
required: True
"""
EXAMPLES = """
- debug: msg="{{ lookup('ara_api','/api/v1/playbooks/1') }}"
"""
RETURN = """
_raw:
description: response from query
"""
class LookupModule(LookupBase):
def __init__(self, *args, **kwargs):
super(LookupModule, self).__init__(*args, **kwargs)
self.client = client_utils.active_client()
def run(self, terms, variables, **kwargs):
ret = []
for term in terms:
ret.append(self.client.get(term))
return ret

View File

@ -23,3 +23,4 @@ path = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
plugins = os.path.abspath(os.path.join(path, "plugins"))
action_plugins = os.path.abspath(os.path.join(plugins, "action"))
callback_plugins = os.path.abspath(os.path.join(plugins, "callback"))
lookup_plugins = os.path.abspath(os.path.join(plugins, "lookup"))

View File

@ -17,14 +17,15 @@
from __future__ import print_function
from . import action_plugins, callback_plugins
from . import action_plugins, callback_plugins, lookup_plugins
config = """
[defaults]
callback_plugins={}
action_plugins={}
lookup_plugins={}
""".format(
callback_plugins, action_plugins
callback_plugins, action_plugins, lookup_plugins
)
if __name__ == "__main__":

View File

@ -20,13 +20,14 @@ from __future__ import print_function
import os
from distutils.sysconfig import get_python_lib
from . import action_plugins, callback_plugins
from . import action_plugins, callback_plugins, lookup_plugins
exports = """
export ANSIBLE_CALLBACK_PLUGINS=${{ANSIBLE_CALLBACK_PLUGINS:-}}${{ANSIBLE_CALLBACK_PLUGINS+:}}{}
export ANSIBLE_ACTION_PLUGINS=${{ANSIBLE_ACTION_PLUGINS:-}}${{ANSIBLE_ACTION_PLUGINS+:}}{}
export ANSIBLE_LOOKUP_PLUGINS=${{ANSIBLE_LOOKUP_PLUGINS:-}}${{ANSIBLE_LOOKUP_PLUGINS+:}}{}
""".format(
callback_plugins, action_plugins
callback_plugins, action_plugins, lookup_plugins
)
if "VIRTUAL_ENV" in os.environ:

View File

@ -0,0 +1,23 @@
# Copyright (c) 2019 Red Hat, Inc.
#
# This file is part of ARA Records Ansible.
#
# ARA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ARA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ARA. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from . import lookup_plugins
if __name__ == "__main__":
print(lookup_plugins)

View File

@ -13,9 +13,11 @@ Once you've set up the ``callback_plugins`` configuration or the
``ANSIBLE_CALLBACK_PLUGINS`` environment variable, Ansible will automatically
use the ARA callback plugin to start recording data.
If you'd like to use the ``ara_record`` action plugin to record arbitrary data
during your playbook, you'll need to set ``action_plugins`` and
``ANSIBLE_ACTION_PLUGINS`` as well.
``ANSIBLE_ACTION_PLUGINS`` or ``action_plugins`` must be set if you'd like to
use the ``ara_record`` or ``ara_playbook`` action plugins.
If you would like to use the ``ara_api`` lookup plugin, then
``ANSIBLE_LOOKUP_PLUGINS`` or ``lookup_plugins`` must also be set.
Using setup helper modules
--------------------------
@ -36,17 +38,22 @@ The modules can be used directly on the command line:
$ python3 -m ara.setup.callback_plugins
/usr/lib/python3.7/site-packages/ara/plugins/callback
$ python3 -m ara.setup.lookup_plugins
/usr/lib/python3.7/site-packages/ara/plugins/lookup
# Note: This doesn't export anything, it only prints the commands.
# If you want to export directly from the command, you can use:
# source <(python3 -m ara.setup.env)
$ python3 -m ara.setup.env
export ANSIBLE_CALLBACK_PLUGINS=/usr/lib/python3.7/site-packages/ara/plugins/callback
export ANSIBLE_ACTION_PLUGINS=/usr/lib/python3.7/site-packages/ara/plugins/action
export ANSIBLE_LOOKUP_PLUGINS=/usr/lib/python3.7/site-packages/ara/plugins/lookup
$ python3 -m ara.setup.ansible
[defaults]
callback_plugins=/usr/lib/python3.7/site-packages/ara/plugins/callback
action_plugins=/usr/lib/python3.7/site-packages/ara/plugins/action
lookup_plugins=/usr/lib/python3.7/site-packages/ara/plugins/lookup
Or from python, for example:
@ -59,3 +66,7 @@ Or from python, for example:
>>> from ara.setup import action_plugins
>>> print(action_plugins)
/usr/lib/python3.7/site-packages/ara/plugins/action
>>> from ara.setup import lookup_plugins
>>> print(lookup_plugins)
/usr/lib/python3.7/site-packages/ara/plugins/lookup

View File

@ -0,0 +1,58 @@
.. _ara_api_lookup:
Querying ARA from inside playbooks
==================================
ara_api
-------
ARA comes with a built-in Ansible lookup plugin called ``ara_api`` that can be
made available by :ref:`configuring Ansible <ansible-configuration>` with the
``ANSIBLE_LOOKUP_PLUGINS`` environment variable or the ``lookup_plugins``
setting in an ``ansible.cfg`` file.
There is no other configuration required for this lookup plugin to work since
it retrieves necessary settings (such as API server endpoint and authentication)
from the callback plugin.
The ``ara_api`` lookup plugin can be used to do free-form queries to the
ARA API while the playbook is running:
.. code-block:: yaml
- name: Test playbook
hosts: localhost
tasks:
- name: Get list of playbooks
set_fact:
playbooks: "{{ lookup('ara_api', '/api/v1/playbooks') }}"
ara_playbook
------------
The ``ara_playbook`` Ansible action plugin can be enabled by
:ref:`configuring Ansible <ansible-configuration>` with the
``ANSIBLE_ACTION_PLUGINS`` environment variable or the ``action_plugins``
setting in an ``ansible.cfg`` file.
There is no other configuration required for this action plugin to work since
it retrieves necessary settings (such as API server endpoint and authentication)
from the callback plugin.
The ``ara_playbook`` action plugin can be used in combination with ``ara_api``
to query the API about the current playbook:
.. code-block:: yaml
- name: Test playbook
hosts: localhost
tasks:
- name: Get the currently running playbook
ara_playbook:
register: playbook_query
- name: Get failed tasks for the currently running playbook
vars:
playbook_id: "{{ playbook_query.playbook.id | string }}"
set_fact:
tasks: "{{ lookup('ara_api', '/api/v1/tasks?status=failed&playbook=' + playbook_id) }}"

View File

@ -20,6 +20,7 @@ Table of Contents
API: Distributed sqlite backend <distributed-sqlite-backend>
Setting playbook names and labels <playbook-names-and-labels>
Recording arbitrary data in playbooks <ara-record>
Querying ARA from inside playbooks <ara-api-lookup>
Contributing to ARA <contributing>
.. toctree::

View File

@ -105,6 +105,7 @@
- environment:
ANSIBLE_CALLBACK_PLUGINS: "{{ ara_setup_plugins.stdout }}/callback"
ANSIBLE_ACTION_PLUGINS: "{{ ara_setup_plugins.stdout }}/action"
ANSIBLE_LOOKUP_PLUGINS: "{{ ara_setup_plugins.stdout }}/lookup"
ARA_DEBUG: "{{ ara_api_debug }}"
ARA_LOG_LEVEL: "{{ ara_api_log_level }}"
ARA_BASE_DIR: "{{ ara_api_root_dir }}/server"
@ -120,6 +121,9 @@
- name: Run smoke.yaml integration test
command: "ansible-playbook -vvv {{ _test_root }}/smoke.yaml"
- name: Run lookup integration tests
command: "ansible-playbook -vvv {{ _test_root }}/lookups.yaml"
- name: Run hosts.yaml integration test
command: "ansible-playbook -vvv {{ _test_root }}/hosts.yaml"

View File

@ -0,0 +1,33 @@
- name: Assert playbook properties
hosts: localhost
gather_facts: yes
vars:
ara_playbook_name: ARA self tests
ara_playbook_labels:
- lookup-tests
tasks:
- name: Retrieve the current playbook so we can get the ID
ara_playbook:
register: playbook_query
- name: Recover data from ARA
vars:
playbook_id: "{{ playbook_query.playbook.id | string }}"
set_fact:
playbook: "{{ lookup('ara_api', '/api/v1/playbooks/' + playbook_id) }}"
tasks: "{{ lookup('ara_api', '/api/v1/tasks?playbook=' + playbook_id) }}"
results: "{{ lookup('ara_api', '/api/v1/results?playbook=' + playbook_id) }}"
- name: Assert playbook properties
assert:
that:
- playbook.name == 'ARA self tests'
- "playbook.labels | selectattr('name', 'search', 'lookup-tests') | list | length == 1"
- playbook.ansible_version == ansible_version.full
- playbook_dir in playbook.path
- "'tests/integration/lookups.yaml' in playbook.path"
- "playbook.files | length == playbook['items']['files']"
- "playbook.hosts | length == playbook['items']['hosts']"
- "playbook.plays | length == playbook['items']['plays']"
- "tasks.results | length == playbook['items']['tasks']"
- "results.results | length == playbook['items']['results']"

View File

@ -78,6 +78,7 @@
- environment:
ANSIBLE_CALLBACK_PLUGINS: "{{ ara_setup_plugins.stdout }}/callback"
ANSIBLE_ACTION_PLUGINS: "{{ ara_setup_plugins.stdout }}/action"
ANSIBLE_LOOKUP_PLUGINS: "{{ ara_setup_plugins.stdout }}/lookup"
ARA_SETTINGS: "{{ ara_api_settings }}"
ARA_API_CLIENT: "{{ ara_api_client | default('offline') }}"
ARA_API_SERVER: "{{ ara_api_server | default('http://127.0.0.1:8000') }}"
@ -90,6 +91,9 @@
- name: Run smoke.yaml integration test
command: "ansible-playbook -vvv {{ _test_root }}/smoke.yaml"
- name: Run lookups.yaml integration test
command: "ansible-playbook -vvv {{ _test_root }}/lookups.yaml"
- name: Run hosts.yaml integration test
command: "ansible-playbook -vvv {{ _test_root }}/hosts.yaml"