Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/_autofix_rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
- [no-jinja-when](rules/no-jinja-when.md)
- [no-log-password](rules/no-log-password.md)
- [partial-become](rules/partial-become.md)
- [pattern](rules/pattern.md)
- [yaml](rules/yaml.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"schema_version": "1.0",
"name": "wrong_name",
"title": "Weather Forecasting",
"description": "This pattern is designed to help get the weather forecast for a given airport code. It creates a project, EE, and job templates in automation controller to get the weather forecast.",
"short_description": "This pattern is designed to help get the weather forecast for a given airport code.",
"tags": ["weather", "forecasting"],
"aap_resources": {
"controller_project": {
"name": "Weather Forecasting",
"description": "Project for the Weather Forecasting pattern"
},
"controller_execution_environment": {
"name": "Weather Forecasting",
"description": "EE for the Weather Forecasting pattern",
"image_name": "weather-demo-ee",
"pull": "missing"
},
"controller_labels": ["weather", "forecasting"],
"controller_job_templates": [
{
"name": "Get Weather Forecast",
"description": "This job template gets the weather at the location of a provided airport code.",
"execution_environment": "Weather Forecasting",
"playbook": "site.yml",
"primary": true,
"labels": ["weather", "forecasting"],
"survey": {
"name": "Weather Forecasting",
"description": "Survey to configure the weather forecasting pattern",
"spec": [
{
"type": "text",
"question_name": "Location",
"question_description": "Enter the airport code for which you want to get the weather forecast",
"variable": "location",
"required": true
}
]
}
}
]
}
}
3 changes: 2 additions & 1 deletion src/ansiblelint/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ def report_summary( # pylint: disable=too-many-locals # noqa: C901
summary.sort()

if changed_files_count:
console_stderr.print(f"Modified {changed_files_count} files.")
file_word = "file" if changed_files_count == 1 else "files"
console_stderr.print(f"Modified {changed_files_count} {file_word}.")

# determine which profile passed
summary.passed_profile = ""
Expand Down
68 changes: 66 additions & 2 deletions src/ansiblelint/rules/pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@
from __future__ import annotations

import json
import os
import sys
from pathlib import Path
from typing import TYPE_CHECKING
from unittest import mock

from ansiblelint.rules import AnsibleLintRule
from ansiblelint.rules import AnsibleLintRule, TransformMixin
from ansiblelint.runner import get_matches
from ansiblelint.transformer import Transformer

if TYPE_CHECKING:
from ruamel.yaml.comments import CommentedMap, CommentedSeq

from ansiblelint.config import Options
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable


class PatternRule(AnsibleLintRule):
class PatternRule(AnsibleLintRule, TransformMixin):
"""Rule for checking pattern directory."""

id = "pattern"
Expand Down Expand Up @@ -104,6 +111,30 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:

return results

def transform(
self,
match: MatchError,
lintable: Lintable,
data: CommentedMap | CommentedSeq | str,
) -> None:
"""Transform pattern.json to fix name-mismatch validation issues."""
if match.tag == f"{self.id}[name-mismatch]":
# Get the pattern directory name from the file path
# pattern.json should be located at: pattern_dir/meta/pattern.json
pattern_dir = lintable.path.parent.parent.name

# For JSON files, data is a string, so we need to parse it
if isinstance(data, str):
pattern_data = json.loads(data)
pattern_data["name"] = pattern_dir
lintable.content = json.dumps(pattern_data, indent=2)
else:
# For YAML files, data is CommentedMap/CommentedSeq
# This shouldn't happen for pattern.json, but just in case
data["name"] = pattern_dir

match.fixed = True


def values_from_pattern_json(file: Path) -> list[str]:
"""Extract playbook name and pattern name values from pattern.json file."""
Expand Down Expand Up @@ -164,3 +195,36 @@ def test_pattern(
for index, result in enumerate(results):
assert result.rule.id == PatternRule.id, result
assert result.tag == expected[index]

@pytest.mark.libyaml
@mock.patch.dict(os.environ, {"ANSIBLE_LINT_WRITE_TMP": "1"}, clear=True)
def test_pattern_transform(
config_options: Options,
) -> None:
"""Test transform functionality for pattern rule."""
pattern_file = Path(
"examples/collections/extensions/patterns/transform_pattern/meta/pattern.json"
)
config_options.write_list = ["pattern"]
rules = RulesCollection(options=config_options)
rules.register(PatternRule())

config_options.lintables = [str(pattern_file)]
runner_result = get_matches(rules=rules, options=config_options)
transformer = Transformer(result=runner_result, options=config_options)
transformer.run()

matches = runner_result.matches
assert len(matches) == 3

orig_content = pattern_file.read_text(encoding="utf-8")
transformed_content = pattern_file.with_suffix(
f".tmp{pattern_file.suffix}"
).read_text(
encoding="utf-8",
)

assert orig_content != transformed_content
transformed_name = json.loads(transformed_content)["name"]
assert transformed_name == "transform_pattern"
pattern_file.with_suffix(f".tmp{pattern_file.suffix}").unlink()
Loading