Skip to content

Commit ccdeab2

Browse files
ssbarneaMarkus Teufelberger
and
Markus Teufelberger
authored
Add ability to auto-fix fcqn rule violations (#3316)
Signed-off-by: Markus Teufelberger <[email protected]> Co-authored-by: Markus Teufelberger <[email protected]>
1 parent e157979 commit ccdeab2

File tree

7 files changed

+72
-6
lines changed

7 files changed

+72
-6
lines changed

.config/dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ jsonfile
174174
jsonschema
175175
junitxml
176176
keepends
177+
keypair
177178
keyserver
178179
konstruktoid
179180
kubernetes

.github/workflows/tox.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
WSLENV: FORCE_COLOR:PYTEST_REQPASS:TOXENV:GITHUB_STEP_SUMMARY
6060
# Number of expected test passes, safety measure for accidental skip of
6161
# tests. Update value if you add/remove tests.
62-
PYTEST_REQPASS: 794
62+
PYTEST_REQPASS: 795
6363
steps:
6464
- name: Activate WSL1
6565
if: "contains(matrix.shell, 'wsl')"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
- name: FQCN transform test file
3+
hosts: localhost
4+
tasks:
5+
- name: Rewrite shell to ansible.builtin.shell via the fqcn[action-core] transform # noqa: command-instead-of-shell
6+
ansible.builtin.shell: echo This rule should get matched by the fqcn[action-core] rule
7+
changed_when: false
8+
- name: Rewrite openssh_keypair to community.crypto.openssh_keypair via the fqcn[action] transform
9+
community.crypto.openssh_keypair:
10+
path: /tmp/supersecret
11+
- name: Rewrite ansible.builtin.synchronize to ansible.posix.synchronize via the fqcn[canonical] transform
12+
ansible.posix.synchronize:
13+
src: dummy
14+
dest: dummy2
15+
owner: false
16+
group: false

examples/playbooks/fqcn.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
- name: FQCN transform test file
3+
hosts: localhost
4+
tasks:
5+
- name: Rewrite shell to ansible.builtin.shell via the fqcn[action-core] transform # noqa: command-instead-of-shell
6+
shell: echo This rule should get matched by the fqcn[action-core] rule
7+
changed_when: false
8+
- name: Rewrite openssh_keypair to community.crypto.openssh_keypair via the fqcn[action] transform
9+
openssh_keypair:
10+
path: /tmp/supersecret
11+
- name: Rewrite ansible.builtin.synchronize to ansible.posix.synchronize via the fqcn[canonical] transform
12+
ansible.builtin.synchronize:
13+
src: dummy
14+
dest: dummy2
15+
owner: false
16+
group: false

src/ansiblelint/rules/fqcn.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33

44
import logging
55
import sys
6-
from typing import Any
6+
from typing import TYPE_CHECKING, Any
77

88
from ansible.plugins.loader import module_loader
99

1010
from ansiblelint.constants import LINE_NUMBER_KEY
1111
from ansiblelint.errors import MatchError
12-
from ansiblelint.file_utils import Lintable
13-
from ansiblelint.rules import AnsibleLintRule, RulesCollection
12+
from ansiblelint.rules import AnsibleLintRule, TransformMixin
13+
14+
if TYPE_CHECKING:
15+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
16+
17+
from ansiblelint.file_utils import Lintable # noqa: F811
1418

1519
_logger = logging.getLogger(__name__)
1620

@@ -86,7 +90,7 @@
8690
]
8791

8892

89-
class FQCNBuiltinsRule(AnsibleLintRule):
93+
class FQCNBuiltinsRule(AnsibleLintRule, TransformMixin):
9094
"""Use FQCN for builtin actions."""
9195

9296
id = "fqcn"
@@ -176,9 +180,37 @@ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
176180
]
177181
return []
178182

183+
def transform(
184+
self,
185+
match: MatchError,
186+
lintable: Lintable,
187+
data: CommentedMap | CommentedSeq | str,
188+
) -> None:
189+
if match.tag in {"fqcn[action-core]", "fqcn[action]", "fqcn[canonical]"}:
190+
target_task = self.seek(match.yaml_path, data)
191+
# Unfortunately, a lot of data about Ansible content gets lost here, you only get a simple dict.
192+
# For now, just parse the error messages for the data about action names etc. and fix this later.
193+
if match.tag == "fqcn[action-core]":
194+
# split at the first bracket, cut off the last bracket and dot
195+
current_action = match.message.split("(")[1][:-2]
196+
# This will always replace builtin modules with "ansible.builtin" versions, not "ansible.legacy".
197+
# The latter is technically more correct in what ansible has executed so far, the former is most likely better understood and more robust.
198+
new_action = match.details.split("`")[1]
199+
elif match.tag == "fqcn[action]":
200+
current_action = match.details.split("`")[1]
201+
new_action = match.message.split("`")[1]
202+
elif match.tag == "fqcn[canonical]":
203+
current_action = match.message.split("`")[3]
204+
new_action = match.message.split("`")[1]
205+
for _ in range(len(target_task)):
206+
k, v = target_task.popitem(False)
207+
target_task[new_action if k == current_action else k] = v
208+
match.fixed = True
209+
179210

180211
# testing code to be loaded only with pytest or when executed the rule file
181212
if "pytest" in sys.modules:
213+
from ansiblelint.rules import RulesCollection
182214
from ansiblelint.runner import Runner # pylint: disable=ungrouped-imports
183215

184216
def test_fqcn_builtin_fail() -> None:

test/test_transformer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def fixture_runner_result(
7878
),
7979
pytest.param("examples/playbooks/vars/strings.yml", 0, True, id="strings"),
8080
pytest.param("examples/playbooks/name-case.yml", 1, True, id="name_case"),
81+
pytest.param("examples/playbooks/fqcn.yml", 3, True, id="fqcn"),
8182
),
8283
)
8384
def test_transformer( # pylint: disable=too-many-arguments, too-many-locals

tools/install-reqs.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ set -euo pipefail
33
pushd examples/playbooks/collections >/dev/null
44
MISSING=()
55
export ANSIBLE_COLLECTIONS_PATH=.
6-
for COLLECTION in ansible.posix community.docker community.general community.molecule ansible.windows;
6+
for COLLECTION in ansible.posix community.docker community.general community.molecule ansible.windows community.crypto;
77
do
88
COL_NAME=${COLLECTION//\./-}
99
FILENAME=$(find . -maxdepth 1 -name "$COL_NAME*" -print -quit)

0 commit comments

Comments
 (0)