From 878fe4ca8fa1ef424d7d1d5cae9a6d37ff168185 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Fri, 26 Jun 2026 10:07:15 +0100 Subject: [PATCH] Cleaner split of kolla-ansible install and configure This allows us to install kayobe without generating the config. This was previous possible before the change to merge kolla-tags with tags[1]. [1] https://github.com/openstack/kayobe/commit/fb1767ad7f42235f576ec5ae3fd6f623f207e512 Change-Id: I81c2b2a8e1ccde44854257ce8e34e3517c945a4d Signed-off-by: Will Szumski --- ansible/install.yml | 19 +++++++++ ansible/roles/bootstrap/tasks/install.yml | 46 +++++++++++++++++++++ ansible/roles/bootstrap/tasks/main.yml | 47 ---------------------- ansible/roles/kolla-ansible/tasks/main.yml | 6 --- kayobe/cli/commands.py | 18 +++++++++ kayobe/tests/unit/cli/test_commands.py | 25 ++++++++++++ 6 files changed, 108 insertions(+), 53 deletions(-) create mode 100644 ansible/install.yml create mode 100644 ansible/roles/bootstrap/tasks/install.yml diff --git a/ansible/install.yml b/ansible/install.yml new file mode 100644 index 000000000..19537676a --- /dev/null +++ b/ansible/install.yml @@ -0,0 +1,19 @@ +--- + +- name: Ensure depnedencies are installed + hosts: localhost + gather_facts: true + tags: + - kolla-ansible + - install + tasks: + - name: Install Kayobe dependencies + import_role: + name: bootstrap + tasks_from: install.yml + + - name: Install kolla-ansible + import_role: + name: kolla-ansible + tasks_from: install.yml + diff --git a/ansible/roles/bootstrap/tasks/install.yml b/ansible/roles/bootstrap/tasks/install.yml new file mode 100644 index 000000000..402960d31 --- /dev/null +++ b/ansible/roles/bootstrap/tasks/install.yml @@ -0,0 +1,46 @@ +--- +- block: + - name: Testing privilege escalation + raw: "true" + become: true + failed_when: false + changed_when: false + register: privilege_escalation_result + + - name: Assert that we can escalate privileges + assert: + that: + - privilege_escalation_result is success + - '"password is required" not in privilege_escalation_result.stderr' + fail_msg: >- + Could not escalate privileges. You can either: set kayobe_control_host_become: true, + set ansible_become_password, or set up passwordless sudo. + when: kayobe_control_host_become | bool + +- name: Include OS family-specific variables + include_vars: "{{ ansible_facts.os_family }}.yml" + +- name: Gather the package facts + ansible.builtin.package_facts: + manager: auto + +- block: + - name: Assert that all packages are installed if not using privilege escalation + assert: + that: missing_packages is falsy + fail_msg: >- + The following packages are missing from your system: {{ missing_packages | join(', ') }} and + privilege escalation is disabled. Please get your system administator to install these packages + or enable kayobe_control_host_become. + when: not kayobe_control_host_become | bool + + - name: Ensure required packages are installed + package: + name: "{{ bootstrap_package_dependencies }}" + state: present + cache_valid_time: "{{ apt_cache_valid_time if ansible_facts.os_family == 'Debian' else omit }}" + update_cache: "{{ True if ansible_facts.os_family == 'Debian' else omit }}" + become: True + when: missing_packages is truthy + vars: + missing_packages: "{{ bootstrap_package_dependencies | difference(ansible_facts.packages.keys()) }}" diff --git a/ansible/roles/bootstrap/tasks/main.yml b/ansible/roles/bootstrap/tasks/main.yml index ee673a204..71bc73729 100644 --- a/ansible/roles/bootstrap/tasks/main.yml +++ b/ansible/roles/bootstrap/tasks/main.yml @@ -1,50 +1,3 @@ ---- -- block: - - name: Testing privilege escalation - raw: "true" - become: true - failed_when: false - changed_when: false - register: privilege_escalation_result - - - name: Assert that we can escalate privileges - assert: - that: - - privilege_escalation_result is success - - '"password is required" not in privilege_escalation_result.stderr' - fail_msg: >- - Could not escalate privileges. You can either: set kayobe_control_host_become: true, - set ansible_become_password, or set up passwordless sudo. - when: kayobe_control_host_become | bool - -- name: Include OS family-specific variables - include_vars: "{{ ansible_facts.os_family }}.yml" - -- name: Gather the package facts - ansible.builtin.package_facts: - manager: auto - -- block: - - name: Assert that all packages are installed if not using privilege escalation - assert: - that: missing_packages is falsy - fail_msg: >- - The following packages are missing from your system: {{ missing_packages | join(', ') }} and - privilege escalation is disabled. Please get your system administator to install these packages - or enable kayobe_control_host_become. - when: not kayobe_control_host_become | bool - - - name: Ensure required packages are installed - package: - name: "{{ bootstrap_package_dependencies }}" - state: present - cache_valid_time: "{{ apt_cache_valid_time if ansible_facts.os_family == 'Debian' else omit }}" - update_cache: "{{ True if ansible_facts.os_family == 'Debian' else omit }}" - become: True - when: missing_packages is truthy - vars: - missing_packages: "{{ bootstrap_package_dependencies | difference(ansible_facts.packages.keys()) }}" - - name: Check whether an SSH key exists stat: path: "{{ bootstrap_ssh_private_key_path }}" diff --git a/ansible/roles/kolla-ansible/tasks/main.yml b/ansible/roles/kolla-ansible/tasks/main.yml index 031f38a97..1fc412a10 100644 --- a/ansible/roles/kolla-ansible/tasks/main.yml +++ b/ansible/roles/kolla-ansible/tasks/main.yml @@ -1,10 +1,4 @@ --- -# NOTE: Use import_tasks here, since tags are not applied to tasks included via -# include_tasks. -- import_tasks: install.yml - tags: - - install - - import_tasks: config.yml tags: - config diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index 529d9b623..66ba0db33 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -18,6 +18,7 @@ import os import re import sys +import yaml from cliff.command import Command from cliff.hooks import CommandHook @@ -301,6 +302,13 @@ def get_parser(self, prog_name): group = parser.add_argument_group("Host Bootstrap") group.add_argument("--add-known-hosts", action='store_true', help="add SSH known hosts entries for each host") + group.add_argument( + "--install-only", + action='store_true', + default=yaml.safe_load(os.getenv("KAYOBE_INSTALL_ONLY", "false")), + help=("only install dependencies " + "(default from KAYOBE_INSTALL_ONLY env var)"), + ) return parser def take_action(self, parsed_args): @@ -308,6 +316,16 @@ def take_action(self, parsed_args): self.handle_kolla_tags_limits_deprecation(parsed_args) ansible.install_galaxy_roles(parsed_args) ansible.install_galaxy_collections(parsed_args) + + playbooks = _build_playbook_list("install") + self.run_kayobe_playbooks(parsed_args, playbooks, ignore_limit=True) + + if parsed_args.install_only: + self.app.LOG.debug("Skipping kolla-ansible installation and " + "configuration generation due to " + "--install-only") + return + playbooks = _build_playbook_list("bootstrap") self.run_kayobe_playbooks(parsed_args, playbooks, ignore_limit=True) diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index 171cf4678..3c6351b13 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -38,6 +38,21 @@ class TestCase(unittest.TestCase): maxDiff = None + @mock.patch.dict(os.environ, {}, clear=True) + def test_control_host_bootstrap_install_only_default_false(self): + command = commands.ControlHostBootstrap(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + self.assertFalse(parsed_args.install_only) + + @mock.patch.dict(os.environ, {"KAYOBE_INSTALL_ONLY": "false"}, + clear=True) + def test_control_host_bootstrap_install_only_default_false_from_env(self): + command = commands.ControlHostBootstrap(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + self.assertFalse(parsed_args.install_only) + @mock.patch.object(ansible, "install_galaxy_roles", autospec=True) @mock.patch.object(ansible, "install_galaxy_collections", autospec=True) @mock.patch.object(ansible, "passwords_yml_exists", autospec=True) @@ -55,6 +70,11 @@ def test_control_host_bootstrap(self, mock_run, mock_passwords, mock_install_roles.assert_called_once_with(parsed_args) mock_install_collections.assert_called_once_with(parsed_args) expected_calls = [ + mock.call( + mock.ANY, + [utils.get_data_files_path("ansible", "install.yml")], + ignore_limit=True, + ), mock.call( mock.ANY, [utils.get_data_files_path("ansible", "bootstrap.yml")], @@ -89,6 +109,11 @@ def test_control_host_bootstrap_with_passwords( mock_install_roles.assert_called_once_with(parsed_args) mock_install_collections.assert_called_once_with(parsed_args) expected_calls = [ + mock.call( + mock.ANY, + [utils.get_data_files_path("ansible", "install.yml")], + ignore_limit=True, + ), mock.call( mock.ANY, [utils.get_data_files_path("ansible", "bootstrap.yml")],