Repository: ansible/ansible

Issue:
Error on until utilizing variable
##### ISSUE TYPE
- Bug Report
##### ANSIBLE VERSION

```
ansible 2.1.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

I don't do any special ansible configuring, just run my playbooks.
##### OS / ENVIRONMENT

Ubuntu 14.04
##### SUMMARY

When using `get_url` with an `until`, I'm getting an error.
##### STEPS TO REPRODUCE

Create a `site.yml` with the following contents (same problem if you use `ixgbevf_driver_file.stdout`):

```

---
- hosts: localhost
  tasks:
  - name: Get ixgbevf driver
    get_url: url="http://downloads.sourceforge.net/project/e1000/ixgbevf%20stable/2.16.4/ixgbevf-2.16.4.tar.gz" dest="/tmp/ixgbevf-2.16.4.tar.gz" mode=0440
    register: ixgbevf_driver_file
    until: "'OK' in ixgbevf_driver_file.msg or 'file already exists' in ixgbevf_driver_file.msg"
```

Try to run it.
##### EXPECTED RESULTS

I would expect it to continue along once the `msg` is either `OK` or `file already exists`.
##### ACTUAL RESULTS

```
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/ansible/executor/task_executor.py", line 124, in run
    res = self._execute()
  File "/usr/lib/python2.7/dist-packages/ansible/executor/task_executor.py", line 427, in _execute
    retries = self._task.retries + 1
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

fatal: [localhost]: FAILED! => {"failed": true, "msg": "Unexpected failure during module execution.", "stdout": ""}
```



Discussion:

Resolving patch:
diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py
--- a/lib/ansible/executor/task_executor.py
+++ b/lib/ansible/executor/task_executor.py
@@ -430,11 +430,13 @@ def _execute(self, variables=None):
 
         # Read some values from the task, so that we can modify them if need be
         if self._task.until:
-            retries = self._task.retries + 1
+            retries = self._task.retries
             if retries is None:
                 retries = 3
             elif retries <= 0:
                 retries = 1
+            else:
+                retries += 1
         else:
             retries = 1
 


Test patch:
diff --git a/test/integration/non_destructive.yml b/test/integration/non_destructive.yml
--- a/test/integration/non_destructive.yml
+++ b/test/integration/non_destructive.yml
@@ -20,6 +20,7 @@
     - { role: test_changed_when, tags: test_changed_when }
     - { role: test_failed_when, tags: test_failed_when }
     - { role: test_handlers, tags: test_handlers }
+    - { role: test_until, tags: test_until }
     - { role: test_copy, tags: test_copy }
     - { role: test_stat, tags: test_stat }
     - { role: test_template, tags: test_template }
diff --git a/test/integration/roles/test_until/tasks/main.yml b/test/integration/roles/test_until/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/roles/test_until/tasks/main.yml
@@ -0,0 +1,32 @@
+- shell: python -c "import tempfile; print(tempfile.mkstemp()[1])"
+  register: tempfilepath
+
+- set_fact:
+    until_tempfile_path: "{{ tempfilepath.stdout }}"
+
+- name: loop with default retries
+  shell: echo "run" >> {{ until_tempfile_path }} && wc -w < {{ until_tempfile_path }} | tr -d ' '
+  register: runcount
+  until: runcount.stdout | int == 3
+  delay: 0.01
+
+- assert:
+    that: runcount.stdout | int == 3
+
+- file: path="{{ until_tempfile_path }}" state=absent
+
+- name: loop with specified max retries
+  shell: echo "run" >> {{ until_tempfile_path }}
+  until: 1==0
+  retries: 5
+  delay: 0.01
+  ignore_errors: true
+
+- name: validate output
+  shell: wc -l < {{ until_tempfile_path }}
+  register: runcount
+
+- assert:
+    that: runcount.stdout | int == 6 # initial + 5 retries
+
+- file: path="{{ until_tempfile_path }}" state=absent