fix warnings about reserved variable names to cover all sources (#84432)

Also remove redundant check from tqm
Now covers module output (set_fact/include_vars)
Includes play objects at any stage (tasks that error were not covered)
Added tests, moved them to role structure
pull/84545/head
Brian Coca 11 months ago committed by GitHub
parent 96f7090acc
commit 20baf29a2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,2 @@
bugfixes:
- Ansible will now also warn when reserved keywords are set via a module (set_fact, include_vars, etc).

@ -39,7 +39,6 @@ from ansible.plugins.loader import callback_loader, strategy_loader, module_load
from ansible.plugins.callback import CallbackBase from ansible.plugins.callback import CallbackBase
from ansible.template import Templar from ansible.template import Templar
from ansible.vars.hostvars import HostVars from ansible.vars.hostvars import HostVars
from ansible.vars.reserved import warn_if_reserved
from ansible.utils.display import Display from ansible.utils.display import Display
from ansible.utils.lock import lock_decorator from ansible.utils.lock import lock_decorator
from ansible.utils.multiprocessing import context as multiprocessing_context from ansible.utils.multiprocessing import context as multiprocessing_context
@ -282,7 +281,6 @@ class TaskQueueManager:
all_vars = self._variable_manager.get_vars(play=play) all_vars = self._variable_manager.get_vars(play=play)
templar = Templar(loader=self._loader, variables=all_vars) templar = Templar(loader=self._loader, variables=all_vars)
warn_if_reserved(all_vars, templar.environment.globals.keys())
new_play = play.copy() new_play = play.copy()
new_play.post_validate(templar) new_play.post_validate(templar)

@ -31,7 +31,6 @@ from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles
from ansible.playbook.role import Role from ansible.playbook.role import Role
from ansible.playbook.task import Task from ansible.playbook.task import Task
from ansible.playbook.taggable import Taggable from ansible.playbook.taggable import Taggable
from ansible.vars.manager import preprocess_vars
from ansible.utils.display import Display from ansible.utils.display import Display
display = Display() display = Display()
@ -236,6 +235,9 @@ class Play(Base, Taggable, CollectionSearch):
return self.roles return self.roles
def _load_vars_prompt(self, attr, ds): def _load_vars_prompt(self, attr, ds):
# avoid circular dep
from ansible.vars.manager import preprocess_vars
new_ds = preprocess_vars(ds) new_ds = preprocess_vars(ds)
vars_prompts = [] vars_prompts = []
if new_ds is not None: if new_ds is not None:

@ -39,6 +39,7 @@ from ansible.utils.vars import combine_vars, load_extra_vars, load_options_vars
from ansible.utils.unsafe_proxy import wrap_var from ansible.utils.unsafe_proxy import wrap_var
from ansible.vars.clean import namespace_facts, clean_facts from ansible.vars.clean import namespace_facts, clean_facts
from ansible.vars.plugins import get_vars_from_inventory_sources, get_vars_from_path from ansible.vars.plugins import get_vars_from_inventory_sources, get_vars_from_path
from ansible.vars.reserved import warn_if_reserved
display = Display() display = Display()
@ -410,6 +411,9 @@ class VariableManager:
# extra vars # extra vars
all_vars = _combine_and_track(all_vars, self._extra_vars, "extra vars") all_vars = _combine_and_track(all_vars, self._extra_vars, "extra vars")
# before we add 'reserved vars', check we didn't add any reserved vars
warn_if_reserved(all_vars.keys())
# magic variables # magic variables
all_vars = _combine_and_track(all_vars, magic_variables, "magic vars") all_vars = _combine_and_track(all_vars, magic_variables, "magic vars")
@ -555,6 +559,7 @@ class VariableManager:
if not isinstance(facts, Mapping): if not isinstance(facts, Mapping):
raise AnsibleAssertionError("the type of 'facts' to set for host_facts should be a Mapping but is a %s" % type(facts)) raise AnsibleAssertionError("the type of 'facts' to set for host_facts should be a Mapping but is a %s" % type(facts))
warn_if_reserved(facts.keys())
try: try:
host_cache = self._fact_cache[host] host_cache = self._fact_cache[host]
except KeyError: except KeyError:
@ -578,6 +583,7 @@ class VariableManager:
if not isinstance(facts, Mapping): if not isinstance(facts, Mapping):
raise AnsibleAssertionError("the type of 'facts' to set for nonpersistent_facts should be a Mapping but is a %s" % type(facts)) raise AnsibleAssertionError("the type of 'facts' to set for nonpersistent_facts should be a Mapping but is a %s" % type(facts))
warn_if_reserved(facts.keys())
try: try:
self._nonpersistent_fact_cache[host] |= facts self._nonpersistent_fact_cache[host] |= facts
except KeyError: except KeyError:
@ -587,6 +593,8 @@ class VariableManager:
""" """
Sets a value in the vars_cache for a host. Sets a value in the vars_cache for a host.
""" """
warn_if_reserved(varname)
if host not in self._vars_cache: if host not in self._vars_cache:
self._vars_cache[host] = dict() self._vars_cache[host] = dict()
if varname in self._vars_cache[host] and isinstance(self._vars_cache[host][varname], MutableMapping) and isinstance(value, MutableMapping): if varname in self._vars_cache[host] and isinstance(self._vars_cache[host][varname], MutableMapping) and isinstance(value, MutableMapping):

@ -21,15 +21,17 @@ from ansible.playbook import Play
from ansible.playbook.block import Block from ansible.playbook.block import Block
from ansible.playbook.role import Role from ansible.playbook.role import Role
from ansible.playbook.task import Task from ansible.playbook.task import Task
from ansible.template import Templar
from ansible.utils.display import Display from ansible.utils.display import Display
display = Display() display = Display()
def get_reserved_names(include_private=True): def get_reserved_names(include_private: bool = True) -> set[str]:
""" this function returns the list of reserved names associated with play objects""" """ this function returns the list of reserved names associated with play objects"""
public = set() templar = Templar(loader=None)
public = set(templar.environment.globals.keys())
private = set() private = set()
result = set() result = set()
@ -61,7 +63,7 @@ def get_reserved_names(include_private=True):
return result return result
def warn_if_reserved(myvars, additional=None): def warn_if_reserved(myvars: list[str], additional: list[str] | None = None) -> None:
""" this function warns if any variable passed conflicts with internally reserved names """ """ this function warns if any variable passed conflicts with internally reserved names """
if additional is None: if additional is None:
@ -75,7 +77,7 @@ def warn_if_reserved(myvars, additional=None):
display.warning('Found variable using reserved name: %s' % varname) display.warning('Found variable using reserved name: %s' % varname)
def is_reserved_name(name): def is_reserved_name(name: str) -> bool:
return name in _RESERVED_NAMES return name in _RESERVED_NAMES

@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -eux
ansible-playbook reserved_varname_warning.yml "$@" 2>&1| grep 'Found variable using reserved name: lipsum'

@ -0,0 +1,8 @@
- hosts: localhost
gather_facts: false
tasks:
- name: test block
vars:
query: jinja2 uses me internally
block:
- debug:

@ -0,0 +1,23 @@
- name: check output for warning
vars:
canary: Found variable using reserved name
block:
- shell: ansible-playbook '{{[ role_path, "tasks", item ~ ".yml"] | path_join }}'
environment:
ANSIBLE_LOCALHOST_WARNING: 0
failed_when: false
loop:
- play_vars
- block_vars
- task_vars
- task_vars_used
- set_fact
register: play_out
- name: check they all complain about bad defined var
assert:
that:
- canary in item['stderr']
loop: '{{play_out.results}}'
loop_control:
label: '{{item.item}}'

@ -0,0 +1,5 @@
- hosts: localhost
gather_facts: false
tasks:
- set_fact:
lookup: jinja2 uses me internally

@ -0,0 +1,6 @@
- hosts: localhost
gather_facts: false
tasks:
- debug:
vars:
query: jinja2 uses me internally

@ -0,0 +1,8 @@
- hosts: localhost
gather_facts: false
tasks:
- name: task fails due to overriding q, but we should also see warning
debug:
msg: "{{q('pipe', 'pwd'}}"
vars:
q: jinja2 uses me internally
Loading…
Cancel
Save