Merge "Bump up Ansible supported versions to 11.x/12.x"
This commit is contained in:
19
ansible/action_plugins/template_content.py
Normal file
19
ansible/action_plugins/template_content.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2025 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.action.template_content
|
||||
|
||||
ActionModule = kayobe.plugins.action.template_content.ActionModule
|
||||
@@ -93,7 +93,7 @@
|
||||
kolla_ansible_passwords_path: "{{ kayobe_env_config_path }}/kolla/passwords.yml"
|
||||
kolla_overcloud_inventory_search_paths_static:
|
||||
- "{{ kayobe_config_path }}"
|
||||
kolla_overcloud_inventory_search_paths: "{{ kolla_overcloud_inventory_search_paths_static + kayobe_env_search_paths }}"
|
||||
kolla_overcloud_inventory_search_paths: "{{ kolla_overcloud_inventory_search_paths_static + kayobe_env_search_paths | default([]) }}"
|
||||
kolla_ansible_certificates_path: "{{ kayobe_env_config_path }}/kolla/certificates"
|
||||
kolla_inspector_dhcp_pool_start: "{{ inspection_net_name | net_inspection_allocation_pool_start }}"
|
||||
kolla_inspector_dhcp_pool_end: "{{ inspection_net_name | net_inspection_allocation_pool_end }}"
|
||||
@@ -103,7 +103,7 @@
|
||||
kolla_libvirt_tls: "{{ compute_libvirt_enable_tls | bool }}"
|
||||
kolla_globals_paths_static:
|
||||
- "{{ kayobe_config_path }}"
|
||||
kolla_globals_paths_extra: "{{ kolla_globals_paths_static + kayobe_env_search_paths }}"
|
||||
kolla_globals_paths_extra: "{{ kolla_globals_paths_static + kayobe_env_search_paths | default([]) }}"
|
||||
kolla_ironic_inspector_host: "{{ groups[controller_ironic_inspector_group][0] if groups[controller_ironic_inspector_group] | length > 0 else '' }}"
|
||||
|
||||
- name: Generate Kolla Ansible host vars for the seed host
|
||||
|
||||
@@ -26,7 +26,7 @@ kolla_ansible_venv_extra_requirements: []
|
||||
# tested code. Changes to this limit should be tested. It is possible to only
|
||||
# install ansible-core by setting kolla_ansible_venv_ansible to None.
|
||||
kolla_ansible_venv_ansible:
|
||||
kolla_ansible_venv_ansible_core: 'ansible-core>=2.17,<2.19'
|
||||
kolla_ansible_venv_ansible_core: 'ansible-core>=2.18,<2.20'
|
||||
|
||||
# Path to a requirements.yml file for Ansible collections.
|
||||
kolla_ansible_requirements_yml: "{{ kolla_ansible_venv }}/share/kolla-ansible/requirements.yml"
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
- "{{ kolla_ansible_venv_ansible_core }}"
|
||||
- "{{ kolla_ansible_venv_ansible }}"
|
||||
pip:
|
||||
name: "{{ (kolla_ansible_packages + kolla_ansible_venv_extra_requirements) | select | list }}"
|
||||
name: "{{ (kolla_ansible_packages | default([]) + kolla_ansible_venv_extra_requirements | default([])) | select | list }}"
|
||||
state: latest
|
||||
extra_args: "{% if kolla_upper_constraints_file %}-c {{ kolla_upper_constraints_file }}{% endif %}"
|
||||
virtualenv: "{{ kolla_ansible_venv }}"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- block:
|
||||
- name: Test the kolla-ansible role with default values
|
||||
include_role:
|
||||
name: ../../kolla-ansible
|
||||
name: "{{ playbook_dir }}/.."
|
||||
vars:
|
||||
kolla_ansible_source_path: "{{ temp_path }}/src"
|
||||
kolla_ansible_ctl_install_type: "source"
|
||||
|
||||
@@ -119,11 +119,11 @@
|
||||
params:
|
||||
content: |
|
||||
{%- for path in item.sources -%}
|
||||
{{ lookup('template', path) }}
|
||||
{{ lookup('file', path) }}
|
||||
{%- endfor -%}
|
||||
dest: "{{ item.dest }}"
|
||||
mode: 0640
|
||||
copy: "{{ params | combine(item.params) }}"
|
||||
template_content: "{{ params | combine(item.params) }}"
|
||||
with_items: "{{ kolla_custom_config_info.concat }}"
|
||||
|
||||
- name: Ensure unnecessary extra configuration files are absent
|
||||
|
||||
@@ -21,7 +21,14 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
|
||||
# TODO(dougszu): Backwards compatibility for Ansible 11. This exception
|
||||
# handler can be removed in the G cycle.
|
||||
try:
|
||||
from ansible.parsing.vault import EncryptedString
|
||||
except ImportError:
|
||||
# Ansible 11
|
||||
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
|
||||
EncryptedString = AnsibleVaultEncryptedUnicode
|
||||
|
||||
from kayobe import exception
|
||||
from kayobe import utils
|
||||
@@ -222,6 +229,9 @@ def _get_environment(parsed_args, external_playbook=False):
|
||||
"""Return an environment dict for executing an Ansible playbook."""
|
||||
env = os.environ.copy()
|
||||
vault.update_environment(parsed_args, env)
|
||||
# TODO(wszusmki): Kayobe still uses broken conditions. Work on fixing these
|
||||
# and remove when that work is complete.
|
||||
env.setdefault("ANSIBLE_ALLOW_BROKEN_CONDITIONALS", "true")
|
||||
# If the configuration path has been specified via --config-path, ensure
|
||||
# the environment variable is set, so that it can be referenced by
|
||||
# playbooks.
|
||||
@@ -340,7 +350,7 @@ def run_playbook(parsed_args, playbook, *args, **kwargs):
|
||||
|
||||
def _sanitise_hostvar(var):
|
||||
"""Sanitise a host variable."""
|
||||
if isinstance(var, AnsibleVaultEncryptedUnicode):
|
||||
if isinstance(var, EncryptedString):
|
||||
return "******"
|
||||
# Recursively sanitise dicts and lists.
|
||||
if isinstance(var, dict):
|
||||
|
||||
@@ -14,6 +14,16 @@
|
||||
|
||||
from ansible.plugins.action import ActionBase
|
||||
|
||||
# TODO(dougszu): From Ansible 12 onwards we must explicitly trust templates.
|
||||
# Since this feature is not supported in previous releases, we define a
|
||||
# noop method here for backwards compatibility. This can be removed in the
|
||||
# G cycle.
|
||||
try:
|
||||
from ansible.template import trust_as_template
|
||||
except ImportError:
|
||||
def trust_as_template(template):
|
||||
return template
|
||||
|
||||
|
||||
class ConfigError(Exception):
|
||||
pass
|
||||
@@ -28,6 +38,11 @@ class ActionModule(ActionBase):
|
||||
|
||||
TRANSFERS_FILES = False
|
||||
|
||||
def trusted_template(self, input):
|
||||
# Mark all input as trusted.
|
||||
trusted_input = trust_as_template(input)
|
||||
return self._templar.template(trusted_input)
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
if task_vars is None:
|
||||
task_vars = dict()
|
||||
@@ -97,11 +112,11 @@ class ActionModule(ActionBase):
|
||||
def _get_interface_fact(self, net_name, required, description):
|
||||
# Check whether the network is mapped to this host.
|
||||
condition = "{{ '%s' in network_interfaces }}" % net_name
|
||||
condition = self._templar.template(condition)
|
||||
condition = self.trusted_template(condition)
|
||||
if condition:
|
||||
# Get the network interface for this network.
|
||||
iface = ("{{ '%s' | net_interface }}" % net_name)
|
||||
iface = self._templar.template(iface)
|
||||
iface = self.trusted_template(iface)
|
||||
if required and not iface:
|
||||
msg = ("Required network '%s' (%s) does not have an interface "
|
||||
"configured for this host" % (net_name, description))
|
||||
@@ -114,20 +129,20 @@ class ActionModule(ActionBase):
|
||||
|
||||
def _get_external_interface(self, net_name, required):
|
||||
condition = "{{ '%s' in network_interfaces }}" % net_name
|
||||
condition = self._templar.template(condition)
|
||||
condition = self.trusted_template(condition)
|
||||
if condition:
|
||||
iface = self._templar.template("{{ '%s' | net_interface }}" %
|
||||
net_name)
|
||||
iface = self.trusted_template("{{ '%s' | net_interface }}" %
|
||||
net_name)
|
||||
if iface:
|
||||
# When these networks are VLANs, we need to use the
|
||||
# underlying tagged bridge interface rather than the
|
||||
# untagged interface. We therefore strip the .<vlan> suffix
|
||||
# of the interface name. We use a union here as a single
|
||||
# tagged interface may be shared between these networks.
|
||||
vlan = self._templar.template("{{ '%s' | net_vlan }}" %
|
||||
net_name)
|
||||
parent = self._templar.template("{{ '%s' | net_parent }}" %
|
||||
net_name)
|
||||
vlan = self.trusted_template("{{ '%s' | net_vlan }}" %
|
||||
net_name)
|
||||
parent = self.trusted_template("{{ '%s' | net_parent }}" %
|
||||
net_name)
|
||||
if vlan and parent:
|
||||
iface = parent
|
||||
elif vlan and iface.endswith(".%s" % vlan):
|
||||
@@ -146,15 +161,15 @@ class ActionModule(ActionBase):
|
||||
neutron_external_interfaces = []
|
||||
neutron_physical_networks = []
|
||||
missing_physical_networks = []
|
||||
bridge_suffix = self._templar.template(
|
||||
bridge_suffix = self.trusted_template(
|
||||
"{{ network_bridge_suffix_ovs }}")
|
||||
patch_prefix = self._templar.template("{{ network_patch_prefix }}")
|
||||
patch_suffix = self._templar.template("{{ network_patch_suffix_ovs }}")
|
||||
patch_prefix = self.trusted_template("{{ network_patch_prefix }}")
|
||||
patch_suffix = self.trusted_template("{{ network_patch_suffix_ovs }}")
|
||||
for interface, iface_networks in external_interfaces.items():
|
||||
is_bridge = ("{{ '%s' in (network_interfaces |"
|
||||
"net_select_bridges |"
|
||||
"map('net_interface')) }}" % interface)
|
||||
is_bridge = self._templar.template(is_bridge)
|
||||
is_bridge = self.trusted_template(is_bridge)
|
||||
neutron_bridge_names.append(interface + bridge_suffix)
|
||||
# For a bridge, use a veth pair connected to the bridge. Otherwise
|
||||
# use the interface directly.
|
||||
@@ -171,7 +186,7 @@ class ActionModule(ActionBase):
|
||||
# attribute set, and if so, whether they are consistent.
|
||||
iface_physical_networks = []
|
||||
for iface_network in iface_networks:
|
||||
physical_network = self._templar.template(
|
||||
physical_network = self.trusted_template(
|
||||
"{{ '%s' | net_physical_network }}" % iface_network)
|
||||
if (physical_network and
|
||||
physical_network not in iface_physical_networks):
|
||||
|
||||
@@ -24,10 +24,21 @@ import tempfile
|
||||
|
||||
from ansible import constants
|
||||
from ansible.plugins import action
|
||||
# TODO(dougszu): From Ansible 12 onwards we must explicitly trust templates.
|
||||
# Since this feature is not supported in previous releases, we define a
|
||||
# noop method here for backwards compatibility. This can be removed in the
|
||||
# G cycle.
|
||||
try:
|
||||
from ansible.template import trust_as_template
|
||||
except ImportError:
|
||||
def trust_as_template(template):
|
||||
return template
|
||||
|
||||
from io import StringIO
|
||||
|
||||
from oslo_config import iniparser
|
||||
|
||||
|
||||
_ORPHAN_SECTION = 'TEMPORARY_ORPHAN_VARIABLE_SECTION'
|
||||
|
||||
DOCUMENTATION = '''
|
||||
@@ -154,7 +165,7 @@ class ActionModule(action.ActionBase):
|
||||
# Only use config if present
|
||||
if os.access(source, os.R_OK):
|
||||
with open(source, 'r') as f:
|
||||
template_data = f.read()
|
||||
template_data = trust_as_template(f.read())
|
||||
|
||||
# set search path to mimic 'template' module behavior
|
||||
searchpath = [
|
||||
|
||||
@@ -27,6 +27,16 @@ from ansible import constants
|
||||
from ansible import errors as ansible_errors
|
||||
from ansible.plugins import action
|
||||
|
||||
# TODO(dougszu): From Ansible 12 onwards we must explicitly trust templates.
|
||||
# Since this feature is not supported in previous releases, we define a
|
||||
# noop method here for backwards compatibility. This can be removed in the
|
||||
# G cycle.
|
||||
try:
|
||||
from ansible.template import trust_as_template
|
||||
except ImportError:
|
||||
def trust_as_template(template):
|
||||
return template
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: merge_yaml
|
||||
@@ -95,7 +105,7 @@ class ActionModule(action.ActionBase):
|
||||
# Only use config if present
|
||||
if source and os.access(source, os.R_OK):
|
||||
with open(source, 'r') as f:
|
||||
template_data = f.read()
|
||||
template_data = trust_as_template(f.read())
|
||||
|
||||
# set search path to mimic 'template' module behavior
|
||||
searchpath = [
|
||||
|
||||
47
kayobe/plugins/action/template_content.py
Normal file
47
kayobe/plugins/action/template_content.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# Copyright (c) 2025 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.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.plugins.action.template import ActionModule as TemplateBase
|
||||
|
||||
from ansible import errors as ansible_errors
|
||||
|
||||
|
||||
class ActionModule(TemplateBase):
|
||||
"""Decorates template action to support using content parameter"""
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
module_args = self._task.args
|
||||
if "src" in module_args and "content" in module_args:
|
||||
raise ansible_errors.AnsibleActionFail(
|
||||
"Invalid argument: content and src are mutually exclusive."
|
||||
)
|
||||
if "content" not in module_args and "src" not in module_args:
|
||||
raise ansible_errors.AnsibleActionFail(
|
||||
"Invalid argument: You must speicfy either content or src"
|
||||
)
|
||||
|
||||
if "src" in module_args:
|
||||
return super().run(*args, **kwargs)
|
||||
|
||||
with tempfile.NamedTemporaryFile() as fp:
|
||||
content = module_args.pop("content", "")
|
||||
fp.write(to_bytes(content))
|
||||
fp.flush()
|
||||
tempfile_path = os.path.join(tempfile.gettempdir(), str(fp.name))
|
||||
module_args["src"] = tempfile_path
|
||||
return super().run(*args, **kwargs)
|
||||
@@ -28,6 +28,9 @@ from kayobe import exception
|
||||
from kayobe import utils
|
||||
from kayobe import vault
|
||||
|
||||
from ansible.parsing.vault import VaultSecret
|
||||
from ansible.parsing.vault import VaultSecretsContext
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
class TestCase(unittest.TestCase):
|
||||
@@ -56,6 +59,7 @@ class TestCase(unittest.TestCase):
|
||||
]
|
||||
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ROLES_PATH": ":".join([
|
||||
"/etc/kayobe/ansible/roles",
|
||||
@@ -106,6 +110,7 @@ class TestCase(unittest.TestCase):
|
||||
]
|
||||
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ROLES_PATH": ":".join([
|
||||
utils.get_data_files_path("ansible", "roles"),
|
||||
@@ -223,6 +228,7 @@ class TestCase(unittest.TestCase):
|
||||
]
|
||||
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/path/to/config",
|
||||
"KAYOBE_ENVIRONMENT": "test-env",
|
||||
"ANSIBLE_ROLES_PATH": ":".join([
|
||||
@@ -299,6 +305,7 @@ class TestCase(unittest.TestCase):
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/path/to/config",
|
||||
"KAYOBE_ENVIRONMENT": "test-env",
|
||||
"KAYOBE_VAULT_PASSWORD": "test-pass",
|
||||
@@ -342,6 +349,7 @@ class TestCase(unittest.TestCase):
|
||||
"playbook1.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
"ANSIBLE_COLLECTIONS_PATH": mock.ANY,
|
||||
@@ -379,6 +387,7 @@ class TestCase(unittest.TestCase):
|
||||
"playbook1.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"KAYOBE_VAULT_PASSWORD": "test-pass",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
@@ -446,6 +455,7 @@ class TestCase(unittest.TestCase):
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
"ANSIBLE_COLLECTIONS_PATH": mock.ANY,
|
||||
@@ -483,6 +493,7 @@ class TestCase(unittest.TestCase):
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
"ANSIBLE_COLLECTIONS_PATH": mock.ANY,
|
||||
@@ -520,6 +531,7 @@ class TestCase(unittest.TestCase):
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
"ANSIBLE_COLLECTIONS_PATH": mock.ANY,
|
||||
@@ -553,6 +565,7 @@ class TestCase(unittest.TestCase):
|
||||
expected_env = {
|
||||
"ANSIBLE_CONFIG": "/etc/kayobe/ansible.cfg",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
"ANSIBLE_COLLECTIONS_PATH": mock.ANY,
|
||||
"ANSIBLE_ACTION_PLUGINS": mock.ANY,
|
||||
@@ -585,6 +598,7 @@ class TestCase(unittest.TestCase):
|
||||
"playbook1.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"ANSIBLE_CONFIG": "/path/to/ansible.cfg",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
@@ -656,6 +670,10 @@ class TestCase(unittest.TestCase):
|
||||
@mock.patch.object(tempfile, 'mkdtemp')
|
||||
def test_config_dump_vaulted(self, mock_mkdtemp, mock_run, mock_listdir,
|
||||
mock_read, mock_rmtree):
|
||||
|
||||
secret = VaultSecret(b'test-pass')
|
||||
VaultSecretsContext.initialize(
|
||||
VaultSecretsContext(secrets=[('default', secret)]))
|
||||
parser = argparse.ArgumentParser()
|
||||
parsed_args = parser.parse_args([])
|
||||
dump_dir = "/path/to/dump"
|
||||
@@ -663,31 +681,31 @@ class TestCase(unittest.TestCase):
|
||||
mock_listdir.return_value = ["host1.yml", "host2.yml"]
|
||||
config = """---
|
||||
key1: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
633230623736383232323862393364323037343430393530316636363961626361393133646437
|
||||
643438663261356433656365646138666133383032376532310a63323432306431303437623637
|
||||
346236316161343635636230613838316566383933313338636237616338326439616536316639
|
||||
6334343462333062363334300a3930313762313463613537626531313230303731343365643766
|
||||
666436333037
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
65393836643335346138373665636564643436353231623838636261373565633731303835653139
|
||||
6335343464383063373734636161323236636431316532650a333366333366396262353635313531
|
||||
64666236636262326662323931313065376533333961356239363637333363623464666636616233
|
||||
6130373664393533350a663266613165646565346433313536313461653236303563643262323936
|
||||
6262
|
||||
key2: value2
|
||||
key3:
|
||||
- !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
633230623736383232323862393364323037343430393530316636363961626361393133646437
|
||||
643438663261356433656365646138666133383032376532310a63323432306431303437623637
|
||||
346236316161343635636230613838316566383933313338636237616338326439616536316639
|
||||
6334343462333062363334300a3930313762313463613537626531313230303731343365643766
|
||||
666436333037
|
||||
65393836643335346138373665636564643436353231623838636261373565633731303835653139
|
||||
6335343464383063373734636161323236636431316532650a333366333366396262353635313531
|
||||
64666236636262326662323931313065376533333961356239363637333363623464666636616233
|
||||
6130373664393533350a663266613165646565346433313536313461653236303563643262323936
|
||||
6262
|
||||
"""
|
||||
config_nested = """---
|
||||
key1:
|
||||
key2: !vault |
|
||||
$ANSIBLE_VAULT;1.1;AES256
|
||||
633230623736383232323862393364323037343430393530316636363961626361393133646437
|
||||
643438663261356433656365646138666133383032376532310a63323432306431303437623637
|
||||
346236316161343635636230613838316566383933313338636237616338326439616536316639
|
||||
6334343462333062363334300a3930313762313463613537626531313230303731343365643766
|
||||
666436333037
|
||||
65393836643335346138373665636564643436353231623838636261373565633731303835653139
|
||||
6335343464383063373734636161323236636431316532650a333366333366396262353635313531
|
||||
64666236636262326662323931313065376533333961356239363637333363623464666636616233
|
||||
6130373664393533350a663266613165646565346433313536313461653236303563643262323936
|
||||
6262
|
||||
"""
|
||||
mock_read.side_effect = [config, config_nested]
|
||||
result = ansible.config_dump(parsed_args)
|
||||
@@ -951,6 +969,7 @@ key1:
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
"ANSIBLE_COLLECTIONS_PATH": mock.ANY,
|
||||
@@ -994,6 +1013,7 @@ key1:
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"KAYOBE_ENVIRONMENT": "test-env",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
@@ -1036,6 +1056,7 @@ key1:
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"KAYOBE_ENVIRONMENT": "test-env",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
@@ -1079,6 +1100,7 @@ key1:
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"KAYOBE_ENVIRONMENT": "test-env",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
@@ -1127,6 +1149,7 @@ key1:
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"KAYOBE_ENVIRONMENT": "test-env",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
@@ -1207,6 +1230,7 @@ key1:
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {
|
||||
"ANSIBLE_ALLOW_BROKEN_CONDITIONALS": "true",
|
||||
"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"KAYOBE_ENVIRONMENT": "test-env",
|
||||
"ANSIBLE_ROLES_PATH": mock.ANY,
|
||||
|
||||
@@ -18,7 +18,15 @@ import subprocess
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
|
||||
# TODO(dougszu): Backwards compatibility for Ansible 11. This exception
|
||||
# handler can be removed in the G cycle.
|
||||
try:
|
||||
from ansible.parsing.vault import EncryptedString
|
||||
except ImportError:
|
||||
# Ansible 11
|
||||
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
|
||||
EncryptedString = AnsibleVaultEncryptedUnicode
|
||||
|
||||
import yaml
|
||||
|
||||
from kayobe import exception
|
||||
@@ -167,9 +175,9 @@ key3:
|
||||
mock_read.return_value = config
|
||||
result = utils.read_config_dump_yaml_file("/path/to/file")
|
||||
# Can't read the value without an encryption key, so just check type.
|
||||
self.assertIsInstance(result["key1"], AnsibleVaultEncryptedUnicode)
|
||||
self.assertIsInstance(result["key1"], EncryptedString)
|
||||
self.assertEqual(result["key2"], "value2")
|
||||
self.assertIsInstance(result["key3"][0], AnsibleVaultEncryptedUnicode)
|
||||
self.assertIsInstance(result["key3"][0], EncryptedString)
|
||||
mock_read.assert_called_once_with("/path/to/file")
|
||||
|
||||
@mock.patch.object(utils, "read_file")
|
||||
|
||||
@@ -189,7 +189,8 @@ def read_config_dump_yaml_file(path):
|
||||
sys.exit(1)
|
||||
try:
|
||||
# AnsibleLoader supports loading vault encrypted variables.
|
||||
return AnsibleLoader(content).get_single_data()
|
||||
data = AnsibleLoader(content).get_single_data()
|
||||
return data
|
||||
except yaml.YAMLError as e:
|
||||
print("Failed to decode config dump YAML file %s: %s" %
|
||||
(path, repr(e)))
|
||||
|
||||
@@ -11,11 +11,15 @@
|
||||
---
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: Testing become fails
|
||||
command: "true"
|
||||
become: true
|
||||
register: result
|
||||
failed_when: '"CONTROL_HOST_BECOME_VIOLATION" not in result.module_stderr'
|
||||
- block:
|
||||
- name: Testing become fails
|
||||
command: "true"
|
||||
become: true
|
||||
register: result
|
||||
rescue:
|
||||
- name: Check for become failure
|
||||
fail:
|
||||
when: '"CONTROL_HOST_BECOME_VIOLATION" not in result.msg'
|
||||
dest: /tmp/test-control-host-become.yml
|
||||
|
||||
- name: Check that that kayobe become validator was correctly configured
|
||||
|
||||
6
releasenotes/notes/bump-ansible-12-536bc4a3ff55dc3b.yaml
Normal file
6
releasenotes/notes/bump-ansible-12-536bc4a3ff55dc3b.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Updates the maximum supported version of Ansible from 12 (ansible-core
|
||||
2.18) to 13 (ansible-core 2.19). The minimum supported version is updated
|
||||
from 10.x to 11.x. This is true for both Kayobe and Kolla Ansible.
|
||||
@@ -1,6 +1,6 @@
|
||||
pbr>=2.0 # Apache-2.0
|
||||
Jinja2>3 # BSD
|
||||
ansible>=10,<12 # GPLv3
|
||||
ansible>=11,<13 # GPLv3
|
||||
cliff>=3.1.0 # Apache
|
||||
netaddr!=0.7.16,>=0.7.13 # BSD
|
||||
PyYAML>=3.10.0 # MIT
|
||||
|
||||
@@ -29,9 +29,9 @@ roles:
|
||||
version: 1.3.1
|
||||
- src: giovtorres.tuned
|
||||
version: 1.2.0
|
||||
- src: jriguera.configdrive
|
||||
# There are no versioned releases of this role.
|
||||
version: 71ddface5540ee0ff9e35bcc4334c766ed5d5d3f
|
||||
- src: git+https://github.com/stackhpc/ansible-role-configdrive.git
|
||||
name: jriguera.configdrive
|
||||
version: fb199247333e72e38a9d414cf7b6144daa645477
|
||||
- src: MichaelRigart.interfaces
|
||||
version: v1.15.6
|
||||
- src: mrlesmithjr.chrony
|
||||
@@ -46,8 +46,9 @@ roles:
|
||||
version: 1.1.6
|
||||
- src: stackhpc.drac-facts
|
||||
version: 1.0.0
|
||||
- src: stackhpc.libvirt-host
|
||||
version: v1.14.0
|
||||
- src: git+https://github.com/stackhpc/ansible-role-libvirt-host.git
|
||||
name: stackhpc.libvirt-host
|
||||
version: 9a947f74abdcd2e0d4e3371162f8299aef259271
|
||||
- src: stackhpc.libvirt-vm
|
||||
version: v1.16.3
|
||||
- src: stackhpc.luks
|
||||
|
||||
1
tox.ini
1
tox.ini
@@ -17,6 +17,7 @@ setenv =
|
||||
OS_STDOUT_CAPTURE=1
|
||||
OS_STDERR_CAPTURE=1
|
||||
OS_TEST_TIMEOUT=60
|
||||
ANSIBLE_VERBOSITY=3
|
||||
deps =
|
||||
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
|
||||
-r{toxinidir}/requirements.txt
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
- release-notes-jobs-python3
|
||||
check:
|
||||
jobs:
|
||||
- openstack-tox-py310:
|
||||
#NOTE(wszumski): We have dropped python3.10 support, so disable this job.
|
||||
files: THIS-JOB-IS-DISABLED
|
||||
- kayobe-tox-ansible-syntax
|
||||
- kayobe-tox-ansible
|
||||
- kayobe-tox-molecule
|
||||
@@ -31,6 +34,9 @@
|
||||
- kayobe-seed-vm-ubuntu-noble
|
||||
gate:
|
||||
jobs:
|
||||
- openstack-tox-py310:
|
||||
#NOTE(wszumski): We have dropped python3.10 support, so disable this job.
|
||||
files: THIS-JOB-IS-DISABLED
|
||||
- kayobe-tox-ansible-syntax
|
||||
- kayobe-tox-ansible
|
||||
- kayobe-tox-molecule
|
||||
|
||||
Reference in New Issue
Block a user