diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 1a3903a6b0..45edc4dc32 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -70,7 +70,7 @@ jobs: env: # Number of expected test passes, safety measure for accidental skip of # tests. Update value if you add/remove tests. - PYTEST_REQPASS: 831 + PYTEST_REQPASS: 832 steps: - uses: actions/checkout@v4 with: diff --git a/examples/playbooks/transform-key-order-play.transformed.yml b/examples/playbooks/transform-key-order-play.transformed.yml new file mode 100644 index 0000000000..030364d8a7 --- /dev/null +++ b/examples/playbooks/transform-key-order-play.transformed.yml @@ -0,0 +1,10 @@ +--- +- name: This is a playbook # <-- name key should be the first one + hosts: localhost + tasks: + - name: A block + when: true + block: + - name: Display a message + ansible.builtin.debug: + msg: Hello world! diff --git a/examples/playbooks/transform-key-order-play.yml b/examples/playbooks/transform-key-order-play.yml new file mode 100644 index 0000000000..e61920dca6 --- /dev/null +++ b/examples/playbooks/transform-key-order-play.yml @@ -0,0 +1,10 @@ +--- +- hosts: localhost + name: This is a playbook # <-- name key should be the first one + tasks: + - name: A block + when: true + block: + - name: Display a message + ansible.builtin.debug: + msg: Hello world! diff --git a/src/ansiblelint/rules/key_order.py b/src/ansiblelint/rules/key_order.py index 06b8b53d3c..2cef09df37 100644 --- a/src/ansiblelint/rules/key_order.py +++ b/src/ansiblelint/rules/key_order.py @@ -4,15 +4,15 @@ import functools import sys from dataclasses import dataclass -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from ansiblelint.errors import RuleMatchTransformMeta +from ansiblelint.constants import ANNOTATION_KEYS, LINE_NUMBER_KEY +from ansiblelint.errors import MatchError, RuleMatchTransformMeta from ansiblelint.rules import AnsibleLintRule, TransformMixin 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 @@ -77,6 +77,25 @@ class KeyOrderRule(AnsibleLintRule, TransformMixin): "key-order[task]": "You can improve the task key order", } + def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: + """Return matches found for a specific play (entry in playbook).""" + result: list[MatchError] = [] + if file.kind != "playbook": + return result + keys = [str(key) for key, val in data.items() if key not in ANNOTATION_KEYS] + sorted_keys = sorted(keys, key=functools.cmp_to_key(task_property_sorter)) + if keys != sorted_keys: + result.append( + self.create_matcherror( + f"You can improve the play key order to: {', '.join(sorted_keys)}", + filename=file, + tag=f"{self.id}[play]", + lineno=data[LINE_NUMBER_KEY], + transform_meta=KeyOrderTMeta(fixed=tuple(sorted_keys)), + ), + ) + return result + def matchtask( self, task: Task, @@ -103,9 +122,15 @@ def transform( lintable: Lintable, data: CommentedMap | CommentedSeq | str, ) -> None: - if match.tag == "key-order[task]": - if not isinstance(match.transform_meta, KeyOrderTMeta): - return + if not isinstance(match.transform_meta, KeyOrderTMeta): + return + + if match.tag == f"{self.id}[play]": + play = self.seek(match.yaml_path, data) + for key in match.transform_meta.fixed: + play[key] = play.pop(key) + match.fixed = True + if match.tag == f"{self.id}[task]": task = self.seek(match.yaml_path, data) for key in match.transform_meta.fixed: task[key] = task.pop(key) diff --git a/test/test_transformer.py b/test/test_transformer.py index 13af46a41c..bfe21fa2a3 100644 --- a/test/test_transformer.py +++ b/test/test_transformer.py @@ -127,6 +127,12 @@ def fixture_runner_result( True, id="partial_become", ), + pytest.param( + "examples/playbooks/transform-key-order-play.yml", + 1, + True, + id="key_order_play_transform", + ), ), ) @mock.patch.dict(os.environ, {"ANSIBLE_LINT_WRITE_TMP": "1"}, clear=True)