@@ -33,6 +33,64 @@ class VariableNamingRule(AnsibleLintRule):
33
33
re_pattern_str = options .var_naming_pattern or "^[a-z_][a-z0-9_]*$"
34
34
re_pattern = re .compile (re_pattern_str )
35
35
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
+ }
36
94
37
95
# pylint: disable=too-many-return-statements
38
96
def get_var_naming_matcherror (
@@ -49,7 +107,7 @@ def get_var_naming_matcherror(
49
107
rule = self ,
50
108
)
51
109
52
- if ident in ANNOTATION_KEYS :
110
+ if ident in ANNOTATION_KEYS or ident in self . allowed_special_names :
53
111
return None
54
112
55
113
try :
@@ -75,6 +133,13 @@ def get_var_naming_matcherror(
75
133
rule = self ,
76
134
)
77
135
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
+
78
143
# We want to allow use of jinja2 templating for variable names
79
144
if "{{" in ident :
80
145
return MatchError (
@@ -251,6 +316,7 @@ def test_invalid_var_name_varsfile(
251
316
("var-naming[no-keyword]" , 9 ),
252
317
("var-naming[non-ascii]" , 10 ),
253
318
("var-naming[no-reserved]" , 11 ),
319
+ ("var-naming[read-only]" , 12 ),
254
320
)
255
321
assert len (results ) == len (expected_errors )
256
322
for idx , result in enumerate (results ):
0 commit comments