How to iterate through N level children of hosts u

2019-08-02 17:06发布

问题:

I know how to achieve this using host_vars but the problem with it is the host files can get convoluted so I'm leaning towards ini files where I can put all the data in one file. This SO post helped me have an idea how to put a collection in a variable for a particular host.

I have this sample inventory:

;hosts.yml
[web1]
example1.com databases=["example1_com","mysql"]
example2.com databases=["example1_com","mysql"]

[web1:vars]
ansible_host=10.0.16.21

[web2]
example3.com databases=["example3_com"]
example4.com databases=["example4_com","mysql"]

[web2:vars]
ansible_host=10.0.16.22

[web:children]
web1
web2

Now I wanted to loop through each hosts using the the web group and iterate through the databases host var.

I did something like this:

---
- debug:
    msg: "{{ item }} - {{ hostvars[item]['databases'] }} "
  with_items:
    - "{{ groups['web'] }}"

and the output is:

ok: [localhost] => (item=example1.com) => {
    "item": "example1.com", 
    "msg": "example1.com - [example1_com,mysql] "
}
ok: [localhost] => (item=example2.com) => {
    "item": "example2.com", 
    "msg": "example2.com - [example1_com,mysql] "
}
ok: [localhost] => (item=example3.com) => {
    "item": "example3.com", 
    "msg": "example3.com - [example3_com] "
}
ok: [localhost] => (item=example4.com) => {
    "item": "example4.com", 
    "msg": "example4.com - [example4_com,mysql] "
}

I tried achieving this using with_sublements loop but the problem is the 2nd element needs to be dynamic which is not possible with_subelements.

with_subelements:
    - "{{ groups['web'] }}"
    - {{ hostvars[item]['databases'] }} #item is dynamic, this will cause an undefined host error.

回答1:

It's not 100% clear to me what your original approach was, and if the code in your question was meant to represent your new approach (since you are still referencing hostvars there). I think you need to work more with specifying the groups you want affected within the playbook (hosts: web) or on the command line (-l web) when running the playbook to run the tasks only for those hosts you want, rather than trying to get the group dynamically within the task itself.

Regarding the linked question/answer, where a way of defining a list within a variable was discussed: you need to make sure to enclose the list data within single quotes, e.g. '["example1_com","mysql"]'.

Given that, if you simply want to iterate over a list from a host variable defined in an inventory file, you can do the following:

Inventory File "inv"

[web1]
example1.com databases='["example1_com","mysql"]'
example2.com databases='["example1_com","mysql"]'

[web1:vars]
ansible_host=10.0.16.21

[web2]
example3.com databases='["example3_com"]'
example4.com databases='["example4_com","mysql"]'

[web2:vars]
ansible_host=10.0.16.22

[web:children]
web1
web2

Playbook File "test.yml"

---
- hosts: web
  gather_facts: no
  tasks:
    - debug: msg="Host is {{ inventory_hostname }}. Database is {{ item }}"
      with_items:
        - "{{ databases }}"

You can then run the playbook:

ansible-playbook test.yml -i inv

generating the following output:

PLAY ***************************************************************************

TASK [debug] *******************************************************************
ok: [example3.com] => (item=example3_com) => {
    "item": "example3_com", 
    "msg": "Host is example3.com. Database is example3_com"
}
ok: [example1.com] => (item=example1_com) => {
    "item": "example1_com", 
    "msg": "Host is example1.com. Database is example1_com"
}
ok: [example1.com] => (item=mysql) => {
    "item": "mysql", 
    "msg": "Host is example1.com. Database is mysql"
}
ok: [example2.com] => (item=example1_com) => {
    "item": "example1_com", 
    "msg": "Host is example2.com. Database is example1_com"
}
ok: [example2.com] => (item=mysql) => {
    "item": "mysql", 
    "msg": "Host is example2.com. Database is mysql"
}
ok: [example4.com] => (item=example4_com) => {
    "item": "example4_com", 
    "msg": "Host is example4.com. Database is example4_com"
}
ok: [example4.com] => (item=mysql) => {
    "item": "mysql", 
    "msg": "Host is example4.com. Database is mysql"
}

PLAY RECAP *********************************************************************
example1.com               : ok=1    changed=0    unreachable=0    failed=0   
example2.com               : ok=1    changed=0    unreachable=0    failed=0   
example3.com               : ok=1    changed=0    unreachable=0    failed=0   
example4.com               : ok=1    changed=0    unreachable=0    failed=0   

If you properly structure your playbook, you can also set it up to run different sets of tasks for different host groups (perhaps including the tasks from an external file so you DRY). Or you could simply specify hosts: all in the playbook, and use command line limiting to only run the tasks against a specific set of hosts.