From ba233a0cbad76b4783a03893e7bf4716fbc0f0ec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 26 Jun 2024 08:24:58 +0200 Subject: Merging upstream version 24.6.1. Signed-off-by: Daniel Baumann --- src/ansiblelint/rules/no_free_form.py | 102 ++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 6 deletions(-) (limited to 'src/ansiblelint/rules/no_free_form.py') diff --git a/src/ansiblelint/rules/no_free_form.py b/src/ansiblelint/rules/no_free_form.py index e89333b..13489ef 100644 --- a/src/ansiblelint/rules/no_free_form.py +++ b/src/ansiblelint/rules/no_free_form.py @@ -1,20 +1,25 @@ """Implementation of NoFreeFormRule.""" + from __future__ import annotations +import functools import re import sys -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from ansiblelint.constants import INCLUSION_ACTION_NAMES, LINE_NUMBER_KEY -from ansiblelint.rules import AnsibleLintRule +from ansiblelint.rules import AnsibleLintRule, TransformMixin +from ansiblelint.rules.key_order import task_property_sorter if TYPE_CHECKING: + from ruamel.yaml.comments import CommentedMap, CommentedSeq + from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.utils import Task -class NoFreeFormRule(AnsibleLintRule): +class NoFreeFormRule(AnsibleLintRule, TransformMixin): """Rule for detecting discouraged free-form syntax for action modules.""" id = "no-free-form" @@ -75,7 +80,7 @@ class NoFreeFormRule(AnsibleLintRule): "win_command", "win_shell", ): - if self.cmd_shell_re.match(action_value): + if self.cmd_shell_re.search(action_value): fail = True else: fail = True @@ -89,12 +94,97 @@ class NoFreeFormRule(AnsibleLintRule): ) return results + def transform( + self, + match: MatchError, + lintable: Lintable, + data: CommentedMap | CommentedSeq | str, + ) -> None: + if "no-free-form" in match.tag: + task = self.seek(match.yaml_path, data) + + def filter_values( + val: str, + filter_key: str, + filter_dict: dict[str, Any], + ) -> str: + """Pull out key=value pairs from a string and set them in filter_dict. + + Returns unmatched strings. + """ + if filter_key not in val: + return val + + extra = "" + [k, v] = val.split(filter_key, 1) + if " " in k: + extra, k = k.rsplit(" ", 1) + + if v[0] in "\"'": + # Keep quoted strings together + quote = v[0] + _, v, remainder = v.split(quote, 2) + v = f"{quote}{v}{quote}" + else: + try: + v, remainder = v.split(" ", 1) + except ValueError: + remainder = "" + + filter_dict[k] = v + + extra = " ".join( + (extra, filter_values(remainder, filter_key, filter_dict)), + ) + return extra.strip() + + if match.tag == "no-free-form": + module_opts: dict[str, Any] = {} + for _ in range(len(task)): + k, v = task.popitem(False) + # identify module as key and process its value + if len(k.split(".")) == 3 and isinstance(v, str): + cmd = filter_values(v, "=", module_opts) + if cmd: + module_opts["cmd"] = cmd + + sorted_module_opts = {} + for key in sorted( + module_opts.keys(), + key=functools.cmp_to_key(task_property_sorter), + ): + sorted_module_opts[key] = module_opts[key] + + task[k] = sorted_module_opts + else: + task[k] = v + + match.fixed = True + elif match.tag == "no-free-form[raw]": + exec_key_val: dict[str, Any] = {} + for _ in range(len(task)): + k, v = task.popitem(False) + if isinstance(v, str) and "executable" in v: + # Filter the executable and other parts from the string + task[k] = " ".join( + [ + item + for item in v.split(" ") + if filter_values(item, "=", exec_key_val) + ], + ) + task["args"] = exec_key_val + else: + task[k] = v + match.fixed = True + if "pytest" in sys.modules: import pytest - from ansiblelint.rules import RulesCollection # pylint: disable=ungrouped-imports - from ansiblelint.runner import Runner # pylint: disable=ungrouped-imports + # pylint: disable=ungrouped-imports + from ansiblelint.rules import RulesCollection + from ansiblelint.runner import Runner @pytest.mark.parametrize( ("file", "expected"), -- cgit v1.2.3