using a dict as task parameters

Under Ansible 2.2.1 and python 2.7 I have a playbook for generically creating a docker container and then adding it as a host (using the docker connection plugin of course):

  • name: Start specified container
    gather_facts: no
    hosts: localhost
    connection: local
    vars:
    container_name: openshift_ansible_test_{{ scenario }}
    l_vars: >
    {{
    l_host_vars | default({}) | combine({
    “ansible_connection”: “docker”,
    “name”: container_name,
    “groups”: l_groups if l_groups else “OSEv3,masters,nodes”,
    })
    }}
    tasks:
  • debug: var=l_vars
  • name: start container
    docker_container:
    name: “{{ container_name }}”
    image: “{{ image }}”
    command: sleep 1800
    recreate: yes
    when: False
  • name: add host
    add_host: “{{ l_vars }}”

The idea is to be able to pass in l_host_vars with variable properties for add_host. l_vars gets populated as expected, so the debug looks like this:

ok: [localhost] => {
“l_vars”: {
“ansible_connection”: “docker”,
“deployment_type”: “origin”,
“groups”: “OSEv3,masters,nodes,etcd”,
“name”: “openshift_ansible_test_preflight_fail_all”
}
}

However the add_host task does not like it as parameters and there does not seem to be any variation that will get it to work:

creating host via ‘add_host’: hostname=None
ERROR! Unexpected Exception: unsupported operand type(s) for +: ‘NoneType’ and ‘str’
the full traceback was:

Traceback (most recent call last):
File “/bin/ansible-playbook”, line 103, in
exit_code = cli.run()
File “/usr/lib/python2.7/site-packages/ansible/cli/playbook.py”, line 159, in run
results = pbex.run()
File “/usr/lib/python2.7/site-packages/ansible/executor/playbook_executor.py”, line 154, in run
result = self._tqm.run(play=play)
File “/usr/lib/python2.7/site-packages/ansible/executor/task_queue_manager.py”, line 282, in run
play_return = strategy.run(iterator, play_context)
File “/usr/lib/python2.7/site-packages/ansible/plugins/strategy/linear.py”, line 278, in run
results += self._wait_on_pending_results(iterator)
File “/usr/lib/python2.7/site-packages/ansible/plugins/strategy/init.py”, line 577, in _wait_on_pending_results
results = self._process_pending_results(iterator)
File “/usr/lib/python2.7/site-packages/ansible/plugins/strategy/init.py”, line 478, in _process_pending_results
self._add_host(new_host_info, iterator)
File “/usr/lib/python2.7/site-packages/ansible/plugins/strategy/init.py”, line 598, in _add_host
self._inventory.get_host_vars(new_host)
File “/usr/lib/python2.7/site-packages/ansible/inventory/init.py”, line 771, in get_host_vars
return self._get_hostgroup_vars(host=host, group=None, new_pb_basedir=new_pb_basedir, return_results=return_result
s)
File “/usr/lib/python2.7/site-packages/ansible/inventory/init.py”, line 842, in _get_hostgroup_vars
elif group is None and any(map(lambda ext: host.name + ext in self._host_vars_files, C.YAML_FILENAME_EXTENSIONS)):
File “/usr/lib/python2.7/site-packages/ansible/inventory/init.py”, line 842, in
elif group is None and any(map(lambda ext: host.name + ext in self._host_vars_files, C.YAML_FILENAME_EXTENSIONS)):
TypeError: unsupported operand type(s) for +: ‘NoneType’ and ‘str’

Any idea how I can get this to do what I want? TIA

To elaborate a bit on my use case, I’m trying to run integration tests on playbooks - that is, test the playbooks in various scenarios. For each test, I want to spin up a docker container (or several) and treat them as the hosts against which the playbook is being tested. And then I’d like to delete the docker containers.

Aside from the problem of parameterizing the creation of a “host”, I’m wondering how to delete the containers if the test fails. There doesn’t seem to be any way to recover from a playbook include failing. It has to be a playbook include because it’s a playbook being tested, and I’d like to pass it the inventory that’s been created. But then there’s no way on a play include to add handlers or failed_when or register or any of the other goodies that are available for tasks. The only thing I could think of is to have something write out the inventory (how?) and feed it to a command task that runs the target playbook.

Or is this just a fundamental mismatch with the Ansible Way and I should be considering some other approach entirely?

You need to specify each of them, but you can still use the variable in the add_host.
So I would think that this should work:

   - name: add host
     add_host:
       ansible_connection: "{{ l_vars.l_host_vars.ansible_connection }}"
       name: "{{ l_vars.l_host_vars.name }}"
       groups: "{{ l_vars.l_host_vars.groups }}"

That would work, but is there a way that would allow me to pass in arbitrary variables instead of just a known list?

I don't think it's possible with add_host, but you can use set_fact

   - set_fact:
       "{{ item.name }}": "{{ item.value }}"
     delegate_facts: true
     delegate_to: testhost
     with_items:
       - name: k1
         value: v1
       - name: k2
         value: v2

This will add k1=v1 and k2=v2 to the host testhost.

Thanks, that seems to do what I want! I ended up with:

  • name: adjust host facts.
    set_fact:
    “{{ item.key }}”: “{{ item.value }}”
    delegate_facts: True
    delegate_to: “{{ container_name }}”
    with_dict: “{{ l_host_vars }}”

I should note that first I tried:

  • name: adjust host facts.
    set_fact: “{{ l_host_vars }}”
    delegate_facts: True

delegate_to: “{{ container_name }}”

Which seemed to work, but actually ended up setting the vars somewhere other than directly on the host (not sure what “_raw_params” is supposed to be), so behavior was not what I wanted. Setting the facts individually seems to be the only path that works.