Constructing a loop in Ansible - loops

I'm writing an Ansible role that sets up the network. For each type of interface (ethernet, bond, bridge and vlan) I made a variable that contains the relevant data.
The idea is that I have to make a loop that runs the number of times that there are elements in a list variable ('bridge_ports') and for each pass a configuration file is created via a template.
Parts of the variable for bridge interfaces look like this:
my_network__bridge_interface:
address: "192.168.1.48",
bootproto: "static",
bridge_ports:
- eth0
- eth1
device: "br-mgmt",
...
To make the pass, I have tried with a with_subelements loop - but this does not go well.
- name: Create the network configuration file for the port on the bridge devices
template:
src: "{{ ansible_os_family }}.bridge_port.j2"
dest: "{{ my_network__ifconf_path }}/ifcfg-{{ item.1 }}"
with_subelements
- "{{ my_network__bridge_interface }}"
- bridge_ports
when: device_conf.type == 'bridge'
register: my_network__bridge_port_result
When I run the code, the error message comes: "could not find 'bridge_ports' key in iterated item '{}'".
I can see that I use with_subelements in the wrong way, but I don't really know what type of loop I would otherwise need.

The issue is with the yml definition. The below yml works:
my_network__bridge_interface:
- address: "192.168.1.48"
bootproto: static
bridge_ports:
- eth0
- eth1
device: br-mgmt
playbook -->
---
- hosts: localhost
tasks:
- include_vars: vars.yml
- debug:
msg: "{{ item.1 }}"
with_subelements:
- "{{ my_network__bridge_interface }}"
- bridge_ports
output -->
TASK [debug] ****************************************************************************************************************************
ok: [localhost] => (item=[{u'device': u'br-mgmt', u'bootproto': u'static', u'address': u'192.168.1.48'}, u'eth0']) => {
"msg": "eth0"
}
ok: [localhost] => (item=[{u'device': u'br-mgmt', u'bootproto': u'static', u'address': u'192.168.1.48'}, u'eth1']) => {
"msg": "eth1"
}

Related

Ansible: loop with using collection and role

I´m doing the first steps in Ansible this week and I break on include_tasks for looping ofer a role.
The needed task is to create Letsencrypt certificates for a bunch of domains, thanks to T-Systems-MMS, there is already a collection to do this via APIs of letsencrypt and AutoDNS (see https://github.com/T-Systems-MMS/ansible-collection-acme/blob/master/docs/dns-challenge/autodns.md).
Filling this playbook with my settings, it is working fine for one domain. My try to loop over is (hopefully there was no mistake while anonymising the code):
playbook_getsslcert_main.yml:
---
- hosts: localhost
connection: local
vars:
ansible_python_interpreter: auto
tasks:
- name: Get SSL certificate
include_tasks: playbook_getsslcert_task.yml
loop:
- sub1.domain1.com
- sub2.domain1.com
playbook_getsslcert_task.yml:
---
- name: Doing letsencrypt ACME with AutoDNS
collections:
- t_systems_mms.acme
roles:
- acme
vars:
nbb_emailadress: my.email#example.com
nbb_autodnsuser: login.user#other.com
acme_domain:
certificate_name: "{{ item }}"
zone: "domain1.com"
email_address: "{{ nbb_emailadress }}"
subject_alt_name:
- "{{ item }}"
acme_challenge_provider: autodns
acme_use_live_directory: true
acme_conf_dir: /etc/letsencrypt
acme_account_email: "{{ nbb_emailadress }}"
acme_dns_user: "{{ nbb_autodnsuser }}"
acme_dns_password: "supersecret"
The error I get is
fatal: [localhost]: FAILED! => {"reason": "conflicting action statements: hosts, roles\n\nThe error appears to be in 'playbook_getsslcert_task.yml': line 2, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n---\n- name: Doing letsencrypt ACME with AutoDNS\n ^ here\n"}
My collegues and me are experienced Linux guys, we tested a lot; also we checked the YAML with formatcheckers and so on, did different styles for looping, tried an example tasks.ym just for writing a message, checked file formats (for linefeeds, correct HEX values,...) and so on.
But Ansible doesnt like the playbook.
Thanks for all your suggestions.
Edit:
Ubuntu 18.04 LTS, Python 3.6.9, Ansible 2.9.27
Thanks to #Zeitounator (sorry for overlooing your first link), a suitable and working solution have been found:
---
- hosts: all
connection: local
vars:
ansible_python_interpreter: auto
tasks:
- name: "Doing letsencrypt ACME with AutoDNS for {{ nbb_domain }}"
collections:
- t_systems_mms.acme
include_role:
name: acme
vars:
nbb_emailadress: my.email#example.com
nbb_autodnsuser: login.user#other.com
acme_domain:
certificate_name: "{{ nbb_domain }}"
zone: "domain1.com"
email_address: "{{ nbb_emailadress }}"
subject_alt_name:
- "{{ nbb_domain }}"
acme_challenge_provider: autodns
acme_use_live_directory: true
acme_conf_dir: /etc/letsencrypt
acme_account_email: "{{ nbb_emailadress }}"
acme_dns_user: "{{ nbb_autodnsuser }}"
acme_dns_password: "supersecret"
loop:
- sub1.domain1.com
- sub2.domain1.com
loop_control:
loop_var: nbb_domain

Ansible nested variables with with_sequence loop

i'm trying to create multiple VMs by setting the number of machines as a variable then iterate over that number using with_sequence. The deal is that I want to manually assign static IP addresses to my VMs so I have to iterate with nested variables.
My main file:
- name: Create Azure VM
hosts: localhost
connection: local
vars_files:
- vault.yml
pre_tasks:
- set_fact:
cluster: "testvm"
- set_fact:
subnetName: "default"
- set_fact:
instancesCount: 2
- set_fact:
IP1: "172.16.32.83"
- set_fact:
IP2: "172.16.32.84"
- set_fact:
vmSize: "Standard_DS1_v2"
- set_fact:
osDiskType: "Standard_LRS"
- set_fact:
dataDiskType: "Premium_LRS"
- set_fact:
diskSize: "4"
roles:
- azure
The snippet for the azure role used above where we have issues:
- name: Create virtual network interface cards
azure_rm_networkinterface:
resource_group: "{{ envir }}-emp-{{ cluster }}"
name: "{{ envir }}-emp-{{ cluster }}-nic-{{ item }}"
virtual_network: "/subscriptions/{{ subscriptionId }}/resourceGroups/{{ vnetResourceGroup }}/providers/Microsoft.Network/virtualNetworks/{{ virtualNetworkName }}"
subnet: "{{ subnetName }}"
public_ip: no
create_with_security_group: False
ip_configurations:
- name: ipconfig1
primary: yes
private_ip_address: "{{vars[IP].instancesCount}}" --->> here lies the question
private_ip_allocation_method: Static
primary: True
with_sequence: "count={{ instancesCount }}"
How would we call the values of the IP1 and IP2 inside the loop under the with_sequence block?
You may want to try with something like this:
"{{ lookup('vars', 'IP{}'.format(instancesCount)) }}"

Ansible loop using ansible_hostnames

I'm trying to update a configuration file for a NiFi deployment, the inital deployment configuration needs to include the nodes to allow HTTPS connections to be established between them.
I have an ansible tasks that makes the required structural changes to the configuration files, but I can't seem to get the right details inserted.
- name: Add each host to the authorizers.xml
lineinfile:
path: /opt/nifi/conf/authorizers.xml
line: "<property name=\"Node Identity {{ item }}\">CN={{ item }}, OU=NiFi</property>"
insertafter: <!--accessPolicyProvider Node Identities-->
loop: "{{ query('inventory_hostnames', 'nifi') }}"
This puts the ip addresses for the hosts, and I need to get the ansible_hostname for each node instead.
I've played around with ansible_play_batch and loop: "{{ groups['nifi'] }}" but I'm getting the result, outputting the ip addresses instead of the short hostnames each time.
The short hostnames are not stored in my ansible configuration anywhere, they are (if I understand correctly) determined at run time via the gathering facts process. I'd really like to not have to put the node names into a list variable.
Q: "Get the ansible_hostname for each node"
A: Given the inventory
shell> cat hosts
[nifi]
10.1.0.51
10.1.0.52
The playbook below
- hosts: nifi
tasks:
- debug:
var: ansible_hostname
gives (abridged)
ok: [10.1.0.51] =>
ansible_hostname: test_01
ok: [10.1.0.52] =>
ansible_hostname: test_02
It's possible to iterate the hosts in the group and get ansible_hostname from the hostvars. For example, delegate_to localhost and run_once
- debug:
msg: "{{ hostvars[item].ansible_hostname }}"
loop: "{{ groups.nifi }}"
delegate_to: localhost
run_once: true
gives
ok: [10.1.0.51 -> localhost] => (item=10.1.0.51) =>
msg: test_01
ok: [10.1.0.51 -> localhost] => (item=10.1.0.52) =>
msg: test_02

how to pass split values into a loop in ansible based on input range?

I am pretty new to Ansible loops.
I am trying to replace a set of ip addresses (oldip to newip) in a file.
It needs to accept the input ip address as a dynamic list in the form oldip1:newip1,oldip2:newip2,oldip3:newip3...
I want to split the above ip address pair (old:new) and then pass them over to Ansible replace module in a loop based on input list.
I am trying something like this, but am stuck on how to pass the dynamic range of ip address pair into the loop.
My input variable is like 192.123.12.11;192.123.12.20, 192.123.12.12;192.123.12.19 , 192.123.12.13;192.123.12.18 ... (dynamic ip pairs range)
vars:
ip_pairs: oldIP_newIP
tasks:
- debug: "{{ oldIP_newIP.split (',') }}"
- replace:
path: /home/ubuntu/cassandra.properties
regexp: "{{ item.regexp }}"
replace: "{{ item.replace }}"
backup: yes
with_items:
- { regexp = "{{ oldIP }}", replace = "{{ newIP }}"
- { regexp = "{{ oldIP }}", replace = "{{ newIP }}"
.
.
.
.
This loop should continue based on input ip pairs entry (5 times for 5 ip pairs (old;new).
Something really useful you can use in order to achieve this is the fact that you can register variables at the task level, via the vars property.
For exemple:
- name: This will display 'bar', the content of foo
debug:
msg: "{{ foo }}"
vars:
foo: 'bar'
Base on this, you can achieve what you want quite easily doing:
- name: Replace IPs
replace:
path: /home/ubuntu/cassandra.properties
regexp: "{{ oldNewIp[0] | trim }}"
replace: "{{ oldNewIp[1] | trim }}"
backup: yes
vars:
oldNewIp: "{{ item.split(';') }}"
with_items: "{{ ipPairs.split(',') }}"
Please mind that I added some extra Jinja trim in order to cope with the fact that, in your example input, you do have some space after your commas.
Here is a full example play to demo this:
Given the playbook
- hosts: local
vars:
ipPairs: '192.123.12.11;192.123.12.20, 192.123.12.12;192.123.12.19, 192.123.12.13;192.123.12.18'
tasks:
- name: Create an example file to replace in
copy:
dest: /tmp/ips.txt
content: ''
- name: Populate a file with old IPs
lineinfile:
path: /tmp/ips.txt
line: "{{ oldNewIp[0] | trim }}"
vars:
oldNewIp: "{{ item.split(';') }}"
with_items: "{{ ipPairs.split(',') }}"
- name: Replace IPs
replace:
path: /tmp/ips.txt
regexp: "{{ oldNewIp[0] | trim }}"
replace: "{{ oldNewIp[1] | trim }}"
backup: yes
vars:
oldNewIp: "{{ item.split(';') }}"
with_items: "{{ ipPairs.split(',') }}"
I have the following play:
/ # ansible-playbook -i inventory.yaml play.yaml
PLAY [local] *********************************************************************************************
TASK [Gathering Facts] ***********************************************************************************
[WARNING]: Platform linux on host local is using the discovered Python interpreter at /usr/bin/python3,
but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more
information.
ok: [local]
TASK [Create an example file to replace in] **************************************************************
changed: [local]
TASK [Populate a file with old IPs] ***********************************************************************
changed: [local] => (item=192.123.12.11;192.123.12.20)
changed: [local] => (item= 192.123.12.12;192.123.12.19)
changed: [local] => (item= 192.123.12.13;192.123.12.18)
TASK [Replace IPs] ***************************************************************************************
changed: [local] => (item=192.123.12.11;192.123.12.20)
changed: [local] => (item= 192.123.12.12;192.123.12.19)
changed: [local] => (item= 192.123.12.13;192.123.12.18)
PLAY RECAP ***********************************************************************************************
local : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
/ # cat /tmp/ips.txt
192.123.12.20
192.123.12.19
192.123.12.18

Is there any ansible module to change the existing server ip and reboot it

I have a playbook, which replace the IP and hostname. The playbook works fine till it reboots the machine, but unable to connect it back as now the IP of that server is changed to something else.
Is there any better way to handle this problem.
---
- hosts: test
remote_user: root
vars:
IP_TO_REPLACE: '192.168.1.15'
IP_TO_REPLACE_WITH: '192.168.1.16'
HOSTNAME_TO_REPLACE: 'devops-15'
HOSTNAME_TO_REPLACE_WITH: 'devops-16'
tasks:
- name: modifying the ifcfg-BR0 file
replace:
path: "{{ item.path }}"
regexp: "{{ item.regexp1 }}"
replace: "{{ item.replace }}"
backup: yes
with_items:
- { path: '/etc/sysconfig/network/ifcfg-br0', regexp1: "{{ IP_TO_REPLACE }}", replace: "{{ IP_TO_REPLACE_WITH }}" }
- { path: '/etc/hosts', regexp1: "{{ IP_TO_REPLACE }}", replace: "{{ IP_TO_REPLACE_WITH }}" }
- { path: '/etc/hosts', regexp1: "{{ HOSTNAME_TO_REPLACE }}", replace: "{{ HOSTNAME_TO_REPLACE_WITH }}" }
- { path: '/etc/hostname', regexp1: "{{ HOSTNAME_TO_REPLACE }}", replace: "{{ HOSTNAME_TO_REPLACE_WITH }}" }
register: task_result
- name: Reboot immediately the server to take latest changes
shell: "sleep 15 && reboot"
async: 1
poll: 0
when: task_result is changed
- name: Wait for the reboot to complete if there was a changed
wait_for:
port: 22
host: '{{ (ansible_ssh_host |default(ansible_host))|default(inventory_hostname) }}'
search_regex: OpenSSH
delay: 10
timeout: 90
when: task_result is changed
- name: Check the uptime of the server
shell: "uptime"
register: uptime_result
- debug: var=uptime_result
Actual Output
TASK [Reboot immediately the server to take latest changes] *****************************************************************************
fatal: [192.168.1.15]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Shared connection to 192.168.1.15 closed.\r\n", "unreachable": true}
Expected Output
It should connect to the new IP 192.168.1.16
My research lead me to the following solution
Inspire by the suggestion of "drenthe73" here:
https://www.reddit.com/r/ansible/comments/n6xfyv/change_current_ip_address_and_reboot_into_the_new/
playbook with 2 plays
---
# play 1
hosts:
- host_ip_initial
# ...
tasks:
- name: "change ip address"
# ...
- name: "reboot target"
ansible.builtin.shell: "reboot"
async: 1
poll: 0
# play2
gather_facts: no
hosts:
- host_ip_final
# ...
tasks:
- name: "Wait for the target to reboot before probing it is up and running"
ansible.builtin.wait_for:
timeout: 300
delegate_to: localhost
- name: "wait for boot up"
ansible.builtin.wait_for_connection:
connect_timeout: 5
sleep: 5
delay: 30
timeout: 600
You need to know the final IP address and creates in your inventory the proper homolog entries.
Ansible various way to restart target:
https://www.redhat.com/sysadmin/automate-reboot-ansible
I am a bit late, but it might help someone else...

Resources