Ansible: How to iterate over a role with an array? - ansible

Is it possible to call a role multiple times in a loop like this:
vars:
my_array:
- foo
- bar
- baz
roles:
- role: foobar
with_items: my_array
How can we do this?

Now supported as of Ansible 2.3.0:
- name: myrole
with_items:
- "aone"
- "atwo"
include_role:
name: myrole
vars:
thing: "{{ item }}"

There's no way to loop over a role currently but as mentioned in that Google Group discussion you can pass a list or dict to the role and then loop through that internally.
So instead you could do something like:
# loop_role/tasks/main.yml
- name: debug item
debug: var="{{ item }}"
with_items: my_array
And then use it like this:
- hosts: all
vars:
my_array:
- foo
- bar
- baz
roles:
- { role: loop_role, my_array: "{{ my_array }}" }

I used something like below on Ansible version 2.8
tasks:
- name: looping role to create multiple filesystem
include_role:
name: /opt/ansible/playbook/app_filesystem
vars:
vgname: "{{ item.vgname }}"
lvname: "{{ item.lvname }}"
lvsize: "{{ item.lvsize }}"
mountpoint: "{{ item.mountpoint }}"
loop:
- { vgname: 'vgapp', lvname: 'lvapp', lvsize: '30g', mountpoint: '/app' }
- { vgname: 'vgapp', lvname: 'lvappzk', lvsize: '64g', mountpoint: '/app/z' }
- { vgname: 'vgapp', lvname: 'lvappdatazk', lvsize: '+100%FREE', mountpoint: '/app/data/zookeeper' }
tasks:
- name: looping role to create multiple filesystem
include_role:
name: /opt/ansible/playbook/app_filesystem
vars:
vgname: "{{ item.vgname }}"
lvname: "{{ item.lvname }}"
lvsize: "{{ item.lvsize }}"
mountpoint: "{{ item.mountpoint }}"
loop:
- { vgname: 'vgapp', lvname: 'lvapp', lvsize: '30g', mountpoint: '/app' }
- { vgname: 'vgapp', lvname: 'lvappzk', lvsize: '64g', mountpoint: '/app/zookeeper' }
- { vgname: 'vgapp', lvname: 'lvappdatazk', lvsize: '+100%FREE', mountpoint: '/app/data/zookeeper' }

You can do so using the include_role module. See docs
According to the docs it was introduced in Ansible 2.2 already (not in 2.3 as others have stated).
The code would then look like
- name: Use role in loop
ansible.builtin.include_role:
name: my-role
vars:
some_role_variable: '{{ loop_var }}'
loop:
- '{{ roleinput1 }}'
- '{{ roleinput2 }}'
loop_control:
loop_var: loop_var

Here is a code sample for using include_role looping on my_array:
- name: Use role in loop
include_role:
name: myrole
loop: "{{ my_array }}"

Related

Iterate over dict got from another dict

This is my Ansible task:
- name: Create all daemon supervisor files
ansible.builtin.template:
src: supervisor_daemon_template.j2
dest: /tmp/asd/{{item.brand}}_{{ item.daemon_name }}.conf
# this works
loop: "{{ app_daemons.app1 }}"
# But I want something like that:
# loop: "{{ lookup('ansible.builtin.vars', app_daemons, inventory_hostname ) }}"
vars:
app_daemons:
app1:
- { daemon_name: 'receiver', brand: 'nokia' }
- { daemon_name: 'parser', brand: 'nokia' }
app2:
- { daemon_name: 'receiver', brand: 'samsung' }
- { daemon_name: 'parser', brand: 'samsung' }
I want to create the files accordingly to the current {{ inventory_hostname }} that can be either app1 or app2.
How can I do this?

How to access vars outside item when using with_items?

I have a role which uses with_items:
- name: Create backup config files
template:
src: "config.yml.j2"
dest: "/tmp/{{ project }}_{{ env }}_{{ item.type }}.yml"
with_items:
- "{{ backups }}"
I can access the item.type, as usual, but not project or env which are defined outside the collection:
deploy/main.yml
- hosts: ...
vars:
project: ...
rails_env: qa
roles:
- role: ../../../roles/deploy/dolly
project: "{{ project }}"
env: "{{ rails_env }}"
backups:
- type: mysql
username: ...
password: ...
The error I get is:
Error was a <class 'ansible.errors.AnsibleError'>, original message: An unhandled exception occurred while templating '{{ project }}'
The template, config.j2.yml, is:
type: {{ item.type }}
project: {{ project }}
env: {{ env }}
database:
username: {{ item.username }}
password: {{ item.password }}
It turns out for can't redefine a var with the same name as an existing var, so project: {{ project }} will always fail with an error.
Instead project can be omitted and the existing definition, in vars, can be used.
- hosts: ...
vars:
project: ... # <- already defined here
roles:
- role: ../../../roles/deploy/dolly
backups:
- type: mysql
username: ...
password: ...
If the var is not defined in vars can be defined in the role:
- hosts: ...
vars:
name: ...
roles:
- role: ../../../roles/deploy/dolly
project: "{{ name }}" # <- define here
backups:
- type: mysql
username: ...
password: ...

Creating symlinks with ansible using variables

I'm doing a playbook to create symlinks of nodejs, npm and gulp because I need to use a specific version and to install it all I'm just unzipping folders to /opt/ where all this is going to stay.
The task with items I'm using for creating the links are:
- name: Create NPM symlink
file:
src: '{{ item.src_dir }}/{{ item.src_name }}'
dest: '{{ item.dest_dir }}/{{ item.dest_name }}'
owner: "{{ ansible_ssh_user }}"
group: "{{ ansible_ssh_user }}"
state: link
with_items:
- { src_dir: "{{ npm_real_dir }}", src_name: "{{ npm_real_name }}" }
- { dest_dir: "{{ nodenpm_link_dir }}", dest_name: "{{ npm_link_name }}" }
all the variables used in the items "zone" are declared in the host file as such:
npm_real_dir=/opt/nodejs/node-v6.11.2-linux-x64/lib/node_modules/npm/bin
npm_real_name=npm-cli.js
nodenpm_link_dir=/opt/nodejs/node-v6.11.2-linux-x64/bin
npm_link_name=npm
ansible_ssh_user=vagrant
And I'm getting the error:
FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'dest_dir'
Which I'm not understanding since all the variables used in the task are declared and correct. I made a similar task without items:
- name: Create symbolic link for npm
file:
src: '{{ npm_real_dir }}/{{ npm_real_name }}'
path: '{{ nodenpm_link_dir }}/{{ npm_link_name }}'
owner: "{{ ansible_ssh_user }}"
group: "{{ ansible_ssh_user }}"
state: link
And its working, however the structure is the same as before, just without the items.
At this point I just want to know if its a known bug, if there is any issue in using items to create links, or if I did a stupid mistake and gain knowledge about it
Thanks in advance
The issue is that you're passing two different objects to the with_items property. The first object has two properties (src_dir and src_name), while the second object has two different properties (dest_dir and dest_name).
It looks like you want to combine them into a single object like this:
- name: Create NPM symlink
file:
src: '{{ item.src_dir }}/{{ item.src_name }}'
dest: '{{ item.dest_dir }}/{{ item.dest_name }}'
owner: "{{ ansible_ssh_user }}"
group: "{{ ansible_ssh_user }}"
state: link
with_items:
- { src_dir: "{{ npm_real_dir }}", src_name: "{{ npm_real_name }}", dest_dir: "{{ nodenpm_link_dir }}", dest_name: "{{ npm_link_name }}" }
That should work better and get rid of the error, but in this case you don't really need the with_items, since it's only one item that you're dealing with. You can add more objects for other tools, e.g. gulp in the same manner, e.g. like this:
- name: Create symlinks
file:
src: '{{ item.src_dir }}/{{ item.src_name }}'
dest: '{{ item.dest_dir }}/{{ item.dest_name }}'
owner: "{{ ansible_ssh_user }}"
group: "{{ ansible_ssh_user }}"
state: link
with_items:
- { src_dir: "{{ npm_real_dir }}", src_name: "{{ npm_real_name }}", dest_dir: "{{ nodenpm_link_dir }}", dest_name: "{{ npm_link_name }}" }
- { src_dir: "{{ gulp_real_dir }}", src_name: "{{ gulp_real_name }}", dest_dir: "{{ gulp_link_dir }}", dest_name: "{{ gulp_link_name }}" }

Advice on improving my playbooks efficiency?

Started to experiment with Ansible and using playbooks to automate some routine tasks on network devices. I was able to get some basic stuff working and learn in the process but I know my knowledge is limited so when I see this playbook and how much stuff seems redundant I have to assume there are better ways to eliminate some of the redundancy and make things cleaner and more efficient.
Example I want to try to use and explain in order to get some ideas on is around configuring a new vlan on a group of devices.
Typically a new vlan first needs to be configured on the two distribution switches and then there are specific interfaces on those two switches that we have to add the vlan to.
So, for this first part I have the two hosts in a group called "dist" in my hosts file:
[dist]
DIST01 ansible_host=10.10.1.1
DIST02 ansible_host=10.10.1.2
Then I created the following in my playbook:
- name: Add Heartbeat VLAN to DIST
hosts: dist
connection: local
gather_facts: no
tasks:
- name: Include Login Credentials
include_vars: secrets.yml
- name: Define Provider
set_fact:
provider:
host: "{{ ansible_host }}"
username: "{{ creds['username'] }}"
password: "{{ creds['password'] }}"
tasks:
- name: Ensure VLAN Exists
provider: "{{ provider }}"
nxos_vlan: vlan_id="2600" state=present host={{ ansible_host }}
- name: Ensure VLAN Name Configured
provider: "{{ provider }}"
nxos_vlan: vlan_id={{ item.vid }} name={{ item.name }} host={{ ansible_host }} state=present
with_items:
- { vid: 2600, name: Ansible Heartbeat VLAN }
- name: ASSIGN VLAN TO TRUNK PORTS
nxos_switchport:
interface: "{{ item.interface }}"
mode: trunk
trunk_vlans: "{{ item.vlan }}"
provider: "{{ provider }}"
with_items:
- { interface: po850, vlan: 2600 }
- { interface: po860, vlan: 2600 }
- { interface: po865, vlan: 2600 }
- { interface: po868, vlan: 2600 }
- { interface: po871, vlan: 2600 }
- { interface: po872, vlan: 2600 }
- { interface: po875, vlan: 2600 }
- { interface: po877, vlan: 2600 }
- { interface: po884, vlan: 2600 }
So, for each host in that group it iterates through a list of interfaces / ports and adds the vlan specified.
Question #1.
First thing that stands out as being "inefficient" in my mind is I don't believe its very wise to have to specify the "vlan: 2600" every where.
I would think I should just set the vlan as a variable some where (in the playbook? in some other file that gets called?) to be used in each case where it is needed.
Next set of tasks:
After the previous task the next requires us to connect to each access switch that needs the vlan to be deployed on and configure the new vlan there.
The issue I run into here is that the port-channel on each of these switches is a different interface #. So I can't apply the same config by just iterating through a list of devices.
For instance what I have to do is something like this:
host: ACCESS01 interface: po850 vlan: 2600
host: ACCESS02 interface: po860 vlan: 2600
host: ACCESS03 interface: po870 vlan: 2600
So for each host/switch you add the vlan to the interface associated with that switch.
I just created a new task for each device that specifies the interface to configure for that switch.
Example:
- name: Add Heartbeat VLAN to ACCESS01
hosts: ACCESS01
connection: local
gather_facts: no
tasks:
- name: Include Login Credentials
include_vars: secrets.yml
- name: Define Provider
set_fact:
provider:
host: "{{ ansible_host }}"
username: "{{ creds['username'] }}"
password: "{{ creds['password'] }}"
tasks:
- name: Ensure VLAN Exists
provider: "{{ provider }}"
nxos_vlan: vlan_id="2600" state=present host={{ ansible_host }}
- name: Ensure VLAN Name Configured
provider: "{{ provider }}"
nxos_vlan: vlan_id={{ item.vid }} name={{ item.name }} host={{ ansible_host }} state=present
with_items:
- { vid: 2600, name: Ansible Heartbeat VLAN }
- name: ASSIGN VLAN TO PORTS
nxos_switchport:
interface: "{{ item.interface }}"
mode: trunk
trunk_vlans: "{{ item.vlan }}"
provider: "{{ provider }}"
with_items:
- { interface: po850, vlan: 2600 }
- name: Add Heartbeat VLAN to ACCESS02
hosts: ACCESS02
connection: local
gather_facts: no
tasks:
- name: Include Login Credentials
include_vars: secrets.yml
- name: Define Provider
set_fact:
provider:
host: "{{ ansible_host }}"
username: "{{ creds['username'] }}"
password: "{{ creds['password'] }}"
tasks:
- name: Ensure VLAN Exists
provider: "{{ provider }}"
nxos_vlan: vlan_id="2600" state=present host={{ ansible_host }}
- name: Ensure VLAN Name Configured
provider: "{{ provider }}"
nxos_vlan: vlan_id={{ item.vid }} name={{ item.name }} host={{ ansible_host }} state=present
with_items:
- { vid: 2600, name: Ansible Heartbeat VLAN }
- name: ASSIGN VLAN TO PORTS
nxos_switchport:
interface: "{{ item.interface }}"
mode: trunk
trunk_vlans: "{{ item.vlan }}"
provider: "{{ provider }}"
with_items:
- { interface: po860, vlan: 2600 }
- name: Add Heartbeat VLAN to ACCESS03
hosts: ACCESS03
connection: local
gather_facts: no
tasks:
- name: Include Login Credentials
include_vars: secrets.yml
- name: Define Provider
set_fact:
provider:
host: "{{ ansible_host }}"
username: "{{ creds['username'] }}"
password: "{{ creds['password'] }}"
tasks:
- name: Ensure VLAN Exists
provider: "{{ provider }}"
nxos_vlan: vlan_id="2600" state=present host={{ ansible_host }}
- name: Ensure VLAN Name Configured
provider: "{{ provider }}"
nxos_vlan: vlan_id={{ item.vid }} name={{ item.name }} host={{ ansible_host }} state=present
with_items:
- { vid: 2600, name: Ansible Heartbeat VLAN }
- name: ASSIGN VLAN TO PORTS
nxos_switchport:
interface: "{{ item.interface }}"
mode: trunk
trunk_vlans: "{{ item.vlan }}"
provider: "{{ provider }}"
with_items:
- { interface: po870, vlan: 2600 }
And so you see... I know when I see things almost identical repeated over and over again I have to assume there is a better way and I just don't know enough yet to solve on my own.
Question #2. I suspect there is a better way to handle repeating the following for each task in the playbook:
tasks:
- name: Include Login Credentials
include_vars: secrets.yml
- name: Define Provider
set_fact:
provider:
host: "{{ ansible_host }}"
username: "{{ creds['username'] }}"
password: "{{ creds['password'] }}"
Question #3, Could I possibly just list this data some where, either in the playbook or another file maybe and then create a task that could iterate through the data to determine what port needs to be configured?
host: ACCESS01 interface: po850 vlan: 2600
host: ACCESS02 interface: po860 vlan: 2600
host: ACCESS03 interface: po870 vlan: 2600
Some sort of logic to this in my mind would be something like, if "host" equals "ACCESS01" then interface equals po850.
So the task could just be referencing variables that are populated depending on the host its currently working on?
Any thoughts and advice on improving both the playbook and my knowledge of things is greatly appreciated. I guess I'm look for the most "ansiblistic" way to accomplish this. That's not a word huh?
For Question#1, you can use like this:
- name: ASSIGN VLAN TO TRUNK PORTS
nxos_switchport:
interface: "{{ item.interface }}"
mode: trunk
trunk_vlans: "{{ item.vlan | default('2600') }}"
provider: "{{ provider }}"
with_items:
- interface: po850
- interface: po860
- interface: po865
- interface: po868
- interface: po871
- interface: po872
- interface: po875
- interface: po884
If you want to assign different vlan to one or more interface(s), then you can use like this:
- { interface: po850, vlan: 2700 }
Hope that help you.

ansible 2.0.0-0.3.beta1 ec2_vpc_route_table ec2_vpc_subnet

I am creating an ansible playbook using version 2.0.0-0.3.beta1
I'd like to get the subnet id after i create a subnet. I'm refering the official ansible docs : http://docs.ansible.com/ansible/ec2_vpc_route_table_module.html
- name: Create VPC Public Subnet
ec2_vpc_subnet:
state: present
resource_tags: '{"Name":"{{ prefix }}_subnet_public_0"}'
vpc_id: "{{ vpc.vpc_id }}"
az: "{{ az0 }}"
cidr: 172.16.0.0/24
register: public
- name: Create Public Subnet Route Table
ec2_vpc_route_table:
vpc_id: "{{ vpc.vpc_id }}"
region: "{{ region }}"
tags:
Name: Public
subnets:
- "{{ public.subnet_id }}"
routes:
- dest: 0.0.0.0/0
gateway_id: "{{ igw.gateway_id }}"
after running the playbook i received following error:
fatal: [localhost]: FAILED! => {"failed": true, "msg": "ERROR! 'dict object' has no attribute 'subnet_id'"}
Try using: public.subnet.id instead of public.subnet-id
Its useful to debug by running this task:
- debug: msg="{{ public }}"

Resources