run_once clause

When the run_once clause is present and set to a truthy value, the
instruction it is attached to will only be executed the first time it is
encountered.
This commit is contained in:
Emmanuel BENOîT 2022-09-17 12:22:40 +02:00
parent 14fca45cb7
commit 446280ab6e
3 changed files with 27 additions and 10 deletions

View file

@ -8,9 +8,9 @@ I just don't forget about this whole thing.
A `reconstructed` inventory executes a list of instructions that is read A `reconstructed` inventory executes a list of instructions that is read
from the `instructions` YAML field. Each instruction is a table with some from the `instructions` YAML field. Each instruction is a table with some
minimal control flow (`when` and `loop` keywords that work mostly like their minimal control flow (`when`, `loop` and `run_once` keywords that work mostly
playbook cousins), an `action` field that contains the name of the instruction like their playbook cousins), an `action` field that contains the name of the
to execute, and whatever fields are needed for the instruction. instruction to execute, and whatever fields are needed for the instruction.
The following actions are supported: The following actions are supported:

View file

@ -30,12 +30,15 @@ instructions:
- action: stop - action: stop
# Only create the managed groups if we *have* managed hosts # Only create the managed groups if we *have* managed hosts
- action: create_group - action: block
group: managed run_once: true
- loop: [by_environment, by_network, by_failover_stack, by_service] block:
action: create_group - action: create_group
group: "{{ item }}" group: managed
parent: managed - loop: [by_environment, by_network, by_failover_stack, by_service]
action: create_group
group: "{{ item }}"
parent: managed
# Copy inv__data fields to separate inv__ variables # Copy inv__data fields to separate inv__ variables
- loop: - loop:

View file

@ -40,6 +40,8 @@ DOCUMENTATION = """
- The C(when) field, if present, must contain a Jinja expression - The C(when) field, if present, must contain a Jinja expression
representing a condition which will be checked before the instruction representing a condition which will be checked before the instruction
is executed. is executed.
- The C(run_once) field will ensure that the instuction it is attached
to will only run one time at most.
- The C(action) field must be set to one of the following values. - The C(action) field must be set to one of the following values.
- The C(block) action is another form of flow control, which can be - The C(block) action is another form of flow control, which can be
used to repeat multiple instructions or make them obey a single used to repeat multiple instructions or make them obey a single
@ -91,7 +93,7 @@ DOCUMENTATION = """
default: host default: host
""" """
INSTR_COMMON_FIELDS = ("when", "loop", "loop_var", "action") INSTR_COMMON_FIELDS = ("when", "loop", "loop_var", "action", "run_once")
"""Fields that may be present on all instructions.""" """Fields that may be present on all instructions."""
INSTR_OWN_FIELDS = { INSTR_OWN_FIELDS = {
@ -231,6 +233,7 @@ class RcInstruction(abc.ABC):
self._loop = None self._loop = None
self._loop_var = None self._loop_var = None
self._action = action self._action = action
self._executed_once = None
def __repr__(self): def __repr__(self):
"""Builds a compact debugging representation of the instruction, \ """Builds a compact debugging representation of the instruction, \
@ -242,6 +245,8 @@ class RcInstruction(abc.ABC):
flow.append( flow.append(
"loop=%s, loop_var=%s" % (repr(self._loop), repr(self._loop_var)) "loop=%s, loop_var=%s" % (repr(self._loop), repr(self._loop_var))
) )
if self._executed_once is not None:
flow.append("run_once")
if flow: if flow:
output = "{%s}" % (", ".join(flow),) output = "{%s}" % (", ".join(flow),)
else: else:
@ -269,6 +274,8 @@ class RcInstruction(abc.ABC):
output.append("{when: %s}" % (repr(self._condition),)) output.append("{when: %s}" % (repr(self._condition),))
if self._loop is not None: if self._loop is not None:
output.append("{loop[%s]: %s}" % (self._loop_var, repr(self._loop))) output.append("{loop[%s]: %s}" % (self._loop_var, repr(self._loop)))
if self._executed_once is not None:
output.append("{run_once}")
output.extend(self.dump_instruction()) output.extend(self.dump_instruction())
return output return output
@ -331,6 +338,9 @@ 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,)
) )
# Handle instructions that may only be executed once
if record.get("run_once", False):
self._executed_once = False
# Process action-specific fields # Process action-specific fields
self.parse_action(record) self.parse_action(record)
@ -395,6 +405,10 @@ 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
""" """
if self._executed_once is True:
return True
if self._executed_once is False:
self._executed_once = True
if self._loop is None: if self._loop is None:
self._display.vvvv("%s : running action %s" % (host_name, self._action)) self._display.vvvv("%s : running action %s" % (host_name, self._action))
return self.run_iteration(host_name, variables) return self.run_iteration(host_name, variables)