Skip to content

Commit c4e37e4

Browse files
audgirkassbarnea
andauthored
Check validity of 'become_method' values from code (#3499)
Co-authored-by: Sorin Sbarnea <[email protected]>
1 parent 0f40faa commit c4e37e4

File tree

5 files changed

+81
-5
lines changed

5 files changed

+81
-5
lines changed

.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: 804
62+
PYTEST_REQPASS: 806
6363
steps:
6464
- name: Activate WSL1
6565
if: "contains(matrix.shell, 'wsl')"

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ repos:
146146
# empty args needed in order to match mypy cli behavior
147147
args: [--strict]
148148
additional_dependencies:
149-
- ansible-compat>=4.0.1
149+
- ansible-compat>=4.1.0
150150
- black>=22.10.0
151151
- cryptography>=39.0.1
152152
- filelock
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
- name: Test 'become_method' plugin validity
3+
hosts: localhost
4+
become: true
5+
become_method: this_is_not_an_installed_plugin
6+
tasks:
7+
- name: Another example
8+
ansible.builtin.debug:
9+
msg: "This should not be reached"
10+
become_method: this_is_not_an_installed_plugin
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
- name: Test 'become_method' plugin validity
3+
hosts: localhost
4+
become: true
5+
become_method: ansible.builtin.sudo

src/ansiblelint/rules/schema.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

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

8+
from ansiblelint.app import get_app
89
from ansiblelint.errors import MatchError
910
from ansiblelint.file_utils import Lintable
1011
from ansiblelint.rules import AnsibleLintRule
1112
from ansiblelint.schemas.__main__ import JSON_SCHEMAS
1213
from ansiblelint.schemas.main import validate_file_schema
14+
from ansiblelint.text import has_jinja
1315

1416
if TYPE_CHECKING:
1517
from ansiblelint.utils import Task
@@ -47,6 +49,10 @@
4749
},
4850
}
4951

52+
FIELD_CHECKS = {
53+
"become_method": get_app().runtime.plugins.become.keys(), # pylint: disable=no-member
54+
}
55+
5056

5157
class ValidateSchemaRule(AnsibleLintRule):
5258
"""Perform JSON Schema Validation for known lintable kinds."""
@@ -75,12 +81,49 @@ class ValidateSchemaRule(AnsibleLintRule):
7581
"schema[vars]": "",
7682
}
7783

84+
become_method_msg = f"'become_method' must be one of the currently installed plugins: {', '.join(FIELD_CHECKS['become_method'])}"
85+
86+
def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
87+
"""Return matches found for a specific playbook."""
88+
results: list[MatchError] = []
89+
if not data or file.kind not in ("tasks", "handlers", "playbook"):
90+
return results
91+
# check at play level
92+
for key, value in FIELD_CHECKS.items():
93+
if key in data:
94+
plugin_value = data.get(key, None)
95+
if not has_jinja(plugin_value) and plugin_value not in value:
96+
results.append(
97+
MatchError(
98+
message=self.become_method_msg,
99+
lintable=file or Lintable(""),
100+
rule=ValidateSchemaRule(),
101+
details=ValidateSchemaRule.description,
102+
tag=f"schema[{file.kind}]",
103+
),
104+
)
105+
106+
return results
107+
78108
def matchtask(
79109
self,
80110
task: Task,
81111
file: Lintable | None = None,
82112
) -> bool | str | MatchError | list[MatchError]:
83113
result = []
114+
for key, value in FIELD_CHECKS.items():
115+
if key in task.raw_task:
116+
plugin_value = task.raw_task.get(key, None)
117+
if not has_jinja(plugin_value) and plugin_value not in value:
118+
result.append(
119+
MatchError(
120+
message=self.become_method_msg,
121+
lintable=file or Lintable(""),
122+
rule=ValidateSchemaRule(),
123+
details=ValidateSchemaRule.description,
124+
tag=f"schema[{file.kind}]", # type: ignore[union-attr]
125+
),
126+
)
84127
for key in pre_checks["task"]:
85128
if key in task.raw_task:
86129
msg = pre_checks["task"][key]["msg"]
@@ -98,9 +141,9 @@ def matchtask(
98141

99142
def matchyaml(self, file: Lintable) -> list[MatchError]:
100143
"""Return JSON validation errors found as a list of MatchError(s)."""
101-
result = []
144+
result: list[MatchError] = []
102145
if file.kind not in JSON_SCHEMAS:
103-
return []
146+
return result
104147

105148
errors = validate_file_schema(file)
106149
if errors:
@@ -120,6 +163,9 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
120163
tag=f"schema[{file.kind}]",
121164
),
122165
)
166+
167+
if not result:
168+
result = super().matchyaml(file)
123169
return result
124170

125171

@@ -252,6 +298,21 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
252298
[],
253299
id="rulebook2",
254300
),
301+
pytest.param(
302+
"examples/playbooks/rule-schema-become-method-pass.yml",
303+
"playbook",
304+
[],
305+
id="playbook",
306+
),
307+
pytest.param(
308+
"examples/playbooks/rule-schema-become-method-fail.yml",
309+
"playbook",
310+
[
311+
"'become_method' must be one of the currently installed plugins",
312+
"'become_method' must be one of the currently installed plugins",
313+
],
314+
id="playbook2",
315+
),
255316
),
256317
)
257318
def test_schema(file: str, expected_kind: str, expected: list[str]) -> None:

0 commit comments

Comments
 (0)