Add ability to run playbooks before and after a kayobe command
Sometimes there is a need to develop site specific playbooks. Currently, it is necessary to manually invoke these at the right point during the deployment. Adding the ability to automatically run these custom playbooks will reduce the chance of running these playbooks at the wrong point or forgetting to run them at all. Change-Id: I1ae0f1f94665925326c8b1869dd75038f6f1b87d Story: 2001663 Task: 12606
This commit is contained in:
parent
565a0614dc
commit
e240a29a92
@ -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.
|
||||
|
@ -1969,3 +1969,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