-
Notifications
You must be signed in to change notification settings - Fork 666
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add experimental rule for run_once (#2626)
Co-authored-by: Ajinkya <audgirka@redhat.com>
- Loading branch information
Showing
7 changed files
with
148 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
- name: "Example with run_once" | ||
hosts: all | ||
strategy: free # <-- avoid use of strategy as free | ||
gather_facts: false | ||
tasks: | ||
- name: Task with run_once | ||
ansible.builtin.debug: | ||
msg: "Test" | ||
run_once: true # <-- avoid use of strategy as free at play level when using run_once at task level |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
- name: "Example without run_once" | ||
hosts: all | ||
gather_facts: false | ||
tasks: | ||
- name: Task without run_once | ||
ansible.builtin.debug: | ||
msg: "Test" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# run-once | ||
|
||
This rule warns against the use of `run_once` when `strategy` is set to `free`. | ||
|
||
This rule can produce the following messages: | ||
|
||
- `run_once[play]`: Play uses `strategy: free`. | ||
- `run_once[task]`: Using `run_once` may behave differently if `strategy` is set to `free`. | ||
|
||
For more information see the following topics in Ansible documentation: | ||
|
||
- [free strategy](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/free_strategy.html#free-strategy) | ||
- [selecting a strategy](https://docs.ansible.com/ansible/latest/user_guide/playbooks_strategies.html#selecting-a-strategy) | ||
- [run_once(playbook keyword) more info](https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html) | ||
|
||
## Problematic Code | ||
|
||
```yaml | ||
--- | ||
- name: "Example with run_once" | ||
hosts: all | ||
strategy: free # <-- avoid use of strategy as free | ||
gather_facts: false | ||
tasks: | ||
- name: Task with run_once | ||
ansible.builtin.debug: | ||
msg: "Test" | ||
run_once: true # <-- avoid use of strategy as free at play level when using run_once at task level | ||
``` | ||
## Correct Code | ||
```yaml | ||
- name: "Example without run_once" | ||
hosts: all | ||
gather_facts: false | ||
tasks: | ||
- name: Task without run_once | ||
ansible.builtin.debug: | ||
msg: "Test" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
"""Optional Ansible-lint rule to warn use of run_once with strategy free.""" | ||
from __future__ import annotations | ||
|
||
import sys | ||
from typing import TYPE_CHECKING, Any | ||
|
||
from ansiblelint.errors import MatchError | ||
from ansiblelint.rules import AnsibleLintRule | ||
|
||
if TYPE_CHECKING: | ||
from ansiblelint.file_utils import Lintable | ||
|
||
|
||
class RunOnce(AnsibleLintRule): | ||
"""Run once should use strategy other than free.""" | ||
|
||
id = "run-once" | ||
link = "https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html" | ||
description = "When using run_once, we should avoid using strategy as free." | ||
|
||
tags = ["idiom", "experimental"] | ||
severity = "MEDIUM" | ||
|
||
def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]: | ||
"""Return matches found for a specific playbook.""" | ||
# If the Play uses the 'strategy' and it's value is set to free | ||
|
||
if not file or file.kind != "playbook" or not data: | ||
return [] | ||
|
||
strategy = data.get("strategy", None) | ||
run_once = data.get("run_once", False) | ||
if (not strategy and not run_once) or strategy != "free": | ||
return [] | ||
return [ | ||
self.create_matcherror( | ||
message="Play uses strategy: free", | ||
filename=file, | ||
tag="run_once[play]", | ||
) | ||
] | ||
|
||
def matchtask( | ||
self, task: dict[str, Any], file: Lintable | None = None | ||
) -> list[MatchError]: | ||
"""Return matches for a task.""" | ||
if not file or file.kind != "playbook": | ||
return [] | ||
|
||
run_once = task.get("run_once", False) | ||
if not run_once: | ||
return [] | ||
return [ | ||
self.create_matcherror( | ||
message="Using run_once may behave differently if strategy is set to free.", | ||
filename=file, | ||
tag="run_once[task]", | ||
) | ||
] | ||
|
||
|
||
# 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 | ||
|
||
@pytest.mark.parametrize( | ||
("test_file", "failure"), | ||
( | ||
pytest.param("examples/playbooks/run-once-pass.yml", 0, id="pass"), | ||
pytest.param("examples/playbooks/run-once-fail.yml", 2, id="fail"), | ||
), | ||
) | ||
def test_run_once( | ||
default_rules_collection: RulesCollection, test_file: str, failure: int | ||
) -> None: | ||
"""Test rule matches.""" | ||
results = Runner(test_file, rules=default_rules_collection).run() | ||
for result in results: | ||
assert result.rule.id == RunOnce().id | ||
assert len(results) == failure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters