Skip to content

Commit a782eaf

Browse files
authored
Enable var-name rule to detect read-only variables (#3462)
1 parent 129a7fc commit a782eaf

File tree

3 files changed

+80
-1
lines changed

3 files changed

+80
-1
lines changed

examples/playbooks/vars/rule_var_naming_fail.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ CamelCaseButErrorIgnored: true # noqa: var-naming
99
assert: true # invalid due to being Python reserved keyword
1010
é: true # invalid due to non-ascii character
1111
hosts: true # invalid as being Ansible reserved name
12+
role_name: boo # invalid as being Ansible special magic variable
13+
ansible_facts: {} # special variable that we allow to be written
14+
ansible_python_interpreter: python3 # special variable that we allow to be written

src/ansiblelint/rules/var_naming.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ alphabetic or underscore `_` character.
99
For more information see the [creating valid variable names][var-names] topic in
1010
Ansible documentation and [Naming things (Good Practices for Ansible)][cop].
1111

12+
You should also be fully aware of [special variables][magic-vars], also known as
13+
magic variables, especially as most of them can only be read. While Ansible will
14+
just ignore any attempt to set them, the linter will notify the user, so they
15+
would not be confused about a line that does not effectively do anything.
16+
1217
Possible errors messages:
1318

1419
- `var-naming[non-string]`: Variables names must be strings.
@@ -19,6 +24,7 @@ Possible errors messages:
1924
- `var-naming[no-role-prefix]`: Variables names from within roles should use
2025
`role_name_` as a prefix.
2126
- `var-naming[no-reserved]`: Variables names must not be Ansible reserved names.
27+
- `var-naming[read-only]`: This special variable is read-only.
2228

2329
## Settings
2430

@@ -40,6 +46,7 @@ var_naming_pattern: "^[a-z_][a-z0-9_]*$"
4046
ALL_CAPS: bar # <- Contains only uppercase characters.
4147
v@r!able: baz # <- Contains special characters.
4248
hosts: [] # <- hosts is an Ansible reserved name
49+
role_name: boo # <-- invalid as being Ansible special magic variable
4350
```
4451
4552
## Correct Code
@@ -53,8 +60,11 @@ var_naming_pattern: "^[a-z_][a-z0-9_]*$"
5360
no_caps: bar # <- Does not contains uppercase characters.
5461
variable: baz # <- Does not contain special characters.
5562
my_hosts: [] # <- Does not use a reserved names.
63+
my_role_name: boo
5664
```
5765
5866
[cop]: https://redhat-cop.github.io/automation-good-practices/#_naming_things
5967
[var-names]:
6068
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#creating-valid-variable-names
69+
[magic-vars]:
70+
https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html

src/ansiblelint/rules/var_naming.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,64 @@ class VariableNamingRule(AnsibleLintRule):
3333
re_pattern_str = options.var_naming_pattern or "^[a-z_][a-z0-9_]*$"
3434
re_pattern = re.compile(re_pattern_str)
3535
reserved_names = get_reserved_names()
36+
# List of special variables that should be treated as read-only. This list
37+
# does not include connection variables, which we expect users to tune in
38+
# specific cases.
39+
# https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
40+
read_only_names = {
41+
"ansible_check_mode",
42+
"ansible_collection_name",
43+
"ansible_config_file",
44+
"ansible_dependent_role_names",
45+
"ansible_diff_mode",
46+
"ansible_forks",
47+
"ansible_index_var",
48+
"ansible_inventory_sources",
49+
"ansible_limit",
50+
"ansible_local", # special fact
51+
"ansible_loop",
52+
"ansible_loop_var",
53+
"ansible_parent_role_names",
54+
"ansible_parent_role_paths",
55+
"ansible_play_batch",
56+
"ansible_play_hosts",
57+
"ansible_play_hosts_all",
58+
"ansible_play_name",
59+
"ansible_play_role_names",
60+
"ansible_playbook_python",
61+
"ansible_role_name",
62+
"ansible_role_names",
63+
"ansible_run_tags",
64+
"ansible_search_path",
65+
"ansible_skip_tags",
66+
"ansible_verbosity",
67+
"ansible_version",
68+
"group_names",
69+
"groups",
70+
"hostvars",
71+
"inventory_dir",
72+
"inventory_file",
73+
"inventory_hostname",
74+
"inventory_hostname_short",
75+
"omit",
76+
"play_hosts",
77+
"playbook_dir",
78+
"role_name",
79+
"role_names",
80+
"role_path",
81+
}
82+
83+
# These special variables are used by Ansible but we allow users to set
84+
# them as they might need it in certain cases.
85+
allowed_special_names = {
86+
"ansible_facts",
87+
"ansible_become_user",
88+
"ansible_connection",
89+
"ansible_host",
90+
"ansible_python_interpreter",
91+
"ansible_user",
92+
"ansible_remote_tmp", # no included in docs
93+
}
3694

3795
# pylint: disable=too-many-return-statements
3896
def get_var_naming_matcherror(
@@ -49,7 +107,7 @@ def get_var_naming_matcherror(
49107
rule=self,
50108
)
51109

52-
if ident in ANNOTATION_KEYS:
110+
if ident in ANNOTATION_KEYS or ident in self.allowed_special_names:
53111
return None
54112

55113
try:
@@ -75,6 +133,13 @@ def get_var_naming_matcherror(
75133
rule=self,
76134
)
77135

136+
if ident in self.read_only_names:
137+
return MatchError(
138+
tag="var-naming[read-only]",
139+
message=f"This special variable is read-only. ({ident})",
140+
rule=self,
141+
)
142+
78143
# We want to allow use of jinja2 templating for variable names
79144
if "{{" in ident:
80145
return MatchError(
@@ -251,6 +316,7 @@ def test_invalid_var_name_varsfile(
251316
("var-naming[no-keyword]", 9),
252317
("var-naming[non-ascii]", 10),
253318
("var-naming[no-reserved]", 11),
319+
("var-naming[read-only]", 12),
254320
)
255321
assert len(results) == len(expected_errors)
256322
for idx, result in enumerate(results):

0 commit comments

Comments
 (0)