summaryrefslogtreecommitdiffstats
path: root/src/ansiblelint/rules/key_order.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/ansiblelint/rules/key_order.py')
-rw-r--r--src/ansiblelint/rules/key_order.py78
1 files changed, 71 insertions, 7 deletions
diff --git a/src/ansiblelint/rules/key_order.py b/src/ansiblelint/rules/key_order.py
index 897da64..0c0a2f1 100644
--- a/src/ansiblelint/rules/key_order.py
+++ b/src/ansiblelint/rules/key_order.py
@@ -1,14 +1,19 @@
"""All tasks should be have name come first."""
+
from __future__ import annotations
import functools
import sys
-from typing import TYPE_CHECKING
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, Any
-from ansiblelint.rules import AnsibleLintRule
+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 ansiblelint.errors import MatchError
+ from ruamel.yaml.comments import CommentedMap, CommentedSeq
+
from ansiblelint.file_utils import Lintable
from ansiblelint.utils import Task
@@ -46,7 +51,21 @@ def task_property_sorter(property1: str, property2: str) -> int:
return (v_1 > v_2) - (v_1 < v_2)
-class KeyOrderRule(AnsibleLintRule):
+@dataclass(frozen=True)
+class KeyOrderTMeta(RuleMatchTransformMeta):
+ """Key Order transform metadata.
+
+ :param fixed: tuple with updated key order
+ """
+
+ fixed: tuple[str | int, ...]
+
+ def __str__(self) -> str:
+ """Return string representation."""
+ return f"Fixed to {self.fixed}"
+
+
+class KeyOrderRule(AnsibleLintRule, TransformMixin):
"""Ensure specific order of keys in mappings."""
id = "key-order"
@@ -59,6 +78,25 @@ class KeyOrderRule(AnsibleLintRule):
"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,
@@ -66,7 +104,7 @@ class KeyOrderRule(AnsibleLintRule):
) -> list[MatchError]:
result = []
raw_task = task["__raw_task__"]
- keys = [key for key in raw_task if not key.startswith("_")]
+ keys = [str(key) for key in raw_task if not key.startswith("_")]
sorted_keys = sorted(keys, key=functools.cmp_to_key(task_property_sorter))
if keys != sorted_keys:
result.append(
@@ -74,17 +112,43 @@ class KeyOrderRule(AnsibleLintRule):
f"You can improve the task key order to: {', '.join(sorted_keys)}",
filename=file,
tag="key-order[task]",
+ transform_meta=KeyOrderTMeta(fixed=tuple(sorted_keys)),
),
)
return result
+ def transform(
+ self,
+ match: MatchError,
+ lintable: Lintable,
+ data: CommentedMap | CommentedSeq | str,
+ ) -> None:
+ 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:
+ # other transformation might change the key
+ if key in play:
+ 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:
+ # other transformation might change the key
+ if key in task:
+ task[key] = task.pop(key)
+ match.fixed = True
+
# testing code to be loaded only with pytest or when executed the rule file
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(
("test_file", "failures"),