diff --git a/docsite/latest/rst/playbooks2.rst b/docsite/latest/rst/playbooks2.rst index afb87e76043..8d3777bdbd2 100644 --- a/docsite/latest/rst/playbooks2.rst +++ b/docsite/latest/rst/playbooks2.rst @@ -461,6 +461,39 @@ from turning into arbitrary code with ugly nested ifs, conditionals, and so on - in more streamlined & auditable configuration rules -- especially because there are a minimum of decision points to track. +Do-Until +```````` + +Sometimes you would want to retry a task till a certain condition is met, In such conditions the Do/Until feature will help. +Here's an example which show's the syntax to be applied for the task.:: + + - action: shell /usr/bin/foo + register: result + until: register.stdout.find("all systems go") != -1 + retries: 5 + delay: 10 + +The above example run the shell module recursively till the module's result has "all systems go" in it's stdout or the task has +been retried for 5 times with a delay of 10 seconds. The default value for "retries" is 3 and "delay" is 5. + +The task returns the results returned by the last task run. The results of individual retries can be viewed by -vv option. +The results will have a new key "attempts" which will have the number of the retries for the task. + +.. Note:: + + The Do/Until does not take decision on whether to fail or pass the play when the maximum retries are completed, the user can + can do that in the next task as follows. + +Example:: + + - action: shell /usr/bin/foo + register: result + until: register.stdout.find("all systems go") != -1 + retries: 5 + delay: 10 + failed_when: result.attempts == 5 + + Loops ````` diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py index 9c29593f6f1..7abfea95dab 100644 --- a/lib/ansible/playbook/task.py +++ b/lib/ansible/playbook/task.py @@ -29,7 +29,7 @@ class Task(object): 'delegate_to', 'first_available_file', 'ignore_errors', 'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass', 'items_lookup_plugin', 'items_lookup_terms', 'environment', 'args', - 'any_errors_fatal', 'changed_when', 'failed_when', 'always_run' + 'any_errors_fatal', 'changed_when', 'failed_when', 'always_run', 'delay', 'retries', 'until' ] # to prevent typos and such @@ -38,9 +38,9 @@ class Task(object): 'first_available_file', 'include', 'tags', 'register', 'ignore_errors', 'delegate_to', 'local_action', 'transport', 'sudo', 'sudo_user', 'sudo_pass', 'when', 'connection', 'environment', 'args', - 'any_errors_fatal', 'changed_when', 'failed_when', 'always_run' + 'any_errors_fatal', 'changed_when', 'failed_when', 'always_run', 'delay', 'retries', 'until' ] - + def __init__(self, play, ds, module_vars=None, default_vars=None, additional_conditions=None, role_name=None): ''' constructor loads from a task or handler datastructure ''' @@ -111,6 +111,16 @@ class Task(object): self.sudo = utils.boolean(ds.get('sudo', play.sudo)) self.environment = ds.get('environment', {}) self.role_name = role_name + + #Code to allow do until feature in a Task + if 'until' in ds: + if not ds.get('register'): + raise errors.AnsibleError("register keyword is mandatory when using do until feature") + self.module_vars['delay'] = ds.get('delay', 5) + self.module_vars['retries'] = ds.get('retries', 3) + self.module_vars['register'] = ds.get('register', None) + self.until = "jinja2_compare %s" % (ds.get('until')) + self.module_vars['until'] = utils.compile_when_to_only_if(self.until) # rather than simple key=value args on the options line, these represent structured data and the values # can be hashes and lists, not just scalars diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 023c5c04930..ae636af9301 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -658,7 +658,30 @@ class Runner(object): result = handler.run(conn, tmp, module_name, module_args, inject, complex_args) - + # Code for do until feature + until = self.module_vars.get('until', None) + if until is not None and result.comm_ok: + inject[self.module_vars.get('register')] = result.result + cond = template.template(self.basedir, until, inject, expand_lists=False) + if not utils.check_conditional(cond, self.basedir, inject, fail_on_undefined=self.error_on_undefined_vars): + retries = self.module_vars.get('retries') + delay = self.module_vars.get('delay') + for x in range(1, retries + 1): + time.sleep(delay) + tmp = '' + if getattr(handler, 'NEEDS_TMPPATH', True): + tmp = self._make_tmp_path(conn) + result = handler.run(conn, tmp, module_name, module_args, inject, complex_args) + result.result['attempts'] = x + vv("Result from run %i is: %s" % (x, result.result)) + if not result.comm_ok: + break; + inject[self.module_vars.get('register')] = result.result + cond = template.template(self.basedir, until, inject, expand_lists=False) + if utils.check_conditional(cond, self.basedir, inject, fail_on_undefined=self.error_on_undefined_vars): + break; + else: + result.result['attempts'] = 0 conn.close() if not result.comm_ok: