Search code examples
ansibleyamlansible-inventory

Ansible: iterating and pairing inventory items with variables


This is directly related to the following link question, and the answer from larsks, that I tried, but it does not work: "how to loop through inventory and assign value in ansible"

I was trying to do the same, and I tested to rename 2 VMs controlled by Ansible, but I get errors like the next one when I try to run the Playbook (ansible-playbook -i hosts test_iterate_win.yml -vvv), I would say that it is literally taking by name 'System.Object[]' instead of, for example, wCloud2:

failed: [oldVM2] (item=[u'oldVM2', u'wCloud2']) => {
    "ansible_loop_var": "item", 
    "changed": false, 
    "item": [
        "oldVM2", 
        "wCloud2"
    ], 
    "msg": "Failed to rename computer to 'System.Object[]': Skip computer 'oldVM2' with new name 'System.Object[]' because the new name is not valid. The new computer name entered is not properly formatted. Standard names may contain letters (a-z, A-Z), numbers (0-9), and hyphens (-), but no spaces or periods (.). The name may not consist entirely of digits, and may not be longer than 63 characters.", 
    "old_name": "oldVM2", 
    "reboot_required": false
}

In my inventory file:

[windows]
oldVM1 ansible_host=192.168.122.6
oldVM2 ansible_host=192.168.122.139

My Playbook:

---
- hosts: windows
  gather_facts: false
  vars:
    hostnames:
      - wCloud1
      - wCloud2
  tasks:
    - name: change hostname
      win_hostname:
        name: "{{ item }}"
      loop: "{{ groups.windows|zip(hostnames)|list }}"

What I am doing wrong?


Solution

  • TL;DR;

    I would say, you are making this super complex for yourself for nothing, when there could be a simple solution about it.

    Your task could be easily resolved by just using host variables in your inventory:

    [windows]
    oldVM1 ansible_host=192.168.122.6 newName=wCloud1
    oldVM2 ansible_host=192.168.122.139 newName=wCloud2
    

    Then your playbook is as easy as:

    ---
    - hosts: windows
      gather_facts: false
    
      tasks:
        - name: change hostname
          win_hostname:
            name: "{{ newName }}"
    

    Now, the reason why your attempt didn't work, is actually coming from a bit of a misconception you have on task(s) run on multiple hosts by Ansible, I would say.

    Namely, when Ansible have a task (or a set of tasks) to run on multiple hosts, one and only one task need to be defined.

    Based on the said task, when the inventory hosts is actually a group of hosts, the task will be run on the host 1, then the host 2, ... until the host n, before it goes to run the next task (if any).

    Nota: still, don't take this for granted, there has been know issues where Ansible would not follow the order of definition of the hosts in the inventory (see: https://github.com/ansible/ansible/issues/34861), so really it could end up being host 2, host n, host 1.

    Consider this playbook with the above inventory:

    ---
    - hosts: windows
      gather_facts: false
    
      tasks:
        - name: change hostname
          debug:
            msg: '{{ newName }}'
    
        - name: another task
          debug:
            msg: 'some example'
    

    The output of it will be

    $ansible-playbook test.yml
    
    PLAY [windows] *****************************************************************
    
    TASK [change hostname] *********************************************************
    ok: [oldVM1] => {
        "msg": "wCloud1"
    }
    ok: [oldVM2] => {
        "msg": "wCloud2"
    }
    
    TASK [another task] ************************************************************
    ok: [oldVM1] => {
        "msg": "some example"
    }
    ok: [oldVM2] => {
        "msg": "some example"
    }
    
    PLAY RECAP *********************************************************************
    oldVM1                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
    oldVM2                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
    

    Where you can clearly see that the first task (named, change hostname) is processed on all hosts before Ansible could move forward to other tasks in the play.


    And really what your System.Object[] error means is that you are trying to feed an object (namely a list) in the win_hostname module's name, that tries to convert it as string somehow and fails badly, because, here is the list that your item variable contains:

    [
        "oldVM1",
        "wCloud1"
    ]