Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check validity of 'become_method' values from code #3499

Merged
merged 2 commits into from
May 31, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
WSLENV: FORCE_COLOR:PYTEST_REQPASS:TOXENV:GITHUB_STEP_SUMMARY
# Number of expected test passes, safety measure for accidental skip of
# tests. Update value if you add/remove tests.
PYTEST_REQPASS: 804
PYTEST_REQPASS: 806
steps:
- name: Activate WSL1
if: "contains(matrix.shell, 'wsl')"
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ repos:
# empty args needed in order to match mypy cli behavior
args: [--strict]
additional_dependencies:
- ansible-compat>=4.0.1
- ansible-compat>=4.1.0
- black>=22.10.0
- cryptography>=39.0.1
- filelock
Expand Down
10 changes: 10 additions & 0 deletions examples/playbooks/rule-schema-become-method-fail.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
- name: Test 'become_method' plugin validity
hosts: localhost
become: true
become_method: this_is_not_an_installed_plugin
tasks:
- name: Another example
ansible.builtin.debug:
msg: "This should not be reached"
become_method: this_is_not_an_installed_plugin
5 changes: 5 additions & 0 deletions examples/playbooks/rule-schema-become-method-pass.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- name: Test 'become_method' plugin validity
hosts: localhost
become: true
become_method: ansible.builtin.sudo
67 changes: 64 additions & 3 deletions src/ansiblelint/rules/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

import logging
import sys
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from ansiblelint.app import get_app
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable
from ansiblelint.rules import AnsibleLintRule
from ansiblelint.schemas.__main__ import JSON_SCHEMAS
from ansiblelint.schemas.main import validate_file_schema
from ansiblelint.text import has_jinja

if TYPE_CHECKING:
from ansiblelint.utils import Task
Expand Down Expand Up @@ -47,6 +49,10 @@
},
}

FIELD_CHECKS = {
"become_method": get_app().runtime.plugins.become.keys(), # pylint: disable=no-member
}


class ValidateSchemaRule(AnsibleLintRule):
"""Perform JSON Schema Validation for known lintable kinds."""
Expand Down Expand Up @@ -75,12 +81,49 @@ class ValidateSchemaRule(AnsibleLintRule):
"schema[vars]": "",
}

become_method_msg = f"'become_method' must be one of the currently installed plugins: {', '.join(FIELD_CHECKS['become_method'])}"

def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
"""Return matches found for a specific playbook."""
results: list[MatchError] = []
if not data or file.kind not in ("tasks", "handlers", "playbook"):
return results
# check at play level
for key, value in FIELD_CHECKS.items():
if key in data:
plugin_value = data.get(key, None)
if not has_jinja(plugin_value) and plugin_value not in value:
results.append(
MatchError(
message=self.become_method_msg,
lintable=file or Lintable(""),
rule=ValidateSchemaRule(),
details=ValidateSchemaRule.description,
tag=f"schema[{file.kind}]",
),
)

return results

def matchtask(
self,
task: Task,
file: Lintable | None = None,
) -> bool | str | MatchError | list[MatchError]:
result = []
for key, value in FIELD_CHECKS.items():
if key in task.raw_task:
plugin_value = task.raw_task.get(key, None)
if not has_jinja(plugin_value) and plugin_value not in value:
result.append(
MatchError(
message=self.become_method_msg,
lintable=file or Lintable(""),
rule=ValidateSchemaRule(),
details=ValidateSchemaRule.description,
tag=f"schema[{file.kind}]", # type: ignore[union-attr]
),
)
for key in pre_checks["task"]:
if key in task.raw_task:
msg = pre_checks["task"][key]["msg"]
Expand All @@ -98,9 +141,9 @@ def matchtask(

def matchyaml(self, file: Lintable) -> list[MatchError]:
"""Return JSON validation errors found as a list of MatchError(s)."""
result = []
result: list[MatchError] = []
if file.kind not in JSON_SCHEMAS:
return []
return result

errors = validate_file_schema(file)
if errors:
Expand All @@ -120,6 +163,9 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
tag=f"schema[{file.kind}]",
),
)

if not result:
result = super().matchyaml(file)
return result


audgirka marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -252,6 +298,21 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
[],
id="rulebook2",
),
pytest.param(
"examples/playbooks/rule-schema-become-method-pass.yml",
"playbook",
[],
id="playbook",
),
pytest.param(
"examples/playbooks/rule-schema-become-method-fail.yml",
"playbook",
[
"'become_method' must be one of the currently installed plugins",
"'become_method' must be one of the currently installed plugins",
],
id="playbook2",
),
),
)
def test_schema(file: str, expected_kind: str, expected: list[str]) -> None:
Expand Down