I have a playbook, there are multiple yaml files under include_vars section of the task as you see i am defining them individually one by one. I'm wondering if i can pass them through the loop.
I went through the google and ansible doc link here but i don't find loop example however, i see with_first_found but i don't want that.
My playbook below
---
- name: Creating aws host
hosts: localhost
connection: local
become: yes
tasks:
- include_vars: awsvariable.yml
no_log: true
- include_vars: vaults/mysecrets.yml
no_log: true
- include_vars: vaults/mydev.yml
no_log: true
- include_vars: requirements.yml
Below is what i am considering
Is this doable as below?
tasks:
- include_vars: "{{ item }}"
loop:
- awsvariable.yml
- vaults/mysecrets.yml
- vaults/mydev.yml
- requirements.yml
OR
tasks:
- include_vars: ['awsvariable.yml', 'vaults/mysecrets.yml', 'vaults/mydev.yml', 'requirements.yml']
no_log: true
Please suggest, if we can better align them some other way around.
ansible version: 2.9
BR.
I see you are already close to your answer and yes loop should work, try to run your play in the dry run mode and set the no_log: false to see if your variables are getting expanded.
Example:
$ ansible-playbook test_play.yml --check --ask-vault-pass
your code
- include_vars: "{{ item }}"
loop:
- 'awsvariable.yml'
- 'vaults/mysecrets.yml'
- 'vaults/mydev.yml'
- 'requirements.yml'
no_log: true
Use dir to include all the files in a folder.
- name: Include variable files
include_vars:
dir: vars
extensions:
- "yml"
Alternatively can use both loop or with_items to loop through the filenames and include them.
- name: Include variable files
include_vars: "{{ item }}"
with_items:
- "vars/file.yml"
- "vars/anotherfile.yml"
Or using the newer loop.
- name: Include variable files
include_vars: "{{ item }}"
loop:
- "vars/file.yml"
- "vars/anotherfile.yml"
Seems the include_vars module can't work with loops. You could use the include_task module to loop over a file that has a single task to include_vars:
---
- hosts: localhost
gather_facts: false
vars:
files_to_load:
- file1.yml
- file2.yml
- file3.yml
tasks:
- include_tasks: include_vars.yml
loop: "{{ files_to_load }}"
loop_control:
loop_var: file_to_load
ant the include_vars.yml file:
- name: include vars {{ file_to_load }}
include_vars: "{{ file_to_load }}"
UPDATE:
Regarding loop syntax that i tried and didnt work, the below attempts failed:
1st:
- include_vars: "{{ item }}"
loop: - "{{ files_to_load }}"
2nd:
- include_vars: "{{ item }}"
loop:
- "{{ files_to_load }}"
i am aware that this syntax works:
- name: Include variable files
include_vars: "{{ item }}"
loop:
- "vars/file.yml"
- "vars/anotherfile.yml"
but personally i dont find convenient the fact that you need to list the loop items on task level.
Related
I have an ansible playbook that interacts with the management card in a bunch of servers, and then produces a report based on that information. Structurally it looks like:
- hosts: all
tasks:
- name: do something with redfish
uri:
...
register: something
- hosts: localhost
tasks:
- name: produce report
template:
...
loop: "{{ SOME_LIST_OF_HOSTS }}"
Originally, the template task in the second was looping over groups.all, but that causes a number of complications if we limited the target hosts using -l on the command line (like ansible-playbook -l only_cluster_a ...). In that case, I would like the template task to loop over only the hosts targeted by the first play. In other words, I want to know ansible_play_hosts_all from the previous play.
This is what I've come up with:
- hosts: all
gather_facts: false
tasks:
- delegate_to: localhost
delegate_facts: true
run_once: true
set_fact:
saved_play_hosts: "{{ ansible_play_hosts_all }}"
...other tasks go here...
- hosts: localhost
gather_facts: false
tasks:
- debug:
msg:
play_hosts: "{{ saved_play_hosts }}"
Is that the best way of doing this?
you could use add_host module: at the end of first play you add a task:
- name: add variables to dummy host
add_host:
name: "variable_holder"
shared_variable: "{{ saved_play_hosts }}"
and you could trap the value in second play:
- name: second play
hosts: localhost
vars:
play_hosts: "{{ hostvars['variable_holder']['shared_variable'] }}"
tasks:
:
:
I am very confused, in the play book where actually we put single hyphen (-). I found similar threads here, but still confused, so decided to draft one new. I have read it will be used to indicate start of a list item. again i have difficulty in understanding where is the start of list and where is start of dictionary.
Can some experts explain me where should i put hyphen in below code. and why is that?
---
connection: local
gather_facts: false
hosts: rtr
tasks:
name: "read configs"
read_csv:
path: "{{ aws_config }}"
register: aws_requests
run_once: true
debug:
msg: "{{ aws_requests.list }}"
name: "display awsconfigs requests"
run_once: true
name: "set awsconfigs requests"
run_once: true
set_fact:
aws_configs: "{{ aws_requests.list }}"
name: "build template"
template:
dest: "{{ config_filename }}"
lstrip_blocks: true
src: "{{ template }}"
I recommend that you read the "Intro to playbooks", which should answer your questions, but below is a summary.
As you noted correctly, hyphens are list items in YAML. YAML documents start with ---, which is why there are hyphens at the start of the file.
The starting point for any Ansible playbook is the playbook itself in the file. The playbook file itself may contain one or more so-called "plays", each as its own list element. Each play typically contains a hosts and a tasks part. In many playbooks, there is just one "play", so your typical minimal playbook looks like this:
---
- hosts: webservers
tasks:
- name: Task 1
...
As you can see above, each "play" then has a list of tasks, each starting with a hyphen. So in the following example there are two tasks, each with a name and the module (yum and service in this case):
---
- hosts: webservers
tasks:
- name: ensure apache is at the latest version
yum:
name: httpd
state: latest
- name: ensure apache is running
service:
name: httpd
state: started
Each Ansible module has different arguments, so you'll need to check the modules documentation for each one how to specify these arguments.
So the correct version for your playbook above would look like this:
---
- connection: local
gather_facts: false
hosts: rtr
tasks:
- name: "read configs"
read_csv:
path: "{{ aws_config }}"
register: aws_requests
run_once: true
- name: "display awsconfigs requests"
debug:
msg: "{{ aws_requests.list }}"
run_once: true
- name: "set awsconfigs requests"
run_once: true
set_fact:
aws_configs: "{{ aws_requests.list }}"
- name: "build template"
template:
dest: "{{ config_filename }}"
lstrip_blocks: true
src: "{{ template }}"
I have a code to backup config using the ios_config module. I used ios_facts to get the hostname of devices and I want to use it to put the backup file in a similarly named folder and also use it in the file name itself.
In the last task of my code, I need to loop through two items - the sequence from 0 to 1(or how many items are in my inventory) as I need to access the hostname in the results and use it in the backup options, and also loop through my inventory of devices which I extracted from a csv file. I am aware of the rule of double curly braces but I do not know how to get around it.
---
- hosts: localhost
gather_facts: false
tasks:
- name: Block
block:
- name: Use CSV
csv_to_facts:
src: '{{playbook_dir}}/NEW/Inventory.csv'
vsheets:
- INFO:
- IP
- OS
- debug:
msg: '{{item.IP}}'
loop: '{{INFO}}'
- name: Create Inventory
add_host:
hostname: '{{item.IP}}'
ansible_network_os: '{{item.OS}}'
ansible_user: cisco
ansible_ssh_pass: cisco
ansible_connection: network_cli
ansible_become: yes
ansible_become_method: enable
groups: group_01
loop: '{{INFO}}'
- name: Gather Facts (IOS)
ios_facts:
register: ios_facts_loop
delegate_to: '{{item}}'
loop: "{{groups['group_01']}}"
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ ios_facts_loop.results.{{item[0]}}.ansible_facts.ansible_net_hostname }}"
filename: "{{ios_facts_loop.results.item{{[0]}}.ansible_facts.ansible_net_hostname}} {{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S')}}"
register: backup_ios_location
delegate_to: '{{item[1]}}'
loop:
- with_sequence: "0-{{output|length - 3}}"
- "{{groups['group_01']}}"
TLDR; for vars notation
You cannot add double curly braces inside double curly braces like in your above code. You current var reference:
ios_facts_loop.results.{{item[0]}}.ansible_facts.ansible_net_hostname
should be turned to
ios_facts_loop.results[item[0]].ansible_facts.ansible_net_hostname
# or equivalent
ios_facts_loop.results[item.0].ansible_facts.ansible_net_hostname
Meanwhile, this will only fix your current syntax error (that you didn't share in your question) as the first element in your loop is a string 'with_sequence: "0-X"' which therefore has no index 0.
Attempt to fix the logic
If I understand correctly, for your last task, you just need to loop over the results of your ios_facts register and delegate the task to the server it was taken from. Luckilly, you should already have all the info you need in ios_facts_loop.results
It is a list so you can directly loop over it
Each element should contain an item key with the actual item that was used in the previous run at time of register (i.e. one of your groups['group_01'] element).
So I would try to write your last task like this. Disclaimer this is a pure guess as I didn't see your exact datastructure.
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ item.ansible_facts.ansible_net_hostname }}"
filename: "{{ item.ansible_facts.ansible_net_hostname}}{{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S')}}"
register: backup_ios_location
delegate_to: '{{item.item}}'
loop: "{{ ios_facts_loop.results }}"
Going further.
I'm not really familiar with the ios_* modules but they should be really close to other stuff I use daily and I think you could really simplify your playbook taking advantage of more ansible feature (e.g. multiple plays in a playbook). I believe the following should actually do the job:
---
- name: Construct inventory from CSV
hosts: localhost
gather_facts: false
tasks:
- name: Use CSV
csv_to_facts:
src: '{{playbook_dir}}/NEW/Inventory.csv'
vsheets:
- INFO:
- IP
- OS
- name: Create Inventory
add_host:
hostname: '{{item.IP}}'
ansible_network_os: '{{item.OS}}'
ansible_user: cisco
ansible_ssh_pass: cisco
ansible_connection: network_cli
ansible_become: yes
ansible_become_method: enable
groups: group_01
loop: '{{INFO}}'
- name: Backup switches from created inventory
hosts: group_01
gather_facts: false
tasks:
- name: Get facts from network os
ios_facts:
gather_subset: all
- name: Backup Switch (IOS)
ios_config:
backup: yes
backup_options:
dir_path: "tmp/backups/{{ ansible_net_hostname }}"
filename: "{{ ansible_net_hostname }}{{ lookup('pipe','date +%Y-%m-%d#%H:%M:%S') }}"
More background on dot and brackets notation for vars
You can basically navigate a yaml datastructure with two notation which are equivalent.
the dot notation
a_list_var.index_number
a_hasmap_var.keyname
the brackets notation
a_list_var[index_number]
a_hashmap_var['key_name']
If we take the following example:
my_servers:
hostA:
ips:
- x.x.x.x
- y.y.y.y
env:
shell: bash
home: somewhere
hostB:
ips:
- a.a.a.a
- b.b.b.b
env:
shell: sh
home: elsewhere
The following notation are all strictly equivalent:
# all vars of hostA
hostA_vars: "{{ my_servers.hostA }}"
hostA_vars: "{{ my_server['hostA'] }}"
# first IP of hostB
hostB_ip: "{{ my_servers.hostB.0 }}"
hostB_ip: "{{ my_servers.hostB[0] }}"
hostB_ip: "{{ my_servers['hostB'].0 }}"
hostB_ip: "{{ my_servers['hostB'][0] }}"
As you can see, the dot notation tends to be less verbose and more readable. Meanwhile, you cannot use a variable identifier with the dot notation. So If you want to ave the home env of a variable server you would have to use:
# set a var for server
server: hostA
# all equivalent again
server_home: "{{ my_servers[server].env.home }}"
server_home: "{{ my_servers[server]['env'].home }}"
server_home: "{{ my_servers[server].env['home'] }}"
server_home: "{{ my_servers[server]['env']['home'] }}"
I am currently taking two hosts and dynamically adding them to a group, followed by a synchronize task using with_together to use 3 lists of 2 elements in parallel to copy the specified files between two remote servers.
Here's an example based on the idea:
---
- name: Configure Hosts for Copying
hosts: localhost
gather_facts: no
tasks:
- name: Adding given hosts to new group...
add_host:
name: "{{ item }}"
groups: copy_group
with_items:
- ["remoteDest1", "remoteDest2"]
- name: Copy Files between servers
hosts: copy_group
gather_facts: no
tasks:
- name: Copying files...
synchronize:
src: "{{ item[1] }}"
dest: "{{ item[2] }}"
with_together:
- ["remoteSrc1", "remoteSrc2"]
- ["/tmp/remote/source/one/", "/tmp/remote/source/two/"]
- ["/tmp/remote/dest/one/", "/tmp/remote/dest/two/"]
delegate_to: "{{ item[0] }}"
Currently, it does both operations for both servers, resulting in 4 operations.
I need it to synchronize like so:
copy /tmp/remote/source/one/ from remoteSrc1 to /tmp/remote/dest/one/ on remoteDest1
copy /tmp/remote/source/two/ from remoteSrc2 to /tmp/remote/dest/two/ on remoteDest2
Which would mean it's a 1:1 ratio; essentially acting on the hosts in the same manner as with_together does for the lists.
The hosts are obtained dynamically, so I can't just make a different play for each host.
Since synchronize is essentially simplified version of rsync, then if there's a simple solution for this using rsync directly, then it would be much appreciated.
There isn't native functionality for this, so this is how I solved it:
Given the original task, add the following two lines:
- "{{ groups['copy_group'] }}"
when: inventory_hostname == item[3]
To get:
- name: Copying files...
synchronize:
src: "{{ item[1] }}"
dest: "{{ item[2] }}"
with_together:
- ["remoteSrc1", "remoteSrc2"]
- ["/tmp/remote/source/one/", "/tmp/remote/source/two/"]
- ["/tmp/remote/dest/one/", "/tmp/remote/dest/two/"]
- "{{ groups['copy_group'] }}"
delegate_to: "{{ item[0] }}"
when: inventory_hostname == item[3]
Essentially, by adding the hosts as a list, they can be used in the when statement to execute the task only when the current host (inventory_hostname) matches the host currently being indexed in the list.
The result is that the play only runs against each host once in a serial manner with the other list items that have the same index.
I have a need to know the index of host names in the inventory. I am using the below code to create a variable file that I can use in a subsequent play book
- name: Debug me
hosts: hosts
tasks:
- debug: msg="{{ inventory_hostname }}"
- debug: msg="{{ play_hosts.index(inventory_hostname) }}"
- local_action: 'lineinfile create=yes dest=/tmp/test.conf
line="host{{ play_hosts.index(inventory_hostname) }}=
{{ inventory_hostname }}"'
I have the following inventory file
[hosts]
my.host1.com
my.host2.com
Now when I run this, the test.conf that gets generated under /tmp sometimes has both hostnames like this
host1= my.host2.com
host0= my.host1.com
when I run the same playbook a few times each time emptying the test.conf before running. quite a few times the file only has one entry
host1= my.host2.com
or
host0= my.host1.com
how come the same ansible playbook behaving differently?
I believe the issue is your running two threads against different hosts, and using local_action is not thread safe.
Try using the serial keyword:
- name: Debug me
hosts: hosts
serial: 1
tasks:
- debug: msg="{{ inventory_hostname }}"
- debug: msg="{{ play_hosts.index(inventory_hostname) }}"
- local_action: 'lineinfile create=yes dest=/tmp/test.conf
line="host{{ play_hosts.index(inventory_hostname) }}=
{{ inventory_hostname }}"'
Edit: A better way to do this if just trying to operate on the list of host in inventory on the localhost, would be to avoid doing the action on the host and using local_action in the first place.
- name: Debug me
hosts: localhost
tasks:
- lineinfile:
create: yes
dest: /tmp/test.conf
line: "host{{ groups['hosts'].index(item)}}={{ item }}"
with_items: " {{ groups['hosts'] }}"
This will get you the results you desire. Then you can add another play to do operations against the hosts themselves.
The solution I am trying to avoid problems with race conditions with non-thread safe Local_action: lineinfile to write gathered data to local file. Split it across 2 different plays in the same file.
eg:
- name: gather_date
hosts: all
any_errors_fatal: false
gather_facts: no
tasks:
- name: get_Aptus_device_count_list
shell: gather_data.sh
become: true
register: Aptus_device_count_list
changed_when: false
- name: Log_gathered_date
hosts: all
any_errors_fatal: false
gather_facts: no
tasks:
- name: log_gathered_info
local_action:
module: lineinfile
dest: /home/rms-mit/MyAnsible/record_Device_count_collection.out
line: "\n--- {{ inventory_hostname }} --- \n
{{ Aptus_device_count_list.stdout }} \n.\n---\n"
changed_when: false