summaryrefslogtreecommitdiffstats
path: root/src/ansiblelint/rules/fqcn.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/ansiblelint/rules/fqcn.py')
-rw-r--r--src/ansiblelint/rules/fqcn.py84
1 files changed, 50 insertions, 34 deletions
diff --git a/src/ansiblelint/rules/fqcn.py b/src/ansiblelint/rules/fqcn.py
index 768fb9e..b571db3 100644
--- a/src/ansiblelint/rules/fqcn.py
+++ b/src/ansiblelint/rules/fqcn.py
@@ -1,17 +1,19 @@
"""Rule definition for usage of fully qualified collection names for builtins."""
+
from __future__ import annotations
import logging
import sys
from typing import TYPE_CHECKING, Any
-from ansible.plugins.loader import module_loader
+from ruamel.yaml.comments import CommentedSeq
from ansiblelint.constants import LINE_NUMBER_KEY
from ansiblelint.rules import AnsibleLintRule, TransformMixin
+from ansiblelint.utils import load_plugin
if TYPE_CHECKING:
- from ruamel.yaml.comments import CommentedMap, CommentedSeq
+ from ruamel.yaml.comments import CommentedMap
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable
@@ -114,11 +116,16 @@ class FQCNBuiltinsRule(AnsibleLintRule, TransformMixin):
task: Task,
file: Lintable | None = None,
) -> list[MatchError]:
- result = []
+ result: list[MatchError] = []
+ if file and file.failed():
+ return result
module = task["action"]["__ansible_module_original__"]
+ if not isinstance(module, str):
+ msg = "Invalid data for module."
+ raise TypeError(msg)
if module not in self.module_aliases:
- loaded_module = module_loader.find_plugin_with_context(module)
+ loaded_module = load_plugin(module)
target = loaded_module.resolved_fqcn
self.module_aliases[module] = target
if target is None:
@@ -137,40 +144,45 @@ class FQCNBuiltinsRule(AnsibleLintRule, TransformMixin):
1,
)
if module != legacy_module:
+ if module == "ansible.builtin.include":
+ message = f"Avoid deprecated module ({module})"
+ details = "Use `ansible.builtin.include_task` or `ansible.builtin.import_tasks` instead."
+ else:
+ message = f"Use FQCN for builtin module actions ({module})."
+ details = f"Use `{module_alias}` or `{legacy_module}` instead."
result.append(
self.create_matcherror(
- message=f"Use FQCN for builtin module actions ({module}).",
- details=f"Use `{module_alias}` or `{legacy_module}` instead.",
+ message=message,
+ details=details,
filename=file,
lineno=task["__line__"],
tag="fqcn[action-core]",
),
)
- else:
- if module.count(".") < 2:
- result.append(
- self.create_matcherror(
- message=f"Use FQCN for module actions, such `{self.module_aliases[module]}`.",
- details=f"Action `{module}` is not FQCN.",
- filename=file,
- lineno=task["__line__"],
- tag="fqcn[action]",
- ),
- )
- # TODO(ssbarnea): Remove the c.g. and c.n. exceptions from here once # noqa: FIX002
- # community team is flattening these.
- # https://github.com/ansible-community/community-topics/issues/147
- elif not module.startswith("community.general.") or module.startswith(
- "community.network.",
- ):
- result.append(
- self.create_matcherror(
- message=f"You should use canonical module name `{self.module_aliases[module]}` instead of `{module}`.",
- filename=file,
- lineno=task["__line__"],
- tag="fqcn[canonical]",
- ),
- )
+ elif module.count(".") < 2:
+ result.append(
+ self.create_matcherror(
+ message=f"Use FQCN for module actions, such `{self.module_aliases[module]}`.",
+ details=f"Action `{module}` is not FQCN.",
+ filename=file,
+ lineno=task["__line__"],
+ tag="fqcn[action]",
+ ),
+ )
+ # TODO(ssbarnea): Remove the c.g. and c.n. exceptions from here once # noqa: FIX002
+ # community team is flattening these.
+ # https://github.com/ansible-community/community-topics/issues/147
+ elif not module.startswith("community.general.") or module.startswith(
+ "community.network.",
+ ):
+ result.append(
+ self.create_matcherror(
+ message=f"You should use canonical module name `{self.module_aliases[module]}` instead of `{module}`.",
+ filename=file,
+ lineno=task["__line__"],
+ tag="fqcn[canonical]",
+ ),
+ )
return result
def matchyaml(self, file: Lintable) -> list[MatchError]:
@@ -220,6 +232,8 @@ class FQCNBuiltinsRule(AnsibleLintRule, TransformMixin):
target_task = self.seek(match.yaml_path, data)
# Unfortunately, a lot of data about Ansible content gets lost here, you only get a simple dict.
# For now, just parse the error messages for the data about action names etc. and fix this later.
+ current_action = ""
+ new_action = ""
if match.tag == "fqcn[action-core]":
# split at the first bracket, cut off the last bracket and dot
current_action = match.message.split("(")[1][:-2]
@@ -233,6 +247,8 @@ class FQCNBuiltinsRule(AnsibleLintRule, TransformMixin):
current_action = match.message.split("`")[3]
new_action = match.message.split("`")[1]
for _ in range(len(target_task)):
+ if isinstance(target_task, CommentedSeq):
+ continue
k, v = target_task.popitem(False)
target_task[new_action if k == current_action else k] = v
match.fixed = True
@@ -241,7 +257,7 @@ class FQCNBuiltinsRule(AnsibleLintRule, TransformMixin):
# testing code to be loaded only with pytest or when executed the rule file
if "pytest" in sys.modules:
from ansiblelint.rules import RulesCollection
- from ansiblelint.runner import Runner # pylint: disable=ungrouped-imports
+ from ansiblelint.runner import Runner
def test_fqcn_builtin_fail() -> None:
"""Test rule matches."""
@@ -269,7 +285,7 @@ if "pytest" in sys.modules:
"""Test rule matches."""
collection = RulesCollection()
collection.register(FQCNBuiltinsRule())
- failure = "examples/collection/plugins/modules/deep/beta.py"
+ failure = "examples/.collection/plugins/modules/deep/beta.py"
results = Runner(failure, rules=collection).run()
assert len(results) == 1
assert results[0].tag == "fqcn[deep]"
@@ -279,6 +295,6 @@ if "pytest" in sys.modules:
"""Test rule does not match."""
collection = RulesCollection()
collection.register(FQCNBuiltinsRule())
- success = "examples/collection/plugins/modules/alpha.py"
+ success = "examples/.collection/plugins/modules/alpha.py"
results = Runner(success, rules=collection).run()
assert len(results) == 0