@@ -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