diff --git a/kayobe/ansible.py b/kayobe/ansible.py index 6fbaa46b1..009c72c56 100644 --- a/kayobe/ansible.py +++ b/kayobe/ansible.py @@ -109,7 +109,7 @@ def _get_vars_files(config_path): vars_files = [] for vars_file in os.listdir(config_path): abs_path = os.path.join(config_path, vars_file) - if utils.is_readable_file(abs_path): + if utils.is_readable_file(abs_path)["result"]: root, ext = os.path.splitext(vars_file) if ext in (".yml", ".yaml", ".json"): vars_files.append(abs_path) @@ -277,3 +277,10 @@ def prune_galaxy_roles(parsed_args): ] LOG.debug("Removing roles: %s", ",".join(roles_to_remove)) utils.galaxy_remove(roles_to_remove, "ansible/roles") + + +def passwords_yml_exists(parsed_args): + """Return whether passwords.yml exists in the kayobe configuration.""" + passwords_path = os.path.join(parsed_args.config_path, + 'kolla', 'passwords.yml') + return utils.is_readable_file(passwords_path)["result"] diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index 4998560cd..02780a2e6 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -144,12 +144,15 @@ class KollaAnsibleMixin(object): return kolla_ansible.run_seed(*args, **kwargs) -class ControlHostBootstrap(KayobeAnsibleMixin, VaultMixin, Command): +class ControlHostBootstrap(KayobeAnsibleMixin, KollaAnsibleMixin, VaultMixin, + Command): """Bootstrap the Kayobe control environment. * Downloads and installs Ansible roles from Galaxy. * Generates an SSH key for the Ansible control host, if one does not exist. * Installs kolla-ansible on the Ansible control host. + * Generates admin-openrc.sh and public-openrc.sh files when passwords.yml + exists. """ def take_action(self, parsed_args): @@ -157,10 +160,33 @@ class ControlHostBootstrap(KayobeAnsibleMixin, VaultMixin, Command): ansible.install_galaxy_roles(parsed_args) playbooks = _build_playbook_list("bootstrap") self.run_kayobe_playbooks(parsed_args, playbooks, ignore_limit=True) + + passwords_exist = ansible.passwords_yml_exists(parsed_args) + if passwords_exist: + # Install and generate configuration - necessary for post-deploy. + ka_tags = None + else: + ka_tags = "install" playbooks = _build_playbook_list("kolla-ansible") - self.run_kayobe_playbooks(parsed_args, playbooks, tags="install", + self.run_kayobe_playbooks(parsed_args, playbooks, tags=ka_tags, ignore_limit=True) + if passwords_exist: + # If we are bootstrapping a control host for an existing + # environment, we should also generate the admin-openrc.sh and + # public-openrc.sh scripts that provide admin credentials. + + # FIXME: Fudge to work around incorrect configuration path. + extra_vars = {"node_config_directory": + parsed_args.kolla_config_path} + self.run_kolla_ansible_overcloud(parsed_args, "post-deploy", + extra_vars=extra_vars) + # Create an environment file for accessing the public API as the + # admin user. + playbooks = _build_playbook_list("public-openrc") + self.run_kayobe_playbooks(parsed_args, playbooks, + ignore_limit=True) + class ControlHostUpgrade(KayobeAnsibleMixin, VaultMixin, Command): """Upgrade the Kayobe control environment. diff --git a/kayobe/tests/unit/cli/test_commands.py b/kayobe/tests/unit/cli/test_commands.py index 7e86c1e83..458f14b42 100644 --- a/kayobe/tests/unit/cli/test_commands.py +++ b/kayobe/tests/unit/cli/test_commands.py @@ -35,9 +35,12 @@ class TestApp(cliff.app.App): class TestCase(unittest.TestCase): @mock.patch.object(ansible, "install_galaxy_roles", autospec=True) + @mock.patch.object(ansible, "passwords_yml_exists", autospec=True) @mock.patch.object(commands.KayobeAnsibleMixin, "run_kayobe_playbooks") - def test_control_host_bootstrap(self, mock_run, mock_install): + def test_control_host_bootstrap(self, mock_run, mock_passwords, + mock_install): + mock_passwords.return_value = False command = commands.ControlHostBootstrap(TestApp(), []) parser = command.get_parser("test") parsed_args = parser.parse_args([]) @@ -59,6 +62,50 @@ class TestCase(unittest.TestCase): ] self.assertEqual(expected_calls, mock_run.call_args_list) + @mock.patch.object(ansible, "install_galaxy_roles", autospec=True) + @mock.patch.object(ansible, "passwords_yml_exists", autospec=True) + @mock.patch.object(commands.KayobeAnsibleMixin, + "run_kayobe_playbooks") + @mock.patch.object(commands.KollaAnsibleMixin, + "run_kolla_ansible_overcloud") + def test_control_host_bootstrap_with_passwords( + self, mock_kolla_run, mock_run, mock_passwords, mock_install): + mock_passwords.return_value = True + command = commands.ControlHostBootstrap(TestApp(), []) + parser = command.get_parser("test") + parsed_args = parser.parse_args([]) + result = command.run(parsed_args) + self.assertEqual(0, result) + mock_install.assert_called_once_with(parsed_args) + expected_calls = [ + mock.call( + mock.ANY, + [utils.get_data_files_path("ansible", "bootstrap.yml")], + ignore_limit=True + ), + mock.call( + mock.ANY, + [utils.get_data_files_path("ansible", "kolla-ansible.yml")], + tags=None, + ignore_limit=True + ), + mock.call( + mock.ANY, + [utils.get_data_files_path("ansible", "public-openrc.yml")], + ignore_limit=True + ), + ] + self.assertEqual(expected_calls, mock_run.call_args_list) + + expected_calls = [ + mock.call( + mock.ANY, + "post-deploy", + extra_vars={"node_config_directory": "/etc/kolla"}, + ) + ] + self.assertEqual(expected_calls, mock_kolla_run.call_args_list) + @mock.patch.object(ansible, "install_galaxy_roles", autospec=True) @mock.patch.object(ansible, "prune_galaxy_roles", autospec=True) @mock.patch.object(commands.KayobeAnsibleMixin, diff --git a/kayobe/tests/unit/test_ansible.py b/kayobe/tests/unit/test_ansible.py index 95e329571..86361f474 100644 --- a/kayobe/tests/unit/test_ansible.py +++ b/kayobe/tests/unit/test_ansible.py @@ -445,3 +445,29 @@ class TestCase(unittest.TestCase): ] mock_remove.assert_called_once_with(expected_roles, "ansible/roles") + + @mock.patch.object(utils, 'is_readable_file', autospec=True) + def test_passwords_yml_exists_false(self, mock_is_readable): + parser = argparse.ArgumentParser() + ansible.add_args(parser) + parsed_args = parser.parse_args([]) + mock_is_readable.return_value = {"result": False} + + result = ansible.passwords_yml_exists(parsed_args) + + self.assertFalse(result) + mock_is_readable.assert_called_once_with( + "/etc/kayobe/kolla/passwords.yml") + + @mock.patch.object(utils, 'is_readable_file', autospec=True) + def test_passwords_yml_exists_true(self, mock_is_readable): + parser = argparse.ArgumentParser() + ansible.add_args(parser) + parsed_args = parser.parse_args(["--config-path", "/path/to/config"]) + mock_is_readable.return_value = {"result": True} + + result = ansible.passwords_yml_exists(parsed_args) + + self.assertTrue(result) + mock_is_readable.assert_called_once_with( + "/path/to/config/kolla/passwords.yml") diff --git a/releasenotes/notes/bootstrap-openrc-9aec3d53d0d62c81.yaml b/releasenotes/notes/bootstrap-openrc-9aec3d53d0d62c81.yaml new file mode 100644 index 000000000..df2e6b834 --- /dev/null +++ b/releasenotes/notes/bootstrap-openrc-9aec3d53d0d62c81.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixes an issue where the ``admin-openrc.sh`` and ``public-openrc.sh`` files + would not be generated when preparing a new control host environment for an + existing cloud. These files are now generated during ``kayobe control host + bootstrap`` if the Kolla Ansible ``passwords.yml`` file exists in the + Kayobe configuration. See `story 2001667 + `__ for details.