Merge "Add ability to run playbooks before and after a kayobe command"
This commit is contained in:
commit
8fb3020827
@ -120,3 +120,84 @@ We should first install the Galaxy role dependencies, to download the
|
||||
Then, to run the ``foo.yml`` playbook::
|
||||
|
||||
(kayobe) $ kayobe playbook run $KAYOBE_CONFIG_PATH/ansible/foo.yml
|
||||
|
||||
Hooks
|
||||
=====
|
||||
|
||||
.. warning::
|
||||
Hooks are an experimental feature and the design could change in the future.
|
||||
You may have to update your config if there are any changes to the design.
|
||||
This warning will be removed when the design has been stabilised.
|
||||
|
||||
Hooks allow you to automatically execute custom playbooks at certain points during
|
||||
the execution of a kayobe command. The point at which a hook is run is referred to
|
||||
as a ``target``. Please see the :ref:`list of available targets<Hook Targets>`.
|
||||
|
||||
Hooks are created by symlinking an existing playbook into the the relevant directory under
|
||||
``$KAYOBE_CONFIG_PATH/hooks``. Kayobe will search the hooks directory for sub-directories
|
||||
matching ``<command>.<target>.d``, where ``command`` is the name of a kayobe command
|
||||
with any spaces replaced with dashes, and ``target`` is one of the supported targets for
|
||||
the command.
|
||||
|
||||
For example, when using the command::
|
||||
|
||||
(kayobe) $ kayobe control host bootstrap
|
||||
|
||||
kayobe will search the paths:
|
||||
|
||||
- ``$KAYOBE_CONFIG_PATH/hooks/control-host-bootstrap/pre.d``
|
||||
- ``$KAYOBE_CONFIG_PATH/hooks/control-host-bootstrap/post.d``
|
||||
|
||||
Any playbooks listed under the ``pre.d`` directory will be run before kayobe executes
|
||||
its own playbooks and any playbooks under ``post.d`` will be run after. You can affect
|
||||
the order of the playbooks by prefixing the symlink with a sequence number. The sequence
|
||||
number must be separated from the hook name with a dash. Playbooks with smaller sequence
|
||||
numbers are run before playbooks with larger ones. Any ties are broken by alphabetical
|
||||
ordering.
|
||||
|
||||
For example to run the playbook ``foo.yml`` after ``kayobe overcloud host configure``,
|
||||
you could do the following::
|
||||
|
||||
(kayobe) $ mkdir -p $KAYOBE_CONFIG_PATH/hooks/overcloud-host-configure/post.d
|
||||
(kayobe) $ ln -s $KAYOBE_CONFIG_PATH/ansible/foo.yml \
|
||||
$KAYOBE_CONFIG_PATH/hooks/overcloud-host-configure/post.d/10-foo.yml
|
||||
|
||||
The sequence number for the ``foo.yml`` playbook is ``10``.
|
||||
|
||||
Failure handling
|
||||
----------------
|
||||
|
||||
If the exit status of any playbook, including built-in playbooks and custom hooks,
|
||||
is non-zero, kayobe will not run any subsequent hooks or built-in kayobe playbooks.
|
||||
Ansible provides several methods for preventing a task from producing a failure. Please
|
||||
see the `Ansible documentation <https://docs.ansible.com/ansible/latest/user_guide/playbooks_error_handling.html>`_
|
||||
for more details. Below is an example showing how you can use the ``ignore_errors`` option
|
||||
to prevent a task from causing the playbook to report a failure::
|
||||
|
||||
---
|
||||
- name: Failure example
|
||||
hosts: localhost
|
||||
tasks:
|
||||
- name: Deliberately fail
|
||||
fail:
|
||||
ignore_errors: true
|
||||
|
||||
A failure in the ``Deliberately fail`` task would not prevent subsequent tasks, hooks,
|
||||
and playbooks from running.
|
||||
|
||||
.. _Hook Targets:
|
||||
|
||||
Targets
|
||||
-------
|
||||
The following targets are available for all commands:
|
||||
|
||||
.. list-table:: all commands
|
||||
:widths: 10 500
|
||||
:header-rows: 1
|
||||
|
||||
* - Target
|
||||
- Description
|
||||
* - pre
|
||||
- Runs before a kayobe command has start executing
|
||||
* - post
|
||||
- Runs after a kayobe command has finished executing
|
||||
|
0
etc/kayobe/hooks/.gitkeep
Normal file
0
etc/kayobe/hooks/.gitkeep
Normal file
@ -12,16 +12,22 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from cliff.command import Command
|
||||
from cliff.hooks import CommandHook
|
||||
|
||||
from kayobe import ansible
|
||||
from kayobe import kolla_ansible
|
||||
from kayobe import utils
|
||||
from kayobe import vault
|
||||
|
||||
# This is set to an arbitrary large number to simplify the sorting logic
|
||||
DEFAULT_SEQUENCE_NUMBER = sys.maxsize
|
||||
|
||||
|
||||
def _build_playbook_list(*playbooks):
|
||||
"""Return a list of names of playbook files given their basenames."""
|
||||
@ -144,6 +150,73 @@ class KollaAnsibleMixin(object):
|
||||
return kolla_ansible.run_seed(*args, **kwargs)
|
||||
|
||||
|
||||
def _split_hook_sequence_number(hook):
|
||||
parts = hook.split("-", 1)
|
||||
if len(parts) < 2:
|
||||
return (DEFAULT_SEQUENCE_NUMBER, hook)
|
||||
try:
|
||||
return (int(parts[0]), parts[1])
|
||||
except ValueError:
|
||||
return (DEFAULT_SEQUENCE_NUMBER, hook)
|
||||
|
||||
|
||||
class HookDispatcher(CommandHook):
|
||||
"""Runs custom playbooks before and after a command"""
|
||||
|
||||
# Order of calls: get_epilog, get_parser, before, after
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.command = kwargs["command"]
|
||||
self.logger = self.command.app.LOG
|
||||
cmd = self.command.cmd_name
|
||||
# Replace white space with dashes for consistency with ansible
|
||||
# playbooks. Example cmd: kayobe control host bootstrap
|
||||
self.name = "-".join(cmd.split())
|
||||
|
||||
def get_epilog(self):
|
||||
pass
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
pass
|
||||
|
||||
def _find_hooks(self, config_path, target):
|
||||
name = self.name
|
||||
path = os.path.join(config_path, "hooks", name, "%s.d" % target)
|
||||
self.logger.debug("Discovering hooks in: %s" % path)
|
||||
if not os.path.exists:
|
||||
return []
|
||||
hooks = glob.glob(os.path.join(path, "*.yml"))
|
||||
self.logger.debug("Discovered the following hooks: %s" % hooks)
|
||||
return hooks
|
||||
|
||||
def hooks(self, config_path, target):
|
||||
hooks = self._find_hooks(config_path, target)
|
||||
# Hooks can be prefixed with a sequence number to adjust running order,
|
||||
# e.g 10-my-custom-playbook.yml. Sort by sequence number.
|
||||
hooks = sorted(hooks, key=_split_hook_sequence_number)
|
||||
# Resolve symlinks so that we can reference roles.
|
||||
hooks = [os.path.realpath(hook) for hook in hooks]
|
||||
return hooks
|
||||
|
||||
def run_hooks(self, parsed_args, target):
|
||||
config_path = parsed_args.config_path
|
||||
hooks = self.hooks(config_path, target)
|
||||
if hooks:
|
||||
self.logger.debug("Running hooks: %s" % hooks)
|
||||
self.command.run_kayobe_playbooks(parsed_args, hooks)
|
||||
|
||||
def before(self, parsed_args):
|
||||
self.run_hooks(parsed_args, "pre")
|
||||
return parsed_args
|
||||
|
||||
def after(self, parsed_args, return_code):
|
||||
if return_code == 0:
|
||||
self.run_hooks(parsed_args, "post")
|
||||
else:
|
||||
self.logger.debug("Not running hooks due to non-zero return code")
|
||||
return return_code
|
||||
|
||||
|
||||
class ControlHostBootstrap(KayobeAnsibleMixin, KollaAnsibleMixin, VaultMixin,
|
||||
Command):
|
||||
"""Bootstrap the Kayobe control environment.
|
||||
|
@ -1972,3 +1972,31 @@ class TestCase(unittest.TestCase):
|
||||
),
|
||||
]
|
||||
self.assertEqual(expected_calls, mock_run.call_args_list)
|
||||
|
||||
|
||||
class TestHookDispatcher(unittest.TestCase):
|
||||
|
||||
@mock.patch('kayobe.cli.commands.os.path')
|
||||
def test_hook_ordering(self, mock_path):
|
||||
mock_command = mock.MagicMock()
|
||||
dispatcher = commands.HookDispatcher(command=mock_command)
|
||||
dispatcher._find_hooks = mock.MagicMock()
|
||||
dispatcher._find_hooks.return_value = [
|
||||
"10-hook.yml",
|
||||
"5-hook.yml",
|
||||
"z-test-alphabetical.yml",
|
||||
"10-before-hook.yml",
|
||||
"5-multiple-dashes-in-name.yml",
|
||||
"no-prefix.yml"
|
||||
]
|
||||
expected_result = [
|
||||
"5-hook.yml",
|
||||
"5-multiple-dashes-in-name.yml",
|
||||
"10-before-hook.yml",
|
||||
"10-hook.yml",
|
||||
"no-prefix.yml",
|
||||
"z-test-alphabetical.yml",
|
||||
]
|
||||
mock_path.realpath.side_effect = lambda x: x
|
||||
actual = dispatcher.hooks("config/path", "pre")
|
||||
self.assertListEqual(actual, expected_result)
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds an experimental mechanism to automatically run custom playbooks
|
||||
before and after kayobe commands. Please see the ``Custom Ansible Playbooks``
|
||||
section in the documentation for more details.
|
105
setup.cfg
105
setup.cfg
@ -89,3 +89,108 @@ kayobe.cli=
|
||||
seed_service_upgrade = kayobe.cli.commands:SeedServiceUpgrade
|
||||
seed_vm_deprovision = kayobe.cli.commands:SeedVMDeprovision
|
||||
seed_vm_provision = kayobe.cli.commands:SeedVMProvision
|
||||
|
||||
kayobe.cli.baremetal_compute_inspect =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.baremetal_compute_manage =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.baremetal_compute_provide =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.baremetal_compute_rename =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.baremetal_compute_update_deployment_image =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.baremetal_compute_serial_console_enable =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.baremetal_compute_serial_console_disable =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.control_host_bootstrap =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.control_host_upgrade =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.configuration_dump =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.kolla_ansible_run =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.network_connectivity_check =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_bios_raid_configure =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_container_image_build =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_container_image_pull =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_database_backup =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_database_recover =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_deployment_image_build =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_deprovision =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_hardware_inspect =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_host_configure =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_host_package_update =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_host_command_run =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_host_upgrade =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_introspection_data_save =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_inventory_discover =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_post_configure =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_provision =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_service_configuration_save =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_service_configuration_generate =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_service_deploy =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_service_deploy_containers =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_service_destroy =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_service_reconfigure =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_service_stop =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_service_upgrade =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.overcloud_swift_rings_generate =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.physical_network_configure =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.playbook_run =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_container_image_build =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_deployment_image_build =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_host_configure =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_host_package_update =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_host_command_run =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_host_upgrade =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_hypervisor_host_configure =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_hypervisor_host_command_run =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_hypervisor_host_upgrade =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_service_deploy =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_service_upgrade =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_vm_deprovision =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
kayobe.cli.seed_vm_provision =
|
||||
hooks = kayobe.cli.commands:HookDispatcher
|
||||
|
Loading…
Reference in New Issue
Block a user