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.
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.
Related
I want to improve my ansible role because i have a lot of users to roll out.
For each user that is created there will be also multiple folders
created and this is very time consuming.
This is my users.yml file where i put every single user in (>1000)
ftp_users:
testuser1:
public_key: "public key"
password: "sha string"
home: /home/testuser1
customer_type: linux
testuser2:
public_key: "public key"
password: "sha string"
home: /home/testuser2
customer_type: windows
For this 2 users i want to create two folders "in" and "out".
Therefore i've created two tasks where i iterating over the dictionary:
- name: Create required out-folder for jailed users.
become: true
ansible.builtin.file:
owner: "{{ item.key }}"
group: ftpusers
mode: 0770
path: "/home/{{ item.value.customer_type }}/{{ item.key }}/out"
state: directory
loop: "{{ ftp_users | dict2items }}"
when: "'state' not in item.value or item.value.state == 'present'"
- name: Create required in-folder for jailed users.
become: true
ansible.builtin.file:
owner: "{{ item.key }}"
group: ftpusers
mode: 0770
path: "/home/{{ item.value.customer_type }}/{{ item.key }}/in"
state: directory
loop: "{{ ftp_users | dict2items }}"
when: "'state' not in item.value or item.value.state == 'present'"
This is very stupid because it takes a lot of time when 1000 users are rolled out.
I want to make one tasks to simultaniously create the "in" and "out" folder for every user, that i dont have to iterate two times over the whole dictionary.
What would be better nested_loops or the product filter?
Can someone show me an example?
Unfortunately block doesnt accept loop, so you could use include_tasks:
- name: "tips4"
hosts: localhost
gather_facts: false
vars:
ftp_users:
testuser1:
public_key: "public key"
password: "sha string"
home: /home/testuser1
customer_type: linux
testuser2:
public_key: "public key"
password: "sha string"
home: /home/testuser2
customer_type: windows
tasks:
- name: Create required out-folder for jailed users
include_tasks: create_folders.yml
loop: "{{ ftp_users | dict2items }}"
when: "'state' not in item.value or item.value.state == 'present'"
Create another file create_folders.yml in same folder than your playbook
# create_folders.yml
---
- name: Create required out-folder for jailed users
debug:
msg: "owner: {{ item.key }}, path: /home/{{ item.value.customer_type }}/{{ item.key }}/out"
- name: Create required in-folder for jailed users
debug:
msg: "owner: {{ item.key }}, path: /home/{{ item.value.customer_type }}/{{ item.key }}/in"
result:
TASK [Create required out-folder for jailed users]
ok: [localhost] => {
"msg": "owner: testuser1, path: /home/linux/testuser1/out"
}
TASK [Create required in-folder for jailed users]
ok: [localhost] => {
"msg": "owner: testuser1, path: /home/linux/testuser1/in"
}
TASK [Create required out-folder for jailed users]
ok: [localhost] => {
"msg": "owner: testuser2, path: /home/windows/testuser2/out"
}
TASK [Create required in-folder for jailed users]
ok: [localhost] => {
"msg": "owner: testuser2, path: /home/windows/testuser2/in"
}
with this playbook, in and out folder are created in same loop, so you just iterate one time...
How to iterate module output from a loop in ansible and capture particular value to be redirected to a file. Example: 'amazon-ssm-agent.service']['state']": "running" should be pushed to a file locally.
[ansibleadm#node1 ~]$ cat myloops3.yaml
---
- name: collect service status remotely
hosts: remote
become: yes
roles:
- role: myServices
myServiceName:
- amazon-ssm-agent.service
- cloud-init-local.service
[ansibleadm#node1 ~]$ cat roles/myServices/tasks/main.yml
---
# tasks file for myServices
- name: collect systemd info
service_facts:
- name: cross verify service is runnng or not
debug:
var: ansible_facts.services['{{ item }}']['state']
loop: "{{ myServiceName }}"
[ansibleadm#node1 ~]$
## Outputs ##
TASK [myServices : cross verify service is runnng or not]
*****************************************************************
ok: [3.109.201.79] => (item=amazon-ssm-agent.service) => {
"ansible_facts.services['amazon-ssm-agent.service']['state']": "running",
"ansible_loop_var": "item",
"item": "amazon-ssm-agent.service"
}
ok: [3.109.201.79] => (item=cloud-init-local.service) => {
"ansible_facts.services['cloud-init-local.service']['state']": "stopped",
"ansible_loop_var": "item",
"item": "cloud-init-local.service"
}
Say you want to output those services status into a file, you may use something like this:
- name: collect systemd info
service_facts:
- name: cross verify service is runnng or not
copy:
content: |
{% for s in myServiceName %}{{ s }}={{ ansible_facts.services[s]['state'] }}
{% endfor %}
dest: /tmp/test.txt
Would give you:
$> cat /tmp/test.txt
amazon-ssm-agent.service=running
cloud-init-local.service=running
Or, if you want one file per service:
- name: cross verify service is runnng or not
loop: "{{ myServiceName }}"
copy:
content: |
{{ ansible_facts.services[item]['state'] }}
dest: "/tmp/{{ item }}.txt"
Which gives:
$> cat /tmp/amazon-ssm-agent.service.txt
running
If you need the quotation and parenthesis from the example (I balanced the beginning)
Example: "['amazon-ssm-agent.service']['state']": "running"
the Jinja below should create it. For example, given myServiceName: [ssh, xen]
- service_facts:
- copy:
content: |
{% for s in myServiceName %}
"['{{ s }}']['state']": "{{ ansible_facts.services[s]['state'] }}"
{% endfor %}
dest: /tmp/test.txt
creates the file
shell> cat /tmp/test.txt
"['ssh']['state']": "running"
"['xen']['state']": "running"
I want to iterate over array and pass each array element value to role from playbook but it is not working in ansible, Can some one help
---
#play book
- name: create config for instance
hosts: all
vars:
LIST: [Asia, Americas, Artic, Antartic ,Oceania,Europe,Africa]
connection: local
roles:
- role: create_config
debug:
msg : "{{ item }}"
vars:
VENUE: "{{ item }}"
with_items:
- "{{ LIST }}"
## Role
- name: create directory structure
file:
path: "{{item}}"
state: directory
mode: 0755
with_items:
- "{{dest_folder}}/{{instance_name}}/{{VENUE}}"
I am getting below error
ansible-playbook -i inventory/AlgoTest_SP create_pkg_1.yml
PLAY [create config for instance] **************************************************************************************************************************************
TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [localhost]
TASK [create_config : create directory structure] *******************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "{{ item }}: 'item' is undefined"}
PLAY RECAP ****************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
To be able to loop over roles, you need the include_role task, as in:
- name: create config for instance
hosts: localhost
vars:
LIST: [Asia, Americas, Artic, Antartic ,Oceania,Europe,Africa]
tasks:
- include_role:
name: create_config
vars:
VENUE: "{{ item }}"
with_items:
- "{{LIST}}"
cat roles/create_config/tasks/main.yml
- debug:
msg: "{{VENUE}}"
- name: create directory structure
file:
path: "{{item}}"
state: directory
mode: 0755
with_items:
- "{{inventory_hostname}}/{{VENUE}}"
Resulting in:
$ ansible-playbook 69045121.yml
PLAY [create config for instance] ********************************************************************************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [include_role : create_config] ******************************************************************************************************************************************************************************************************************************************************************************************
TASK [create_config : debug] *************************************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Asia"
}
TASK [create_config : create directory structure] ****************************************************************************************************************************************************************************************************************************************************************************
changed: [localhost] => (item=localhost/Asia)
TASK [create_config : debug] *************************************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Americas"
}
[...] and so on
As a final note, you could, and should, likely handle this differently. Also, you have clash in the item variable name from the outer (playbook) and inner (role) with_items, you can use loop_var to set a different looping varname.
I can get information for Individual Package Version like this
- name: Print zsh Version
debug:
msg: "{{ ansible_facts.packages['zsh'][0].version }}"
when: " 'zsh' in ansible_facts.packages"
I am trying to use a loop for a list, but I am unable to quote the {{item}}.
software: ['ksh','zsh','bash']
- name: Print Softwre Versions
debug:
msg: "{{ ansible_facts.packages['{{item}}'][0].version }}"
with_items: "{{ software }}"
I get the following error message
"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute '{{item}}'
How do I make this work ?
You don't need to quote it or put it in curly bracers, you are already in curly bracers:
- name: Print software versions
debug:
msg: "{{ ansible_facts.packages[item][0].version }}"
vars:
software:
- 'ksh'
- 'zsh'
- 'bash'
loop: "{{ software }}"
Fully working playbook:
- hosts: localhost
gather_facts: no
tasks:
- name: Gather package facts
package_facts:
manager: auto
- name: Print software versions
debug:
msg: "{{ ansible_facts.packages[item][0].version }}"
vars:
software:
- 'ksh'
- 'zsh'
- 'bash'
loop: "{{ software }}"
Gives this recap:
PLAY [localhost] ***************************************************************
TASK [Gather package facts] ****************************************************
ok: [localhost]
TASK [Print software versions] *************************************************
ok: [localhost] => (item=ksh) => {
"msg": "2020.0.0-5"
}
ok: [localhost] => (item=zsh) => {
"msg": "5.8-3ubuntu1"
}
ok: [localhost] => (item=bash) => {
"msg": "5.0-6ubuntu1"
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
PS: try not to mix YAML and JSON notation, your software array is in JSON, while the rest of your playbook is in YAML.
I have hosts file like below
[test1]
10.33.11.198
10.33.11.185
i am using a template like below
{% for i in groups['test1'] %}
IP{{ i }}={{ hostvars[groups['test1'][i]]['ansible_default_ipv4']['address'] }}
{% endfor %}
my expectation is
IP0=10.33.11.198
IP1=10.33.11.185
but, i am getting below error.
fatal: [10.33.11.198]: FAILED! => {"changed": false, "msg": "AnsibleUndefinedVariable: 'list object' has no attribute u'10.33.11.198'"}
fatal: [10.33.11.185]: FAILED! => {"changed": false, "msg": "AnsibleUndefinedVariable: 'list object' has no attribute u'10.33.11.198'"}
Any help would be appreciated.
Your problem is that i is not an index, but an element of the list.
Try
{% for i in groups['test1'] %}
IP{{ loop.index0 }}={{ hostvars[i]['ansible_default_ipv4']['address'] }}
{% endfor %}
Check Jinja2 for statement
Trying a minimal example:
hosts:
[test1]
10.33.11.198
10.33.11.185
and x.yml (replaced your ['ansible_default_ipv4']['address'] with just inventory_hostname)
- hosts: localhost
tasks:
- debug: msg="{% for i in groups['test1'] %}\nIP{{ loop.index0 }}={{ hostvars[i].inventory_hostname }}\n{% endfor %}"
running:
$ ansible-playbook -i hosts x.yml
PLAY [localhost] ***************************************************************************************
TASK [Gathering Facts] *********************************************************************************
ok: [localhost]
TASK [debug] *******************************************************************************************
ok: [localhost] => {
"msg": "IP0=10.33.11.198\nIP1=10.33.11.185\n"
}
PLAY RECAP *********************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0