Repository: ansible/ansible

Issue:
with_items and args:item yields 'args must be a dictionary' error
##### Issue Type:

Bug Report
##### Ansible Version:

1.5
##### Environment:

Centos 6.5
##### Summary:

After upgrading to 1.5, using `with_items` and `args: item` yields `args must be a dictionary, received item (eol)` error.
##### Steps To Reproduce:

``` yaml
- hosts: localhost
  vars:
    test_vars:
      - { foo: foo, bar: bar }
      - { foo: bar, bar: foo }

  tasks:
    - name: test
      debug: msg=test
      with_items: test_vars
      args: item
```
##### Expected Results:

on ansible 1.4.5

```
% ansible-playbook test.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [test] ******************************************************************
ok: [localhost] => (item={'foo': 'foo', 'bar': 'bar'}) => {
    "item": {
        "bar": "bar",
        "foo": "foo"
    },
    "msg": "test"
}
ok: [localhost] => (item={'foo': 'bar', 'bar': 'foo'}) => {
    "item": {
        "bar": "foo",
        "foo": "bar"
    },
    "msg": "test"
}

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```
##### Actual Results:

on ansible 1.5

```
% ansible-playbook test.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [test] ******************************************************************
fatal: [localhost] => args must be a dictionary, received item

FATAL: all hosts have already failed -- aborting

PLAY RECAP ********************************************************************
           to retry, use: --limit @/home/solar/test.yaml.retry

localhost                  : ok=1    changed=0    unreachable=1    failed=0
```



Discussion:
I am not completely clear about the code behind this. I can agree that I tested that this had worked as you described. Though going forward it clearly doesn't.

So far I don't see a problem here with the code but that your example doesn't line up with what is described in the documentation.

I am able to get what you are trying to do with a little bit of playbook reformatting:

``` YAML
---
- hosts: localhost
  vars:
    test_vars:
      - { foo: foo, bar: bar }
      - { foo: bar, bar: foo }

  tasks:
    - name: test
      debug: msg=test
      with_items: test_vars
```

I am still trying to figure out the "why" of this change but for now the above should work for you.

Looks like a minor bug in regards to "with_items" when used with complex args, yes.   I'd probably consider this a little bit low priority given I don't think we emphasize "args:" in most places at all, but it would be nice if continued to work.

Most folks will do something like this to use complex args:

```
- ec2: 
    key_pair: foo
    ...
```

Rather than having an explicit "args:"  ... I need to comb our docs for this, but yes, it still ought to work.

I've beein using `with_items` and `args: item` like this.

```
# vars are actually defined in role's defaults/main.yaml and inventory configs
vars:
  default_packages:
    - name: gcc
    - name: make
    - name: nodejs
      enablerepo: epel
  packages:
    - name: npm
      enablerepo: epel

tasks:
  - name: install yum packages
    yum: state=present
    with_items: default_packages + packages
    args: item
```

I'm sure I can rewrite like this, so I don't think this issue has high priority too.

```
tasks:
  - name: install yum packages
    yum: name={{ item.name }} enablerepo={{ item.enablerepo|d('') }} state=present
    with_items: default_packages + packages
```


Resolving patch:
diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py
--- a/lib/ansible/runner/__init__.py
+++ b/lib/ansible/runner/__init__.py
@@ -618,13 +618,13 @@ def _executor_internal(self, host, new_stdin):
             all_failed = False
             results = []
             for x in items:
-                # use a fresh inject for each item                
+                # use a fresh inject for each item
                 this_inject = inject.copy()
                 this_inject['item'] = x
 
                 # TODO: this idiom should be replaced with an up-conversion to a Jinja2 template evaluation
                 if isinstance(self.complex_args, basestring):
-                    complex_args = template.template(self.basedir, self.complex_args, inject, convert_bare=True)
+                    complex_args = template.template(self.basedir, self.complex_args, this_inject, convert_bare=True)
                     complex_args = utils.safe_eval(complex_args)
                     if type(complex_args) != dict:
                         raise errors.AnsibleError("args must be a dictionary, received %s" % complex_args)


Test patch: