From 6921d99612615642c3b02b007cf653fc2b829fa5 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Sun, 7 May 2023 09:31:54 +0100 Subject: [PATCH] Change matchtask to receive a Task instance (#3403) --- docs/custom-rules.md | 3 ++- examples/rules/task_has_tag.py | 5 +++-- src/ansiblelint/_internal/rules.py | 3 ++- src/ansiblelint/errors.py | 7 +++++-- src/ansiblelint/rules/__init__.py | 8 ++++---- src/ansiblelint/rules/args.py | 3 ++- src/ansiblelint/rules/avoid_implicit.py | 5 +++-- .../rules/command_instead_of_module.py | 5 +++-- .../rules/command_instead_of_shell.py | 5 +++-- src/ansiblelint/rules/deprecated_bare_vars.py | 3 ++- src/ansiblelint/rules/deprecated_local_action.py | 5 +++-- src/ansiblelint/rules/empty_string_compare.py | 5 +++-- src/ansiblelint/rules/fqcn.py | 4 +++- src/ansiblelint/rules/ignore_errors.py | 5 +++-- src/ansiblelint/rules/inline_env_var.py | 6 +++--- src/ansiblelint/rules/jinja.py | 3 ++- src/ansiblelint/rules/key_order.py | 5 +++-- src/ansiblelint/rules/latest.py | 5 +++-- src/ansiblelint/rules/literal_compare.py | 5 +++-- src/ansiblelint/rules/loop_var_prefix.py | 11 ++++++----- src/ansiblelint/rules/name.py | 3 ++- src/ansiblelint/rules/no_changed_when.py | 7 ++++--- src/ansiblelint/rules/no_free_form.py | 5 +++-- src/ansiblelint/rules/no_handler.py | 5 +++-- src/ansiblelint/rules/no_jinja_when.py | 15 ++++++++++----- src/ansiblelint/rules/no_log_password.py | 6 +++--- src/ansiblelint/rules/no_prompting.py | 3 ++- src/ansiblelint/rules/no_relative_paths.py | 5 +++-- src/ansiblelint/rules/no_same_owner.py | 3 ++- src/ansiblelint/rules/no_tabs.py | 5 +++-- src/ansiblelint/rules/only_builtins.py | 5 +++-- src/ansiblelint/rules/package_latest.py | 5 +++-- src/ansiblelint/rules/risky_file_permissions.py | 5 +++-- src/ansiblelint/rules/risky_octal.py | 5 +++-- src/ansiblelint/rules/risky_shell_pipe.py | 5 +++-- src/ansiblelint/rules/role_name.py | 5 +++-- src/ansiblelint/rules/run_once.py | 3 ++- src/ansiblelint/rules/schema.py | 10 +++++++--- src/ansiblelint/rules/var_naming.py | 4 +++- src/ansiblelint/utils.py | 16 ++++++++++++++-- src/ansiblelint/yaml_utils.py | 8 +++++++- test/rules/fixtures/raw_task.py | 5 +++-- tox.ini | 1 - 43 files changed, 150 insertions(+), 85 deletions(-) diff --git a/docs/custom-rules.md b/docs/custom-rules.md index c772713bfb..e821a77e4d 100644 --- a/docs/custom-rules.md +++ b/docs/custom-rules.md @@ -58,6 +58,7 @@ from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class TaskHasTag(AnsibleLintRule): """Tasks must have tag.""" @@ -66,7 +67,7 @@ class TaskHasTag(AnsibleLintRule): description = 'Tasks must have tag' tags = ['productivity'] - def matchtask(self, task: Dict[str, Any], file: 'Lintable' | None = None) -> Union[bool,str]: + def matchtask(self, task: Task, file: 'Lintable' | None = None) -> Union[bool,str]: # If the task include another task or make the playbook fail # Don't force to have a tag if not set(task.keys()).isdisjoint(['include','fail']): diff --git a/examples/rules/task_has_tag.py b/examples/rules/task_has_tag.py index 8620cdbaa6..a4b927cdc6 100644 --- a/examples/rules/task_has_tag.py +++ b/examples/rules/task_has_tag.py @@ -1,12 +1,13 @@ """Example implementation of a rule requiring tasks to have tags set.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class TaskHasTag(AnsibleLintRule): @@ -18,7 +19,7 @@ class TaskHasTag(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: """Task matching method.""" diff --git a/src/ansiblelint/_internal/rules.py b/src/ansiblelint/_internal/rules.py index 9b6c4a412e..16c9b8ad27 100644 --- a/src/ansiblelint/_internal/rules.py +++ b/src/ansiblelint/_internal/rules.py @@ -12,6 +12,7 @@ from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.rules import RulesCollection + from ansiblelint.utils import Task _logger = logging.getLogger(__name__) LOAD_FAILURE_MD = """\ @@ -105,7 +106,7 @@ def matchlines(self, file: Lintable) -> list[MatchError]: def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str | MatchError | list[MatchError]: """Confirm if current rule is matching a specific task. diff --git a/src/ansiblelint/errors.py b/src/ansiblelint/errors.py index 60b0f789d6..99af2b302f 100644 --- a/src/ansiblelint/errors.py +++ b/src/ansiblelint/errors.py @@ -3,12 +3,15 @@ import functools from dataclasses import dataclass, field -from typing import Any +from typing import TYPE_CHECKING, Any from ansiblelint._internal.rules import BaseRule, RuntimeErrorRule from ansiblelint.config import options from ansiblelint.file_utils import Lintable +if TYPE_CHECKING: + from ansiblelint.utils import Task + class StrictModeError(RuntimeError): """Raise when we encounter a warning in strict mode.""" @@ -72,7 +75,7 @@ def __post_init__(self) -> None: self.match_type: str | None = None # for task matches, save the normalized task object (useful for transforms) - self.task: dict[str, Any] | None = None + self.task: Task | None = None # path to the problem area, like: [0,"pre_tasks",3] for [0].pre_tasks[3] self.yaml_path: list[int | str] = [] diff --git a/src/ansiblelint/rules/__init__.py b/src/ansiblelint/rules/__init__.py index 5625c1b283..9083dbd749 100644 --- a/src/ansiblelint/rules/__init__.py +++ b/src/ansiblelint/rules/__init__.py @@ -103,7 +103,7 @@ def create_matcherror( @staticmethod def _enrich_matcherror_with_task_details( match: MatchError, - task: dict[str, Any], + task: ansiblelint.utils.Task, ) -> None: match.task = task if not match.details: @@ -172,7 +172,7 @@ def matchtasks(self, file: Lintable) -> list[MatchError]: # noqa: C901 if self.needs_raw_task: task.normalized_task["__raw_task__"] = task.raw_task - result = self.matchtask(task.normalized_task, file=file) + result = self.matchtask(task, file=file) if not result: continue @@ -187,7 +187,7 @@ def matchtasks(self, file: Lintable) -> list[MatchError]: # noqa: C901 continue self._enrich_matcherror_with_task_details( match, - task.normalized_task, + task, ) matches.append(match) continue @@ -205,7 +205,7 @@ def matchtasks(self, file: Lintable) -> list[MatchError]: # noqa: C901 filename=file, ) - self._enrich_matcherror_with_task_details(match, task.normalized_task) + self._enrich_matcherror_with_task_details(match, task) matches.append(match) return matches diff --git a/src/ansiblelint/rules/args.py b/src/ansiblelint/rules/args.py index 4e36f04753..43167f3c77 100644 --- a/src/ansiblelint/rules/args.py +++ b/src/ansiblelint/rules/args.py @@ -28,6 +28,7 @@ if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task _logger = logging.getLogger(__name__) @@ -96,7 +97,7 @@ class ArgsRule(AnsibleLintRule): def matchtask( # noqa: C901 self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: # pylint: disable=too-many-branches,too-many-locals diff --git a/src/ansiblelint/rules/avoid_implicit.py b/src/ansiblelint/rules/avoid_implicit.py index 5729d8793c..8d1fe26058 100644 --- a/src/ansiblelint/rules/avoid_implicit.py +++ b/src/ansiblelint/rules/avoid_implicit.py @@ -3,12 +3,13 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class AvoidImplicitRule(AnsibleLintRule): @@ -26,7 +27,7 @@ class AvoidImplicitRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: """Confirm if current rule is matching a specific task.""" diff --git a/src/ansiblelint/rules/command_instead_of_module.py b/src/ansiblelint/rules/command_instead_of_module.py index 61ca405ff2..068e430790 100644 --- a/src/ansiblelint/rules/command_instead_of_module.py +++ b/src/ansiblelint/rules/command_instead_of_module.py @@ -22,13 +22,14 @@ import sys from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import convert_to_boolean, get_first_cmd_arg, get_second_cmd_arg if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class CommandsInsteadOfModulesRule(AnsibleLintRule): @@ -75,7 +76,7 @@ class CommandsInsteadOfModulesRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: if task["action"]["__ansible_module__"] not in self._commands: diff --git a/src/ansiblelint/rules/command_instead_of_shell.py b/src/ansiblelint/rules/command_instead_of_shell.py index 123ee489f8..346a071c1a 100644 --- a/src/ansiblelint/rules/command_instead_of_shell.py +++ b/src/ansiblelint/rules/command_instead_of_shell.py @@ -21,13 +21,14 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import get_cmd_args if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class UseCommandInsteadOfShellRule(AnsibleLintRule): @@ -45,7 +46,7 @@ class UseCommandInsteadOfShellRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: # Use unjinja so that we don't match on jinja filters diff --git a/src/ansiblelint/rules/deprecated_bare_vars.py b/src/ansiblelint/rules/deprecated_bare_vars.py index 5f4254db7e..1756e926df 100644 --- a/src/ansiblelint/rules/deprecated_bare_vars.py +++ b/src/ansiblelint/rules/deprecated_bare_vars.py @@ -31,6 +31,7 @@ if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class UsingBareVariablesIsDeprecatedRule(AnsibleLintRule): @@ -48,7 +49,7 @@ class UsingBareVariablesIsDeprecatedRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: loop_type = next((key for key in task if key.startswith("with_")), None) diff --git a/src/ansiblelint/rules/deprecated_local_action.py b/src/ansiblelint/rules/deprecated_local_action.py index 40bacc8d5f..fc3e4ff696 100644 --- a/src/ansiblelint/rules/deprecated_local_action.py +++ b/src/ansiblelint/rules/deprecated_local_action.py @@ -4,12 +4,13 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class TaskNoLocalAction(AnsibleLintRule): @@ -24,7 +25,7 @@ class TaskNoLocalAction(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: """Return matches for a task.""" diff --git a/src/ansiblelint/rules/empty_string_compare.py b/src/ansiblelint/rules/empty_string_compare.py index 4dbc8cca0c..5c7cafc88d 100644 --- a/src/ansiblelint/rules/empty_string_compare.py +++ b/src/ansiblelint/rules/empty_string_compare.py @@ -6,13 +6,14 @@ import re import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule from ansiblelint.yaml_utils import nested_items_path if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class ComparisonToEmptyStringRule(AnsibleLintRule): @@ -31,7 +32,7 @@ class ComparisonToEmptyStringRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: for k, v, _ in nested_items_path(task): diff --git a/src/ansiblelint/rules/fqcn.py b/src/ansiblelint/rules/fqcn.py index 01a3261e6b..c949511220 100644 --- a/src/ansiblelint/rules/fqcn.py +++ b/src/ansiblelint/rules/fqcn.py @@ -15,6 +15,8 @@ from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task + _logger = logging.getLogger(__name__) @@ -104,7 +106,7 @@ class FQCNBuiltinsRule(AnsibleLintRule, TransformMixin): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: result = [] diff --git a/src/ansiblelint/rules/ignore_errors.py b/src/ansiblelint/rules/ignore_errors.py index 271bf0de62..4144f2d5e7 100644 --- a/src/ansiblelint/rules/ignore_errors.py +++ b/src/ansiblelint/rules/ignore_errors.py @@ -2,12 +2,13 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class IgnoreErrorsRule(AnsibleLintRule): @@ -26,7 +27,7 @@ class IgnoreErrorsRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: if ( diff --git a/src/ansiblelint/rules/inline_env_var.py b/src/ansiblelint/rules/inline_env_var.py index 231b7db4f8..f578fb7203 100644 --- a/src/ansiblelint/rules/inline_env_var.py +++ b/src/ansiblelint/rules/inline_env_var.py @@ -20,11 +20,11 @@ # THE SOFTWARE. from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.constants import FILENAME_KEY, LINE_NUMBER_KEY from ansiblelint.rules import AnsibleLintRule -from ansiblelint.utils import get_first_cmd_arg +from ansiblelint.utils import Task, get_first_cmd_arg if TYPE_CHECKING: from ansiblelint.file_utils import Lintable @@ -61,7 +61,7 @@ class EnvVarsInCommandRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: if task["action"]["__ansible_module__"] in ["command"]: diff --git a/src/ansiblelint/rules/jinja.py b/src/ansiblelint/rules/jinja.py index 8a22eb4bc1..cf0c01b3aa 100644 --- a/src/ansiblelint/rules/jinja.py +++ b/src/ansiblelint/rules/jinja.py @@ -24,6 +24,7 @@ if TYPE_CHECKING: from ansiblelint.errors import MatchError + from ansiblelint.utils import Task _logger = logging.getLogger(__package__) @@ -77,7 +78,7 @@ def _msg(self, tag: str, value: str, reformatted: str) -> str: # pylint: disable=too-many-branches,too-many-locals def matchtask( # noqa: C901 self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: result = [] diff --git a/src/ansiblelint/rules/key_order.py b/src/ansiblelint/rules/key_order.py index 3e72195e58..6f466a3f10 100644 --- a/src/ansiblelint/rules/key_order.py +++ b/src/ansiblelint/rules/key_order.py @@ -3,13 +3,14 @@ import functools import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task SORTER_TASKS = ( @@ -57,7 +58,7 @@ class KeyOrderRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: result = [] diff --git a/src/ansiblelint/rules/latest.py b/src/ansiblelint/rules/latest.py index c51a4f8537..923669af6c 100644 --- a/src/ansiblelint/rules/latest.py +++ b/src/ansiblelint/rules/latest.py @@ -1,13 +1,14 @@ """Implementation of latest rule.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class LatestRule(AnsibleLintRule): @@ -24,7 +25,7 @@ class LatestRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str | MatchError: """Check if module args are safe.""" diff --git a/src/ansiblelint/rules/literal_compare.py b/src/ansiblelint/rules/literal_compare.py index 66f95e2d43..1129d1dd2b 100644 --- a/src/ansiblelint/rules/literal_compare.py +++ b/src/ansiblelint/rules/literal_compare.py @@ -6,13 +6,14 @@ import re import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule from ansiblelint.yaml_utils import nested_items_path if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class ComparisonToLiteralBoolRule(AnsibleLintRule): @@ -31,7 +32,7 @@ class ComparisonToLiteralBoolRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: for k, v, _ in nested_items_path(task): diff --git a/src/ansiblelint/rules/loop_var_prefix.py b/src/ansiblelint/rules/loop_var_prefix.py index 695ec123b2..be0c09b883 100644 --- a/src/ansiblelint/rules/loop_var_prefix.py +++ b/src/ansiblelint/rules/loop_var_prefix.py @@ -3,7 +3,7 @@ import re import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.config import LOOP_VAR_PREFIX, options from ansiblelint.rules import AnsibleLintRule @@ -12,6 +12,7 @@ if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class RoleLoopVarPrefix(AnsibleLintRule): @@ -32,7 +33,7 @@ class RoleLoopVarPrefix(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: """Return matches for a task.""" @@ -42,13 +43,13 @@ def matchtask( self.prefix = re.compile( options.loop_var_prefix.format(role=toidentifier(file.role)), ) - has_loop = "loop" in task - for key in task: + has_loop = "loop" in task.raw_task + for key in task.raw_task: if key.startswith("with_"): has_loop = True if has_loop: - loop_control = task.get("loop_control", {}) + loop_control = task.raw_task.get("loop_control", {}) loop_var = loop_control.get("loop_var", "") if loop_var: diff --git a/src/ansiblelint/rules/name.py b/src/ansiblelint/rules/name.py index a30f406e09..2a7944ae95 100644 --- a/src/ansiblelint/rules/name.py +++ b/src/ansiblelint/rules/name.py @@ -14,6 +14,7 @@ from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class NameRule(AnsibleLintRule, TransformMixin): @@ -54,7 +55,7 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: results = [] diff --git a/src/ansiblelint/rules/no_changed_when.py b/src/ansiblelint/rules/no_changed_when.py index 2eba4d80e6..28ba4275fe 100644 --- a/src/ansiblelint/rules/no_changed_when.py +++ b/src/ansiblelint/rules/no_changed_when.py @@ -22,13 +22,14 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class CommandHasChangesCheckRule(AnsibleLintRule): @@ -53,7 +54,7 @@ class CommandHasChangesCheckRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: result = [] @@ -62,7 +63,7 @@ def matchtask( task["__ansible_action_type__"] in ["task", "meta"] and task["action"]["__ansible_module__"] in self._commands and ( - "changed_when" not in task + "changed_when" not in task.raw_task and "creates" not in task["action"] and "removes" not in task["action"] ) diff --git a/src/ansiblelint/rules/no_free_form.py b/src/ansiblelint/rules/no_free_form.py index 1ee857e3af..4ba2385ab3 100644 --- a/src/ansiblelint/rules/no_free_form.py +++ b/src/ansiblelint/rules/no_free_form.py @@ -3,7 +3,7 @@ import re import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.constants import INCLUSION_ACTION_NAMES, LINE_NUMBER_KEY from ansiblelint.rules import AnsibleLintRule @@ -11,6 +11,7 @@ if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class NoFreeFormRule(AnsibleLintRule): @@ -28,7 +29,7 @@ class NoFreeFormRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: results: list[MatchError] = [] diff --git a/src/ansiblelint/rules/no_handler.py b/src/ansiblelint/rules/no_handler.py index 40ec5554f7..d0886a66dd 100644 --- a/src/ansiblelint/rules/no_handler.py +++ b/src/ansiblelint/rules/no_handler.py @@ -22,12 +22,13 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task def _changed_in_when(item: str) -> bool: @@ -65,7 +66,7 @@ class UseHandlerRatherThanWhenChangedRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: if task["__ansible_action_type__"] != "task": diff --git a/src/ansiblelint/rules/no_jinja_when.py b/src/ansiblelint/rules/no_jinja_when.py index c0de999111..807081d954 100644 --- a/src/ansiblelint/rules/no_jinja_when.py +++ b/src/ansiblelint/rules/no_jinja_when.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class NoFormattingInWhenRule(AnsibleLintRule): @@ -44,7 +45,11 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: if "roles" not in data or data["roles"] is None: return errors for role in data["roles"]: - if self.matchtask(role, file=file): + if ( + isinstance(role, dict) + and "when" in role + and not self._is_valid(role["when"]) + ): errors.append( self.create_matcherror( details=str({"when": role}), @@ -56,10 +61,10 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: - return "when" in task and not self._is_valid(task["when"]) + return "when" in task.raw_task and not self._is_valid(task.raw_task["when"]) if "pytest" in sys.modules: @@ -67,7 +72,7 @@ def matchtask( from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner - def test_file_positive() -> None: + def test_jinja_file_positive() -> None: """Positive test for no-jinja-when.""" collection = RulesCollection() collection.register(NoFormattingInWhenRule()) @@ -75,7 +80,7 @@ def test_file_positive() -> None: good_runner = Runner(success, rules=collection) assert [] == good_runner.run() - def test_file_negative() -> None: + def test_jinja_file_negative() -> None: """Negative test for no-jinja-when.""" collection = RulesCollection() collection.register(NoFormattingInWhenRule()) diff --git a/src/ansiblelint/rules/no_log_password.py b/src/ansiblelint/rules/no_log_password.py index 05a65ab787..7cc7439356 100644 --- a/src/ansiblelint/rules/no_log_password.py +++ b/src/ansiblelint/rules/no_log_password.py @@ -16,10 +16,10 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule -from ansiblelint.utils import convert_to_boolean +from ansiblelint.utils import Task, convert_to_boolean if TYPE_CHECKING: from ansiblelint.file_utils import Lintable @@ -39,7 +39,7 @@ class NoLogPasswordsRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: if task["action"]["__ansible_module_original__"] == "ansible.builtin.user" and ( diff --git a/src/ansiblelint/rules/no_prompting.py b/src/ansiblelint/rules/no_prompting.py index 5c748e63a6..662277157f 100644 --- a/src/ansiblelint/rules/no_prompting.py +++ b/src/ansiblelint/rules/no_prompting.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class NoPromptingRule(AnsibleLintRule): @@ -44,7 +45,7 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: """Return matches for ansible.builtin.pause tasks.""" diff --git a/src/ansiblelint/rules/no_relative_paths.py b/src/ansiblelint/rules/no_relative_paths.py index 82cca8a0aa..470b1b840d 100644 --- a/src/ansiblelint/rules/no_relative_paths.py +++ b/src/ansiblelint/rules/no_relative_paths.py @@ -5,12 +5,13 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class RoleRelativePath(AnsibleLintRule): @@ -31,7 +32,7 @@ class RoleRelativePath(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: module = task["action"]["__ansible_module__"] diff --git a/src/ansiblelint/rules/no_same_owner.py b/src/ansiblelint/rules/no_same_owner.py index 82d853df03..021900e984 100644 --- a/src/ansiblelint/rules/no_same_owner.py +++ b/src/ansiblelint/rules/no_same_owner.py @@ -11,6 +11,7 @@ if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class NoSameOwnerRule(AnsibleLintRule): @@ -27,7 +28,7 @@ class NoSameOwnerRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: """Return matches for a task.""" diff --git a/src/ansiblelint/rules/no_tabs.py b/src/ansiblelint/rules/no_tabs.py index 9337f3ac24..c53f1bbd4b 100644 --- a/src/ansiblelint/rules/no_tabs.py +++ b/src/ansiblelint/rules/no_tabs.py @@ -4,13 +4,14 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule from ansiblelint.yaml_utils import nested_items_path if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class NoTabsRule(AnsibleLintRule): @@ -38,7 +39,7 @@ class NoTabsRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: action = task["action"]["__ansible_module__"] diff --git a/src/ansiblelint/rules/only_builtins.py b/src/ansiblelint/rules/only_builtins.py index 463da00dd3..78ad93a9df 100644 --- a/src/ansiblelint/rules/only_builtins.py +++ b/src/ansiblelint/rules/only_builtins.py @@ -3,7 +3,7 @@ import os import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.config import options from ansiblelint.rules import AnsibleLintRule @@ -12,6 +12,7 @@ if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class OnlyBuiltinsRule(AnsibleLintRule): @@ -24,7 +25,7 @@ class OnlyBuiltinsRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: module = task["action"]["__ansible_module_original__"] diff --git a/src/ansiblelint/rules/package_latest.py b/src/ansiblelint/rules/package_latest.py index d1bd884b39..a00a5405e8 100644 --- a/src/ansiblelint/rules/package_latest.py +++ b/src/ansiblelint/rules/package_latest.py @@ -21,12 +21,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class PackageIsNotLatestRule(AnsibleLintRule): @@ -71,7 +72,7 @@ class PackageIsNotLatestRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: return ( diff --git a/src/ansiblelint/rules/risky_file_permissions.py b/src/ansiblelint/rules/risky_file_permissions.py index b1a7c63f9b..9fe43b650e 100644 --- a/src/ansiblelint/rules/risky_file_permissions.py +++ b/src/ansiblelint/rules/risky_file_permissions.py @@ -22,12 +22,13 @@ import sys from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task # Despite documentation mentioning 'preserve' only these modules support it: @@ -88,7 +89,7 @@ class MissingFilePermissionsRule(AnsibleLintRule): # pylint: disable=too-many-return-statements def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: module = task["action"]["__ansible_module__"] diff --git a/src/ansiblelint/rules/risky_octal.py b/src/ansiblelint/rules/risky_octal.py index e299db1bd2..e3651ea106 100644 --- a/src/ansiblelint/rules/risky_octal.py +++ b/src/ansiblelint/rules/risky_octal.py @@ -22,13 +22,14 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule, RulesCollection from ansiblelint.runner import Runner if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class OctalPermissionsRule(AnsibleLintRule): @@ -93,7 +94,7 @@ def is_invalid_permission(mode: int) -> bool: def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: if task["action"]["__ansible_module__"] in self._modules: diff --git a/src/ansiblelint/rules/risky_shell_pipe.py b/src/ansiblelint/rules/risky_shell_pipe.py index 66f9b84b54..58a6f5f08f 100644 --- a/src/ansiblelint/rules/risky_shell_pipe.py +++ b/src/ansiblelint/rules/risky_shell_pipe.py @@ -3,13 +3,14 @@ import re import sys -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import convert_to_boolean, get_cmd_args if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class ShellWithoutPipefail(AnsibleLintRule): @@ -33,7 +34,7 @@ class ShellWithoutPipefail(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: if task["__ansible_action_type__"] != "task": diff --git a/src/ansiblelint/rules/role_name.py b/src/ansiblelint/rules/role_name.py index 4ed9532608..3cec18a03e 100644 --- a/src/ansiblelint/rules/role_name.py +++ b/src/ansiblelint/rules/role_name.py @@ -24,7 +24,7 @@ import re import sys from functools import cache -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.constants import ROLE_IMPORT_ACTION_NAMES from ansiblelint.rules import AnsibleLintRule @@ -35,6 +35,7 @@ from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task ROLE_NAME_REGEX = re.compile(r"^[a-z][a-z0-9_]*$") @@ -64,7 +65,7 @@ class RoleNames(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: results = [] diff --git a/src/ansiblelint/rules/run_once.py b/src/ansiblelint/rules/run_once.py index 053c5853c8..f2835123e4 100644 --- a/src/ansiblelint/rules/run_once.py +++ b/src/ansiblelint/rules/run_once.py @@ -10,6 +10,7 @@ if TYPE_CHECKING: from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class RunOnce(AnsibleLintRule): @@ -45,7 +46,7 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: """Return matches for a task.""" diff --git a/src/ansiblelint/rules/schema.py b/src/ansiblelint/rules/schema.py index 92be23cf13..22b16782a4 100644 --- a/src/ansiblelint/rules/schema.py +++ b/src/ansiblelint/rules/schema.py @@ -3,7 +3,7 @@ import logging import sys -from typing import Any +from typing import TYPE_CHECKING from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable @@ -11,6 +11,10 @@ from ansiblelint.schemas.__main__ import JSON_SCHEMAS from ansiblelint.schemas.main import validate_file_schema +if TYPE_CHECKING: + from ansiblelint.utils import Task + + _logger = logging.getLogger(__name__) @@ -56,12 +60,12 @@ class ValidateSchemaRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str | MatchError | list[MatchError]: result = [] for key in pre_checks["task"]: - if key in task: + if key in task.raw_task: msg = pre_checks["task"][key]["msg"] tag = pre_checks["task"][key]["tag"] result.append( diff --git a/src/ansiblelint/rules/var_naming.py b/src/ansiblelint/rules/var_naming.py index d90129d905..ec7cbd0425 100644 --- a/src/ansiblelint/rules/var_naming.py +++ b/src/ansiblelint/rules/var_naming.py @@ -20,6 +20,8 @@ from pathlib import Path from ansiblelint.errors import MatchError + from ansiblelint.utils import Task + # Should raise var-naming at line [2, 6]. FAIL_VARS = """--- @@ -101,7 +103,7 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> list[MatchError]: """Return matches for task based variables.""" diff --git a/src/ansiblelint/utils.py b/src/ansiblelint/utils.py index a71b10e2dd..e770cb8fb4 100644 --- a/src/ansiblelint/utils.py +++ b/src/ansiblelint/utils.py @@ -27,7 +27,7 @@ import logging import os import re -from collections.abc import ItemsView, Iterator, Mapping, Sequence +from collections.abc import Generator, ItemsView, Iterator, Mapping, Sequence from dataclasses import _MISSING_TYPE, dataclass, field from functools import cache from pathlib import Path @@ -758,7 +758,7 @@ def extract_from_list( @dataclass -class Task: +class Task(dict[str, Any]): """Class that represents a task from linter point of view. raw_task: @@ -817,6 +817,18 @@ def __repr__(self) -> str: """Return a string representation of the task.""" return f"Task('{self.name}' [{self.position}])" + def get(self, key: str, default: Any = None) -> Any: + """Get a value from the task.""" + return self.normalized_task.get(key, default) + + def __getitem__(self, index: str) -> Any: + """Allow access as task[...].""" + return self.normalized_task[index] + + def __iter__(self) -> Generator[str, None, None]: + """Provide support for 'key in task'.""" + yield from (f for f in self.normalized_task) + def task_in_list( # noqa: C901 data: AnsibleBaseYAMLObject, diff --git a/src/ansiblelint/yaml_utils.py b/src/ansiblelint/yaml_utils.py index 539b7758df..d4db18b2f4 100644 --- a/src/ansiblelint/yaml_utils.py +++ b/src/ansiblelint/yaml_utils.py @@ -29,6 +29,7 @@ PLAYBOOK_TASK_KEYWORDS, SKIPPED_RULES_KEY, ) +from ansiblelint.utils import Task if TYPE_CHECKING: # noinspection PyProtectedMember @@ -171,8 +172,13 @@ def nested_items_path( # valid data, we better ignore NoneType if data_collection is None: return + data: dict[Any, Any] | list[Any] + if isinstance(data_collection, Task): + data = data_collection.normalized_task + else: + data = data_collection yield from _nested_items_path( - data_collection=data_collection, + data_collection=data, parent_path=[], ignored_keys=ignored_keys, ) diff --git a/test/rules/fixtures/raw_task.py b/test/rules/fixtures/raw_task.py index f9761f8167..0d5b023d23 100644 --- a/test/rules/fixtures/raw_task.py +++ b/test/rules/fixtures/raw_task.py @@ -1,12 +1,13 @@ """Test Rule that needs_raw_task.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from ansiblelint.file_utils import Lintable + from ansiblelint.utils import Task class RawTaskRule(AnsibleLintRule): @@ -19,7 +20,7 @@ class RawTaskRule(AnsibleLintRule): def matchtask( self, - task: dict[str, Any], + task: Task, file: Lintable | None = None, ) -> bool | str: """Match a task using __raw_task__ to inspect the module params type.""" diff --git a/tox.ini b/tox.ini index 7150a50d08..94281e7e0b 100644 --- a/tox.ini +++ b/tox.ini @@ -32,7 +32,6 @@ commands = --showlocals \ --doctest-modules \ --durations=10 \ - -m "not eco" \ } sh -c "coverage combine -a -q --data-file=.coverage .tox/.coverage.*"