Implemented vars clause

* Can be used on any instruction
  * Variables are evaluated before condition is checked
This commit is contained in:
Emmanuel BENOîT 2022-09-18 10:06:19 +02:00
parent 2fd5ba9bb4
commit f2d1e2da50
2 changed files with 69 additions and 10 deletions

View file

@ -95,7 +95,7 @@ instructions:
# Component group. We add the host directly if there is no subcomponent. # Component group. We add the host directly if there is no subcomponent.
- when: inv__component is defined - when: inv__component is defined
action: block action: block
locals: vars:
comp_group: "svcm_{{ inv__service }}_{{ inv__component }}" comp_group: "svcm_{{ inv__service }}_{{ inv__component }}"
block: block:
- action: create_group - action: create_group

View file

@ -93,7 +93,7 @@ DOCUMENTATION = """
default: host default: host
""" """
INSTR_COMMON_FIELDS = ("when", "loop", "loop_var", "action", "run_once") INSTR_COMMON_FIELDS = ("action", "loop", "loop_var", "run_once", "vars", "when")
"""Fields that may be present on all instructions.""" """Fields that may be present on all instructions."""
INSTR_OWN_FIELDS = { INSTR_OWN_FIELDS = {
@ -229,11 +229,13 @@ class RcInstruction(abc.ABC):
self._inventory = inventory self._inventory = inventory
self._templar = templar self._templar = templar
self._display = display self._display = display
self._action = action
self._condition = None self._condition = None
self._executed_once = None
self._loop = None self._loop = None
self._loop_var = None self._loop_var = None
self._action = action self._vars = None
self._executed_once = None self._save = None
def __repr__(self): def __repr__(self):
"""Builds a compact debugging representation of the instruction, \ """Builds a compact debugging representation of the instruction, \
@ -338,12 +340,52 @@ class RcInstruction(abc.ABC):
raise AnsibleParserError( raise AnsibleParserError(
"%s: 'loop_var' clause found without 'loop'" % (self._action,) "%s: 'loop_var' clause found without 'loop'" % (self._action,)
) )
# Extract local variables
self._vars = self.parse_vars(record)
# Cache the list of variables to save before execution
save = list(self._vars.keys())
if self._loop is not None:
save.append(self._loop_var)
self._save = tuple(save)
# Handle instructions that may only be executed once # Handle instructions that may only be executed once
if record.get("run_once", False): if record.get("run_once", False):
self._executed_once = False self._executed_once = False
# Process action-specific fields # Process action-specific fields
self.parse_action(record) self.parse_action(record)
def parse_vars(self, record):
"""Parse local variable definitions from the record.
This method checks for a ``vars`` section in the YAML data, and extracts
it if it exists.
Args:
record: the YAML data for the instruction
Returns:
a dictionnary that contains the variable definitions
Raises:
AnsibleParserError: when the ``vars`` entry is invalid or contains \
invalid definitions
"""
if "vars" not in record:
return {}
if not isinstance(record["vars"], dict):
raise AnsibleParserError(
"%s: 'vars' should be a dictionnary" % (self._action,)
)
for k, v in record["vars"].items():
if not isinstance(k, string_types):
raise AnsibleParserError(
"%s: vars identifiers must be strings" % (self._action,)
)
if not isidentifier(k):
raise AnsibleParserError(
"%s: '%s' is not a valid identifier" % (self._action, k)
)
return record["vars"]
def parse_group_name(self, record, name): def parse_group_name(self, record, name):
"""Parse a field containing the name of a group, or a template. """Parse a field containing the name of a group, or a template.
@ -409,12 +451,13 @@ class RcInstruction(abc.ABC):
return True return True
if self._executed_once is False: if self._executed_once is False:
self._executed_once = True self._executed_once = True
if self._loop is None: # Save previous loop and local variables state
self._display.vvvv("%s : running action %s" % (host_name, self._action)) variables._script_stack_push(self._save)
return self.run_iteration(host_name, variables)
# Save previous loop variable state
variables._script_stack_push([self._loop_var])
try: try:
# Instructions without loops
if self._loop is None:
self._display.vvvv("%s : running action %s" % (host_name, self._action))
return self.run_iteration(host_name, variables)
# Loop over all values # Loop over all values
for value in self.evaluate_loop(host_name, variables): for value in self.evaluate_loop(host_name, variables):
self._display.vvvv( self._display.vvvv(
@ -440,6 +483,7 @@ class RcInstruction(abc.ABC):
``True`` if execution must continue, ``False`` if it must be ``True`` if execution must continue, ``False`` if it must be
interrupted interrupted
""" """
self.compute_locals(host_name, variables)
if self.evaluate_condition(host_name, variables): if self.evaluate_condition(host_name, variables):
rv = self.execute_action(host_name, variables) rv = self.execute_action(host_name, variables)
if not rv: if not rv:
@ -466,7 +510,6 @@ class RcInstruction(abc.ABC):
if self._condition is None: if self._condition is None:
return True return True
t = self._templar t = self._templar
t.available_variables = variables
template = "%s%s%s" % ( template = "%s%s%s" % (
t.environment.variable_start_string, t.environment.variable_start_string,
self._condition, self._condition,
@ -479,6 +522,22 @@ class RcInstruction(abc.ABC):
) )
return rv return rv
def compute_locals(self, host_name, variables):
"""Compute local variables.
This method iterates through all local variable definitions and runs
them through the templar.
Args:
host_name: the name of the host the instruction is being executed for
variables: the variable storage instance
"""
self._templar.available_variables = variables
for key, value in self._vars.items():
result = self._templar.template(value)
variables[key] = result
self._display.vvvv("- set local variable %s to %s" % (key, result))
def evaluate_loop(self, host_name, variables): def evaluate_loop(self, host_name, variables):
"""Evaluate the values to iterate over when a ``loop`` is defined. """Evaluate the values to iterate over when a ``loop`` is defined.