diff --git a/ansible/inventory/group_vars/all/globals b/ansible/inventory/group_vars/all/globals index 719368a6f..54c562c87 100644 --- a/ansible/inventory/group_vars/all/globals +++ b/ansible/inventory/group_vars/all/globals @@ -4,6 +4,9 @@ ############################################################################### # Local path configuration (Ansible control host). +# Path to Kayobe data files +kayobe_path: "{{ lookup('cached', 'kayobe_path') }}" + # Path to Kayobe configuration directory on Ansible control host. kayobe_config_path: "{{ lookup('env', 'KAYOBE_CONFIG_PATH') | default('/etc/kayobe', true) }}" diff --git a/ansible/inventory/group_vars/all/inspector b/ansible/inventory/group_vars/all/inspector index b26110112..097bdce58 100644 --- a/ansible/inventory/group_vars/all/inspector +++ b/ansible/inventory/group_vars/all/inspector @@ -93,7 +93,7 @@ inspector_lldp_switch_port_interface_default: eth0 # check for an LLDP switch port description to use as the node's name. inspector_lldp_switch_port_interface_map: {} -# Enable IPMI rules: +# Enable IPMI rules. Default is true. inspector_rules_ipmi_enabled: True # IPMI username referenced by inspector rule. @@ -102,7 +102,7 @@ inspector_rule_var_ipmi_username: # IPMI password referenced by inspector rule. inspector_rule_var_ipmi_password: -# Enable Redfish rules +# Enable Redfish rules. Default is false. inspector_rules_redfish_enabled: False # Redfish username referenced by inspector rule. @@ -111,10 +111,10 @@ inspector_rule_var_redfish_username: # Redfish password referenced by inspector rule. inspector_rule_var_redfish_password: -# Redfish CA setting. +# Redfish CA setting. Default is true. inspector_rule_var_redfish_verify_ca: True -# Log Ironic inspector rules marked sensitive. +# Log Ironic inspector rules marked sensitive. Default is true. ironic_inspector_sensitive_rule_no_log: True # Ironic inspector rule to set IPMI credentials. diff --git a/ansible/lookup_plugins/kayobe_path.py b/ansible/lookup_plugins/kayobe_path.py new file mode 100644 index 000000000..af1d3d65b --- /dev/null +++ b/ansible/lookup_plugins/kayobe_path.py @@ -0,0 +1,19 @@ +# Copyright (c) 2026 StackHPC Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +__metaclass__ = type + +import kayobe.plugins.lookup.kayobe_path + +LookupModule = kayobe.plugins.lookup.kayobe_path.LookupModule diff --git a/ansible/roles/kolla-ansible/templates/overcloud-services.j2 b/ansible/roles/kolla-ansible/templates/overcloud-services.j2 index c02c2333d..e80e0bca4 100644 --- a/ansible/roles/kolla-ansible/templates/overcloud-services.j2 +++ b/ansible/roles/kolla-ansible/templates/overcloud-services.j2 @@ -18,15 +18,11 @@ common [fluentd:children] common -[kolla-toolbox:children] +[kolla_logs:children] common -[kolla_logs:children] -control -network -compute -storage -monitoring +[kolla_toolbox:children] +common [opensearch:children] control diff --git a/etc/kayobe/inspector.yml b/etc/kayobe/inspector.yml index a39242a37..6b25d596c 100644 --- a/etc/kayobe/inspector.yml +++ b/etc/kayobe/inspector.yml @@ -66,7 +66,7 @@ ############################################################################### # Ironic inspector introspection rules configuration. -# Ironic inspector option to enable IPMI rules. Set to 'True' by default. +# Ironic inspector option to enable IPMI rules. Default is true. #inspector_rules_ipmi_enabled: # Ironic inspector IPMI username to set. @@ -86,7 +86,7 @@ # Ironic inspector uses IPMI by default enroll the baremetal nodes, however it # is possible to use Redfish instead. To do that enable Redfish and make sure # all of the necessary variables below have been properly set. -# Enable inspector Redfish rules. Set to 'False' by default. +# Enable inspector Redfish rules. Default is false. #inspector_rules_redfish_enabled: # Ironic inspector Redfish username to set. @@ -95,10 +95,10 @@ # Ironic inspector Redfish password to set. #inspector_redfish_password: -# Redfish CA setting. Set to 'True' by default +# Redfish CA setting. Default is true. #inspector_rule_var_redfish_verify_ca: -# Log Ironic inspector rules marked sensitive. Set to 'True' by default +# Log Ironic inspector rules marked sensitive. Default is true. #ironic_inspector_sensitive_rule_no_log: # Ironic inspector rule to set IPMI credentials. diff --git a/kayobe/ansible.py b/kayobe/ansible.py index 6abd416a3..10cb30726 100644 --- a/kayobe/ansible.py +++ b/kayobe/ansible.py @@ -308,6 +308,19 @@ def _get_environment(parsed_args, external_playbook=False): env.setdefault("ANSIBLE_TEST_PLUGINS", ":".join(test_plugins)) + if external_playbook: + lookup_plugins = [ + os.path.join(parsed_args.config_path, "ansible", "lookup_plugins"), + utils.get_data_files_path("ansible", "lookup_plugins"), + ] + else: + lookup_plugins = [ + utils.get_data_files_path("ansible", "lookup_plugins"), + os.path.join(parsed_args.config_path, "ansible", "lookup_plugins"), + ] + + env.setdefault("ANSIBLE_LOOKUP_PLUGINS", ":".join(lookup_plugins)) + return env diff --git a/kayobe/plugins/lookup/kayobe_path.py b/kayobe/plugins/lookup/kayobe_path.py new file mode 100644 index 000000000..6c17ab139 --- /dev/null +++ b/kayobe/plugins/lookup/kayobe_path.py @@ -0,0 +1,24 @@ +# Copyright (c) 2026 StackHPC Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ansible.plugins.lookup import LookupBase + +from kayobe.utils import get_data_files_path + +__version__ = "1.0.0" + + +class LookupModule(LookupBase): + def run(self, _terms, _variables=None, **_kwargs): + return [get_data_files_path("")] diff --git a/kayobe/tests/unit/test_ansible.py b/kayobe/tests/unit/test_ansible.py index 10352dd25..eb4cee23a 100644 --- a/kayobe/tests/unit/test_ansible.py +++ b/kayobe/tests/unit/test_ansible.py @@ -81,6 +81,10 @@ def test_run_playbooks(self, mock_validate, mock_vars, mock_run): "/etc/kayobe/ansible/test_plugins", utils.get_data_files_path("ansible", "test_plugins"), ]), + "ANSIBLE_LOOKUP_PLUGINS": ":".join([ + "/etc/kayobe/ansible/lookup_plugins", + utils.get_data_files_path("ansible", "lookup_plugins"), + ]), } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -132,6 +136,10 @@ def test_run_playbooks_internal(self, mock_validate, mock_vars, mock_run): utils.get_data_files_path("ansible", "test_plugins"), "/etc/kayobe/ansible/test_plugins", ]), + "ANSIBLE_LOOKUP_PLUGINS": ":".join([ + utils.get_data_files_path("ansible", "lookup_plugins"), + "/etc/kayobe/ansible/lookup_plugins", + ]), } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -251,6 +259,10 @@ def test_run_playbooks_all_the_args(self, mock_validate, mock_vars, "/path/to/config/ansible/test_plugins", utils.get_data_files_path("ansible", "test_plugins"), ]), + "ANSIBLE_LOOKUP_PLUGINS": ":".join([ + "/path/to/config/ansible/lookup_plugins", + utils.get_data_files_path("ansible", "lookup_plugins"), + ]), } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -314,6 +326,7 @@ def test_run_playbooks_all_the_long_args(self, mock_ask, mock_validate, "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } expected_calls = [ mock.call(["which", "kayobe-vault-password-helper"], @@ -356,6 +369,7 @@ def test_run_playbooks_vault_password_file(self, mock_update, "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -395,6 +409,7 @@ def test_run_playbooks_vault_password_helper(self, mock_validate, "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -462,6 +477,7 @@ def test_run_playbooks_func_args(self, mock_validate, mock_vars, mock_run): "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -500,6 +516,7 @@ def test_run_playbooks_ignore_limit(self, mock_validate, mock_vars, "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -538,6 +555,7 @@ def test_run_playbooks_list_tasks_arg(self, mock_validate, mock_vars, "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -571,6 +589,7 @@ def test_run_playbooks_ansible_cfg(self, mock_validate, mock_vars, "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -606,6 +625,7 @@ def test_run_playbooks_ansible_cfg_env(self, mock_validate, mock_vars, "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -976,6 +996,7 @@ def test_multiple_inventory_args(self, mock_validate, mock_vars, mock_run): "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -1021,6 +1042,7 @@ def exists_replacement(path): "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -1064,6 +1086,7 @@ def exists_replacement(path): "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -1108,6 +1131,7 @@ def exists_replacement(path): "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -1157,6 +1181,7 @@ def exists_replacement(path): "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) @@ -1238,6 +1263,7 @@ def exists_replacement(path): "ANSIBLE_ACTION_PLUGINS": mock.ANY, "ANSIBLE_FILTER_PLUGINS": mock.ANY, "ANSIBLE_TEST_PLUGINS": mock.ANY, + "ANSIBLE_LOOKUP_PLUGINS": mock.ANY, } mock_run.assert_called_once_with(expected_cmd, check_output=False, quiet=False, env=expected_env) diff --git a/releasenotes/notes/adds-kayobe-path-832afde0fd057569.yaml b/releasenotes/notes/adds-kayobe-path-832afde0fd057569.yaml new file mode 100644 index 000000000..2cd94cf97 --- /dev/null +++ b/releasenotes/notes/adds-kayobe-path-832afde0fd057569.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adds a new variable: ``kayobe_path``, which is set to the path of the git + repository where the Kayobe source code is located for editable installs, + or the installation path in the virtualenv where the ansible folder is + located, in the case of a regular install. diff --git a/releasenotes/notes/fixes-lookup-plugins-for-external-playbooks-cc55db9decd7e33b.yaml b/releasenotes/notes/fixes-lookup-plugins-for-external-playbooks-cc55db9decd7e33b.yaml new file mode 100644 index 000000000..cea4d6f13 --- /dev/null +++ b/releasenotes/notes/fixes-lookup-plugins-for-external-playbooks-cc55db9decd7e33b.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes an issue where internal kayobe lookup plugins could not be used in + external playbooks. + `LP#2142876 `__ diff --git a/releasenotes/notes/nmstate-networking-engine-9eca23fe61902134.yaml b/releasenotes/notes/nmstate-networking-engine-9eca23fe61902134.yaml index b6ec7a908..c7bede6b5 100644 --- a/releasenotes/notes/nmstate-networking-engine-9eca23fe61902134.yaml +++ b/releasenotes/notes/nmstate-networking-engine-9eca23fe61902134.yaml @@ -4,46 +4,37 @@ features: Adds an opt-in ``nmstate`` network engine (``network_engine: nmstate``) for host network configuration via NetworkManager/libnmstate. - - | + Supports Ethernet, VLAN, bond, bridge, routes, and routing rules, and adds OVS patch-link veth generation for overcloud bridge-to-OVS connectivity. - - | + Adds structured ethtool configuration via ``_ethtool_config`` for ring parameters and selected offload features. - - | + The nmstate network engine is only supported on Rocky Linux. Ubuntu Noble is not supported because the required system packages (nmstate, python3-libnmstate) are not available in Ubuntu repositories. Attempting to use nmstate on Ubuntu will fail with a clear error message directing - users to use the ``legacy`` network engine. + users to use the ``default`` network engine. upgrade: - | - Introduces ``network_engine`` in ``globals.yml``: - ``legacy`` (default) and ``nmstate``. + Introduces ``network_engine`` in ``globals.yml`` to control which + engine is used to configure network interfaces. The options are ``default``, + which uses ``MichaelRigart.interfaces`` on Enterprise Linux and + systemd-networkd on Ubuntu, and ``nmstate``. - | With ``nmstate``, ethtool settings use structured YAML in - ``_ethtool_config``. ``legacy`` engine behavior is + ``_ethtool_config``. ``default`` engine behavior is unchanged. - | With ``network_engine: nmstate``, ``_rules`` entries must use dict format (keys such as ``from``, ``to``, ``priority``, ``table``). - String-format rules are rejected on the ``nmstate`` path. ``legacy`` + String-format rules are rejected on the ``nmstate`` path. ``default`` engine behavior is unchanged. - | Switching to ``nmstate`` may reconfigure host networking and cause temporary connectivity disruption. -fixes: - - | - Fixes host configure regressions when using ``network_engine: nmstate`` on - Rocky Linux. Kayobe now ensures named route tables from - ``network_route_tables`` are defined in ``/etc/iproute2/rt_tables`` for - nmstate-managed hosts. These fixes apply only to the ``nmstate`` engine - path; ``legacy`` engine behavior is unchanged. - - | - When using ``network_engine: nmstate`` with firewalld enabled, Kayobe now - reconciles interface-to-zone mappings in both permanent and runtime - firewalld configuration for interfaces that define ``_zone``. diff --git a/requirements.yml b/requirements.yml index 5974a41a5..c7402326a 100644 --- a/requirements.yml +++ b/requirements.yml @@ -4,7 +4,7 @@ collections: type: git version: master - name: community.docker - version: 5.0.5 + version: 5.1.0 - name: community.network version: 5.1.0 - name: dellemc.os6 @@ -29,13 +29,13 @@ roles: version: 1.3.2 - src: giovtorres.tuned version: 2.0.2 - - src: git+https://github.com/stackhpc/ansible-role-configdrive.git - name: jriguera.configdrive - version: fb199247333e72e38a9d414cf7b6144daa645477 + - src: jriguera.configdrive + # There are no versioned releases of this role. + version: 17d9f7e76942012e9d09490ecb119066d16aad3d - src: MichaelRigart.interfaces version: v1.16.1 - src: mrlesmithjr.chrony - version: v0.1.6 + version: v0.2.0 - src: mrlesmithjr.manage_lvm version: v0.2.13 - src: mrlesmithjr.mdadm diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index 37fe7338e..1e7975186 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -9,8 +9,8 @@ - release-notes-jobs-python3 check: jobs: - - openstack-tox-py310: - #NOTE(wszumski): We have dropped python3.10 support, so disable this job. + - openstack-tox-py311: + #NOTE(wszumski): We have dropped python3.11 support, so disable this job. files: THIS-JOB-IS-DISABLED - kayobe-tox-ansible-syntax - kayobe-tox-ansible @@ -40,8 +40,8 @@ - kayobe-seed-vm-ubuntu-noble gate: jobs: - - openstack-tox-py310: - #NOTE(wszumski): We have dropped python3.10 support, so disable this job. + - openstack-tox-py311: + #NOTE(wszumski): We have dropped python3.11 support, so disable this job. files: THIS-JOB-IS-DISABLED - kayobe-tox-ansible-syntax - kayobe-tox-ansible