From 1530eb520bde81e0979685115419d3fcc477a963 Mon Sep 17 00:00:00 2001 From: Maciej Delmanowski Date: Mon, 22 Jan 2024 14:58:02 +0100 Subject: [PATCH] Implement support for "playbook sets" --- CHANGELOG.rst | 4 ++ docs/user-guide/project-directories.rst | 64 +++++++++++++++++++ .../templates/projectdir/modern/view.yml.j2 | 7 +- src/debops/ansibleplaybookrunner.py | 42 ++++++++++-- 4 files changed, 108 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dbfede8502..141346126c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -33,6 +33,10 @@ General to the :command:`syslog` service. Use the ``--verbose`` or ``-v`` flag to enable log output on the console. +- Users can define "playbook sets" on the view level of the "modern" project + directories. Playbook sets can be used as aliases to call multiple playbooks + using a custom name. See :ref:`playbook_sets` documentation for more details. + Changed ~~~~~~~ diff --git a/docs/user-guide/project-directories.rst b/docs/user-guide/project-directories.rst index 28d2dfccbf..75940e6d41 100644 --- a/docs/user-guide/project-directories.rst +++ b/docs/user-guide/project-directories.rst @@ -282,6 +282,70 @@ default collection for a given "infrastructure view" to one which contains unprivileged playbooks and roles, or add more Ansible Collections which should be searched for playbooks. +.. _playbook_sets: + +Per-view playbook sets +~~~~~~~~~~~~~~~~~~~~~~ + +Users can define "playbook sets" at the view level, using the +`views..playbook_sets` configuration option. This option is a YAML +dictionary with lists of playbooks to execute when a particular "playbook set" +is specified on the command line. An example configuration: + +.. code-block:: yaml + + --- + views: + system: + playbook_sets: + 'webservice': + - 'layer/common' + - 'service/nginx' + - 'custom-app' + +With the above configuration, users can execute a set of playbooks using the +command: + +.. code-block:: console + + debops run webservice -l webserver + +which will be internally expanded to: + +.. code-block:: console + + debops run layer/common service/nginx custom-app -l webserver + +After that, the usual playbook expansion will take place. The first two +playbooks will be found in the DebOps collection, and the :file:`custom-app` +playbook will be presumably in the :file:`ansible/views/system/playbooks/` +subdirectory of the project directory. If a potential playbook set is not found, +the argument will be expanded into a playbook if possible, or passed to the +:command:`ansible-playbook` command as-is. + +This mechanism can be used to redefine existing playbooks into playbook sets. +For example, if users want to include additional playbooks in the "site" +playbook, they can: + +.. code-block:: yaml + + --- + views: + system: + playbook_sets: + 'site': + - 'site' + - '..custom_playbook' + +Now calling the "site" playbook will execute the DebOps own :file:`site.yml` +playbook, and an additional playbook from a specific Ansible Collection. + +Please remember that this feature does not modify the actual playbooks, just the +way they are called from the command line. This means that including the +:file:`site.yml` playbook in another playbook will run just that one playbook, +not all the playbooks defined in a playbook set. + + DebOps and :command:`git` integration ------------------------------------- diff --git a/src/debops/_data/templates/projectdir/modern/view.yml.j2 b/src/debops/_data/templates/projectdir/modern/view.yml.j2 index ffd5b66ae9..26a13c7f5c 100644 --- a/src/debops/_data/templates/projectdir/modern/view.yml.j2 +++ b/src/debops/_data/templates/projectdir/modern/view.yml.j2 @@ -1,6 +1,6 @@ --- -{# Copyright (C) 2023 Maciej Delmanowski - # Copyright (C) 2023 DebOps +{# Copyright (C) 2023-2024 Maciej Delmanowski + # Copyright (C) 2023-2024 DebOps # SPDX-License-Identifier: GPL-3.0-or-later #} # Configuration for the '{{ view_name }}' view @@ -13,6 +13,9 @@ views: playbook_collections: - 'debops.debops' + # YAML dictionary which defines lists of "playbook sets" to use in this view + playbook_sets: {} + # Configuration options for Ansible, will be included in the 'ansible.cfg' # file generated by DebOps. # Ref: https://docs.ansible.com/ansible/latest/reference_appendices/config.html diff --git a/src/debops/ansibleplaybookrunner.py b/src/debops/ansibleplaybookrunner.py index 2574b289d3..8363bc131f 100644 --- a/src/debops/ansibleplaybookrunner.py +++ b/src/debops/ansibleplaybookrunner.py @@ -1,5 +1,5 @@ -# Copyright (C) 2020-2021 Maciej Delmanowski -# Copyright (C) 2020-2021 DebOps +# Copyright (C) 2020-2024 Maciej Delmanowski +# Copyright (C) 2020-2024 DebOps # SPDX-License-Identifier: GPL-3.0-or-later from .utils import unexpanduser @@ -108,11 +108,17 @@ def __init__(self, project, *args, **kwargs): self._parsed_args.append(index) else: # Most likely a name of a playbook which we can expand - self._ansible_command.append(self._quote_spaces( - self._expand_playbook(project, argument))) - self._found_playbooks.append(self._quote_spaces( - self._expand_playbook(project, argument))) - self._parsed_args.append(index) + + # Check the arguments for possible playbook sets and expand them + # if they're found + parsed_set = self._expand_playbook_set(project, argument) + + for element in parsed_set: + self._ansible_command.append(self._quote_spaces( + self._expand_playbook(project, element))) + self._found_playbooks.append(self._quote_spaces( + self._expand_playbook(project, element))) + self._parsed_args.append(index) def _quote_spaces(self, string): if ' ' in string: @@ -120,6 +126,28 @@ def _quote_spaces(self, string): else: return string + def _expand_playbook_set(self, project, argument): + logger.debug('Checking if playbook "{}" is a playbook set' + .format(argument)) + try: + playbook_set = (project.config.raw['views'] + [project.view] + ['playbook_sets'] + [argument]) + logger.notice('Found playbook set "{}" in view "{}"' + .format(argument, project.view)) + if isinstance(playbook_set, list): + logger.debug('Playbook set "{}" expanded to: {}' + .format(argument, ' '.join(playbook_set))) + return playbook_set + else: + logger.error('Incorrect definition of playbook set', + extra={'block': 'stderr'}) + raise ValueError('Incorrect definition of playbook set') + except KeyError: + logger.debug('No playbook set found') + return [argument] + def _ring_bell(self): # Notify user at end of execution if self.kwargs.get('bell', False):