From 446280ab6eb743054364ac27456899251f1bdc8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Sat, 17 Sep 2022 12:22:40 +0200 Subject: [PATCH] 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. --- README.md | 6 +++--- example/01-test-reconstructed.yml | 15 +++++++++------ inventory_plugins/reconstructed.py | 16 +++++++++++++++- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ca1b399..d96800d 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ I just don't forget about this whole thing. A `reconstructed` inventory executes a list of instructions that is read from the `instructions` YAML field. Each instruction is a table with some -minimal control flow (`when` and `loop` keywords that work mostly like their -playbook cousins), an `action` field that contains the name of the instruction -to execute, and whatever fields are needed for the instruction. +minimal control flow (`when`, `loop` and `run_once` keywords that work mostly +like their playbook cousins), an `action` field that contains the name of the +instruction to execute, and whatever fields are needed for the instruction. The following actions are supported: diff --git a/example/01-test-reconstructed.yml b/example/01-test-reconstructed.yml index f79185e..077d24f 100644 --- a/example/01-test-reconstructed.yml +++ b/example/01-test-reconstructed.yml @@ -30,12 +30,15 @@ instructions: - action: stop # Only create the managed groups if we *have* managed hosts - - action: create_group - group: managed - - loop: [by_environment, by_network, by_failover_stack, by_service] - action: create_group - group: "{{ item }}" - parent: managed + - action: block + run_once: true + block: + - action: create_group + group: 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 - loop: diff --git a/inventory_plugins/reconstructed.py b/inventory_plugins/reconstructed.py index 5cd2656..c3592b8 100644 --- a/inventory_plugins/reconstructed.py +++ b/inventory_plugins/reconstructed.py @@ -40,6 +40,8 @@ DOCUMENTATION = """ - The C(when) field, if present, must contain a Jinja expression representing a condition which will be checked before the instruction 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(block) action is another form of flow control, which can be used to repeat multiple instructions or make them obey a single @@ -91,7 +93,7 @@ DOCUMENTATION = """ 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.""" INSTR_OWN_FIELDS = { @@ -231,6 +233,7 @@ class RcInstruction(abc.ABC): self._loop = None self._loop_var = None self._action = action + self._executed_once = None def __repr__(self): """Builds a compact debugging representation of the instruction, \ @@ -242,6 +245,8 @@ class RcInstruction(abc.ABC): flow.append( "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: output = "{%s}" % (", ".join(flow),) else: @@ -269,6 +274,8 @@ class RcInstruction(abc.ABC): output.append("{when: %s}" % (repr(self._condition),)) if self._loop is not None: 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()) return output @@ -331,6 +338,9 @@ class RcInstruction(abc.ABC): raise AnsibleParserError( "%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 self.parse_action(record) @@ -395,6 +405,10 @@ class RcInstruction(abc.ABC): ``True`` if execution must continue, ``False`` if it must be interrupted """ + if self._executed_once is True: + return True + if self._executed_once is False: + self._executed_once = True if self._loop is None: self._display.vvvv("%s : running action %s" % (host_name, self._action)) return self.run_iteration(host_name, variables)