From 134a0d4030ffd18a61c80817d0c328eaf03c9744 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 15 Apr 2024 18:03:39 +0200 Subject: [PATCH] Make wrapping variables as unsafe smarter to avoid triggering an AWX bug. --- changelogs/fragments/102-unsafe.yml | 2 ++ plugins/inventory/robot.py | 2 +- plugins/plugin_utils/unsafe.py | 41 ++++++++++++++++++++++ tests/unit/plugins/inventory/test_robot.py | 20 +++++------ 4 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 changelogs/fragments/102-unsafe.yml create mode 100644 plugins/plugin_utils/unsafe.py diff --git a/changelogs/fragments/102-unsafe.yml b/changelogs/fragments/102-unsafe.yml new file mode 100644 index 0000000..3e42912 --- /dev/null +++ b/changelogs/fragments/102-unsafe.yml @@ -0,0 +1,2 @@ +bugfixes: + - "inventory plugins - add unsafe wrapper to avoid marking strings that do not contain ``{`` or ``}`` as unsafe, to work around a bug in AWX (https://github.com/ansible-collections/community.hrobot/pull/102)." diff --git a/plugins/inventory/robot.py b/plugins/inventory/robot.py index 19a2f1a..409c909 100644 --- a/plugins/inventory/robot.py +++ b/plugins/inventory/robot.py @@ -85,13 +85,13 @@ from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable from ansible.template import Templar from ansible.utils.display import Display -from ansible.utils.unsafe_proxy import wrap_var as make_unsafe from ansible_collections.community.hrobot.plugins.module_utils.robot import ( BASE_URL, PluginException, plugin_open_url_json, ) +from ansible_collections.community.hrobot.plugins.plugin_utils.unsafe import make_unsafe display = Display() diff --git a/plugins/plugin_utils/unsafe.py b/plugins/plugin_utils/unsafe.py new file mode 100644 index 0000000..1eb61be --- /dev/null +++ b/plugins/plugin_utils/unsafe.py @@ -0,0 +1,41 @@ +# Copyright (c) 2023, Felix Fontein +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.module_utils.six import binary_type, text_type +from ansible.module_utils.common._collections_compat import Mapping, Set +from ansible.module_utils.common.collections import is_sequence +from ansible.utils.unsafe_proxy import ( + AnsibleUnsafe, + wrap_var as _make_unsafe, +) + +_RE_TEMPLATE_CHARS = re.compile(u'[{}]') +_RE_TEMPLATE_CHARS_BYTES = re.compile(b'[{}]') + + +def make_unsafe(value): + if value is None or isinstance(value, AnsibleUnsafe): + return value + + if isinstance(value, Mapping): + return dict((make_unsafe(key), make_unsafe(val)) for key, val in value.items()) + elif isinstance(value, Set): + return set(make_unsafe(elt) for elt in value) + elif is_sequence(value): + return type(value)(make_unsafe(elt) for elt in value) + elif isinstance(value, binary_type): + if _RE_TEMPLATE_CHARS_BYTES.search(value): + value = _make_unsafe(value) + return value + elif isinstance(value, text_type): + if _RE_TEMPLATE_CHARS.search(value): + value = _make_unsafe(value) + return value + + return value diff --git a/tests/unit/plugins/inventory/test_robot.py b/tests/unit/plugins/inventory/test_robot.py index d5514ae..c10524f 100644 --- a/tests/unit/plugins/inventory/test_robot.py +++ b/tests/unit/plugins/inventory/test_robot.py @@ -368,14 +368,14 @@ def test_unsafe(inventory, mocker): .result_json([ { 'server': { - 'server_ip': '1.2.3.4', + 'server_ip': '1.2.{3.4', 'dc': 'abc', }, }, { 'server': { 'server_ip': '1.2.3.5', - 'server_name': 'foo', + 'server_name': 'fo{o', 'dc': 'EVALU{{ "" }}ATED', }, }, @@ -389,27 +389,27 @@ def test_unsafe(inventory, mocker): open_url.assert_is_done() - host_1 = inventory.inventory.get_host('1.2.3.4') - host_2 = inventory.inventory.get_host('foo') + host_1 = inventory.inventory.get_host('1.2.{3.4') + host_2 = inventory.inventory.get_host('fo{o') host_1_vars = host_1.get_vars() host_2_vars = host_2.get_vars() - assert host_1_vars['ansible_host'] == '1.2.3.4' - assert host_1_vars['hrobot_server_ip'] == '1.2.3.4' + assert host_1_vars['ansible_host'] == '1.2.{3.4' + assert host_1_vars['hrobot_server_ip'] == '1.2.{3.4' assert host_1_vars['hrobot_dc'] == 'abc' assert host_2_vars['ansible_host'] == '1.2.3.5' assert host_2_vars['hrobot_server_ip'] == '1.2.3.5' - assert host_2_vars['hrobot_server_name'] == 'foo' + assert host_2_vars['hrobot_server_name'] == 'fo{o' assert host_2_vars['hrobot_dc'] == 'EVALU{{ "" }}ATED' # Make sure everything is unsafe assert isinstance(host_1_vars['ansible_host'], AnsibleUnsafe) assert isinstance(host_1_vars['hrobot_server_ip'], AnsibleUnsafe) - assert isinstance(host_1_vars['hrobot_dc'], AnsibleUnsafe) + assert not isinstance(host_1_vars['hrobot_dc'], AnsibleUnsafe) - assert isinstance(host_2_vars['ansible_host'], AnsibleUnsafe) - assert isinstance(host_2_vars['hrobot_server_ip'], AnsibleUnsafe) + assert not isinstance(host_2_vars['ansible_host'], AnsibleUnsafe) + assert not isinstance(host_2_vars['hrobot_server_ip'], AnsibleUnsafe) assert isinstance(host_2_vars['hrobot_server_name'], AnsibleUnsafe) assert isinstance(host_2_vars['hrobot_dc'], AnsibleUnsafe)