1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
"""Rule definition for usage of builtin actions only."""
from __future__ import annotations
import os
import sys
from typing import TYPE_CHECKING
from ansiblelint.config import options
from ansiblelint.rules import AnsibleLintRule
from ansiblelint.rules.fqcn import builtins
from ansiblelint.skip_utils import is_nested_task
if TYPE_CHECKING:
from ansiblelint.file_utils import Lintable
from ansiblelint.utils import Task
class OnlyBuiltinsRule(AnsibleLintRule):
"""Use only builtin actions."""
id = "only-builtins"
severity = "MEDIUM"
description = "Check whether the playbook uses anything but ``ansible.builtin``"
tags = ["opt-in", "experimental"]
def matchtask(
self,
task: Task,
file: Lintable | None = None,
) -> bool | str:
module = task["action"]["__ansible_module_original__"]
allowed_collections = [
"ansible.builtin",
"ansible.legacy",
*options.only_builtins_allow_collections,
]
allowed_modules = builtins + options.only_builtins_allow_modules
is_allowed = (
any(module.startswith(f"{prefix}.") for prefix in allowed_collections)
or module in allowed_modules
)
return not is_allowed and not is_nested_task(task)
# testing code to be loaded only with pytest or when executed the rule file
if "pytest" in sys.modules:
# pylint: disable=ungrouped-imports
import pytest
from ansiblelint.constants import RC
from ansiblelint.testing import RunFromText, run_ansible_lint
SUCCESS_PLAY = """
- hosts: localhost
tasks:
- name: A block
block:
- name: Shell (fqcn)
ansible.builtin.shell: echo This rule should not get matched by the only-builtins rule
- name: Command with legacy FQCN
ansible.legacy.command: echo This rule should not get matched by the only-builtins rule
"""
def test_only_builtins_fail() -> None:
"""Test rule matches."""
env = os.environ.copy()
env["NO_COLOR"] = "1"
result = run_ansible_lint(
"--strict",
"--warn-list=",
"--enable-list",
"only-builtins",
"examples/playbooks/rule-only-builtins.yml",
env=env,
)
assert result.returncode == RC.VIOLATIONS_FOUND
assert "Failed" in result.stderr
assert "warning(s)" in result.stderr
assert "only-builtins: Use only builtin actions" in result.stdout
def test_only_builtins_allow() -> None:
"""Test rule doesn't match."""
conf_path = "examples/playbooks/.ansible-lint-only-builtins-allow"
result = run_ansible_lint(
f"--config-file={conf_path}",
"--strict",
"--warn-list=",
"--enable-list",
"only-builtins",
"examples/playbooks/rule-only-builtins.yml",
)
assert "only-builtins" not in result.stdout
assert result.returncode == RC.SUCCESS
@pytest.mark.parametrize(
"rule_runner",
(OnlyBuiltinsRule,),
indirect=["rule_runner"],
)
def test_only_builtin_pass(rule_runner: RunFromText) -> None:
"""Test rule does not match."""
results = rule_runner.run_playbook(SUCCESS_PLAY)
assert len(results) == 0, results
|