Ansible loops for assigning sequential integers as hostnames - linux

I'm new to Ansible. I have the following playbook that changes the hostname for a remote server:
---
- hosts: dbservers
remote_user: testuser1
become: yes
become_method: sudo
vars:
LOCAL_HOSTNAME: 'db01'
LOCAL_DOMAIN_NAME: 'ansibletest.com'
tasks:
# Checks and removed the existing occurences of <IP hostname FQDN> from /etc/hosts
- name: Remove occurences of the existing IP
lineinfile: dest=/etc/hosts
regexp='{{ hostvars[item].ansible_default_ipv4.address }}'
state=absent
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
# Adds the IP in the format <IP hostname FQDN> to /etc/hosts
- name: Add the IP and hostname to the hosts file
lineinfile: dest=/etc/hosts
regexp='.*{{ item }}$'
line="{{ hostvars[item].ansible_default_ipv4.address }} {{ LOCAL_HOSTNAME }} {{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}"
state=present
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Remove HOSTNAME occurences from /etc/sysconfig/network
lineinfile: dest=/etc/sysconfig/network
regexp='^HOSTNAME'
state=absent
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Add new HOSTNAME to /etc/sysconfig/network
lineinfile: dest=/etc/sysconfig/network
regexp='^HOSTNAME='
line="HOSTNAME={{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}"
state=present
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Set up the hostname
hostname: name={{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}
In this example, LOCAL_HOSTNAME is already assigned a value of db01. And in this scenario, the dbservers group has only one server:
[dbservers]
192.168.1.93
However, I also have 2 other servers that are designated to be webservers:
[webservers]
192.168.1.95
192.168.1.96
[dbservers]
192.168.1.93
The aim is to name them as web01.domain, web02.domain and so on.
As per the docs it seems that this could be achieved by using with_sequence.
My question is, is it possible (in Ansible) to use 2 variables in loops? Something along the lines of the pseudo-code below:
i=1
for host in webservers:
open host(/etc/hosts):
add "IP<space>HOSTNAME{i}<space>"<space>"HOSTNAME{i}.FQDN"
i++
Could this be achieved using playbooks or am I approaching the issue in an wrong way?

Generate indexed hostname first, define it as hostfact and use it later to fill other servers' hosts files.
- hosts: webservers
gather_facts: no
tasks:
- set_fact:
indexed_hostname: "{{ 'web{0:02d}'.format(play_hosts.index(inventory_hostname)+1) }}"
- hosts: dbservers
gather_facts: no
tasks:
- debug:
msg: "{{ hostvars[item].indexed_hostname }}"
with_items: "{{ groups['webservers'] }}"
Also there is such thing as with_indexed_items.

Related

Multiple with_items in an Ansible module block

I want to create multiple logical volumes with a variable file but it return a sintax error found character that cannot start any token, I have tried in different ways but still doesn't work
main.yml
---
- name: playbook for create volume groups
hosts: localhost
become: true
tasks:
- include_vars: vars.yml
- name: Create a logical volume
lvol:
vg: vg03
lv: "{{ item.var1 }}"
size: "{{ item.var2 }}"
with_items:
- { var1: "{{ var_lv_name }}", var2: "{{ var_lv_size }}" }
vars.yml
var_lv_name:
- lv05
- lv06
var_lv_size:
- 1g
- 1g
Use with_together. Test it first. For example,
- debug:
msg: "Create lv: {{ item.0 }} size: {{ item.1 }}"
with_together:
- "{{ var_lv_name }}"
- "{{ var_lv_size }}"
gives (abridged)
msg: 'Create lv: lv05 size: 1g'
msg: 'Create lv: lv06 size: 1g'
Optionally, put the declaration below into the file vars.yml
var_lv: "{{ var_lv_name|zip(var_lv_size) }}"
This creates the list
var_lv:
- [lv05, 1g]
- [lv06, 1g]
Use it in the code. The simplified task below gives the same results
- debug:
msg: "Create lv: {{ item.0 }} size: {{ item.1 }}"
loop: "{{ var_lv }}"
The previous answer it's totally correct but In my humble opinion we should be getting into the new way to do the things with loop and filters.
Here's my answer:
---
- name: playbook for create volume groups
hosts: localhost
gather_facts: no
become: true
vars_files: vars.yml
tasks:
- name: Create a logical volume
lvol:
vg: vg03
lv: "{{ item[0] }}"
size: "{{ item[1] }}"
loop: "{{ var_lv_name | zip(var_lv_size) | list }}"
In this answer you're using the new way to use loops with keyword loop and using filters like zip and turning the result into a list type for iteration in the loop.

Ansible to execute a task only when multiple files exist

I want to execute a task only when multiple files exist . if only single file is exist i need to ignore this task. How can i achieve this.
Am unable to achieve with the below playbook
---
- name: Standardize
hosts: test
gather_facts: false
vars:
file_vars:
- {id: 1, name: /etc/h_cm}
- {id: 2, name: /etc/H_CM}
tasks:
- block:
- name: Check if both exists
stat:
path: "{{ item.name }}"
with_items: "{{ file_vars }}"
register: cm_result
- name: Move both files
shell: mv "{{ item.item }}" /tmp/merged
with_items: "{{ cm_result.results }}"
when: item.stat.exists
After check if both exist task, you can add a set fact task like this one:
- name: set facts
set_fact:
files_exist: "{{ (files_exist | default([])) + [item.stat.exists] }}"
with_items: "{{ cm_result.results }}"
And you change your move files task to:
- name: Move both files
debug:
msg: "{{ item.stat.exists }}"
with_items: "{{ cm_result.results }}"
when: false not in files_exist
You have to specify shell: mv "{{ item.item.name }}" /tmp/merged insted of shell: mv "{{ item.item }}" /tmp/merged
Check the below works?:
- name: Standardize
hosts: test
gather_facts: false
become: yes ## If needed
vars:
file_vars:
- {id: 1, name: /etc/h_cm}
- {id: 2, name: /etc/H_CM}
tasks:
- block:
- name: Check if both file exists
stat:
path: "{{ item.name }}"
with_items: "{{ file_vars }}"
register: cm_result
- debug:
var: item.stat.exists
loop: "{{ cm_result.results }}"
- name: Crate a dummy list
set_fact:
file_state: []
- name: Add true to list if file exists
set_fact:
file_state: "{{ file_state }} + ['{{ item.stat.exists }}']"
loop: "{{ cm_result.results }}"
when: item.stat.exists == true
- name: Move both files
shell: mv "{{ item.item.name }}" /tmp/merged
loop: "{{ cm_result.results }}"
when: file_state|length > 1

How ansible can call multiple files under path option as a variable

I am just learning ansible and trying to understand how can i include multiple file in the path option in ansible replace module.
I have three files where i need to replace a old hostname with new hostanme.
Files are :
- /etc/hosts
- /etc/hosts.custom
- /etc/hosts-backup
Below Simple Play works fine:
- name: Replace string in hosts file
hosts: all
gather_facts: false
tasks:
- name: Replace string in host file
replace:
path: /etc/hosts
regexp: "171.20.20.16 fostrain.example.com"
replace: "171.20.20.16 dbfoxtrain.example.com"
backup: yes
However, after lot of googling i see this can be done as follows, but in case i have multiple files and those needs to be called as a variable in different modules, How we can define then in such a way so as to call them by variable name.
Below is Just what i am trying to understand..
- name: Replace string in hosts file
hosts: all
gather_facts: false
tasks:
- name: Checking file contents
slurp:
path: "{{ ?? }}" <-- How to check these three files here
register: fileCheck.out
- debug:
msg: "{{ (fileCheck.out.content | b64decode).split('\n') }}"
- name: Replace string in host file
replace:
path: "{{ item.path }}"
regexp: "{{ item:from }}"
replace: "{{ item:to }}"
backup: yes
loop:
- { path: "/etc/hosts", From: "171.20.20.16 fostrain.example.com", To: "171.20.20.16 dbfoxtrain.example.com"}
- { Path: "/etc/hosts.custom", From: "171.20.20.16 fostrain.example.com", To: "171.20.20.16 dbfoxtrain.example.com"}
- { Path: "/etc/hosts-backup", From: "171.20.20.16 fostrain.example.com", To: "171.20.20.16 dbfoxtrain.example.com"}
I will appreciate any help.
Create couple of variables; a list with all the files, from and to replacement strings or divide them by ip and domain. Then loop over all the files using the file list variable and use from and to replacement variables for each file. If multiple ip and domain mapping is required then you need to adjust the structure further. So recommend going through ansible documentation on using variables and loops for more details.
Playbook may look like below. Have used a minor regex and you can adjust as required.
- name: Replace string in hosts file
hosts: all
gather_facts: false
vars:
files:
- /etc/hosts
- /etc/hosts.custom
- /etc/hosts-backup
from_ip: "171.20.20.16"
from_dn: "fostrain.example.com"
to_ip: "171.20.20.16"
to_dn: "dbfoxtrain.example.com"
tasks:
- name: Replace string in host file
replace:
path: "{{ item }}"
regexp: "{{ from_ip }}\\s+{{ from_dn }}"
replace: "{{ to_ip }} {{ to_dn }}"
loop: "{{ files }}"
If you want to see the contents of each file then slurp and debug modules can be used like below:
- slurp:
path: "{{ item }}"
loop: "{{ files }}"
register: contents
- debug:
msg: "{{ (item.content | b64decode).split('\n') }}"
loop: "{{ contents.results }}"

Ansible playbook loop with with_items

I have to update sudoers.d multiple user files with few lines/commands using ansible playbook
users.yml
user1:
- Line1111
- Line2222
- Line3333
user2:
- Line4444
- Line5555
- Line6666
main.yml
- hosts: "{{ host_group }}"
vars_files:
- ../users.yml
tasks:
- name: Add user "user1" to sudoers.d
lineinfile:
path: /etc/sudoers.d/user1
line: '{{ item }}'
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ user1 }}"
The above one is working only for user1..
If I want to also include user2 --> How to change the file name : path: /etc/sudoers.d/user1
I tried below and its not working :
Passing below users as variable to main.yml while running
users:
- "user1"
- "user2"
- name: Add user "{{users}}" to sudoers.d
lineinfile:
path: /etc/sudoers.d/{{users}}
line: '{{ item }}'
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ users }}"
So, basically I want to pass in users to a variable {{users}} as user1 and user2 and wanted to use the lines for each user from users.yml and add it to respective user files (/etc/sudoers.d/user1 and /etc/sudoers.d/user2).
So /etc/sudoers.d/user1 should look like
Line1111
Line2222
Line3333
and /etc/sudoers.d/user2 should look like
Line4444
Line5555
Line6666
Try to add quotes:
users:
- "user1"
- "user2"
- name: "Add user {{users}} to sudoers.d"
lineinfile:
path: "/etc/sudoers.d/{{users}}"
line: "{{ item }}"
state: present
mode: 0440
create: yes
validate: 'visudo -cf %s'
with_items:
- "{{ users }}"
As per Ansible Documentation on Using Variables:
YAML syntax requires that if you start a value with {{ foo }} you quote the whole line, since it wants to be sure you aren’t trying to start a YAML dictionary. This is covered on the YAML Syntax documentation.
This won’t work:
- hosts: app_servers
vars:
app_path: {{ base_path }}/22
Do it like this and you’ll be fine:
- hosts: app_servers
vars:
app_path: "{{ base_path }}/22"
cat users.yml
---
users:
- user1:
filename: user1sudoers
args:
- Line1111
- Line2222
- Line3333
- user2:
filename: user2sudoers
args:
- Line4444
- Line5555
- Line6666
I use template here, instead of lineinfile
---
cat sudoers.j2
{% if item.args is defined and item.args %}
{% for arg in item.args %}
{{ arg }}
{% endfor %}
{% endif %}
the task content
---
- hosts: localhost
vars_files: ./users.yml
tasks:
- name: sync sudoers.j2 to localhost
template:
src: sudoers.j2
dest: "/tmp/{{ item.filename }}"
loop: "{{ users_list }}"
when: "users_list is defined and users_list"
after run the task.yml, generate two files under /tmp directory.
cat /tmp/user1sudoers
Line1111
Line2222
Line3333
cat /tmp/user2sudoers
Line4444
Line5555
Line6666

Ansible vmware_guest optional disk with Ansible Tower survey

I have a playbook for the creation of a VM from a template in VMware ESXi 6.7. My playbook is below. I want to only configure the second (and possible subsequent) disks if the DISK1_SIZE_GB variable is > 0. This is not working. I've also tried using 'when: DISK1_SIZE_GB is defined' with no luck. I'm using a survey in Ansible Tower, with the 2nd disk configuration being an optional answer. In this case I get an error about 0 being an invalid disk size, or when I check for variable definition I get an error about DISK1_SIZE_GB being undefined. Either way, the 'when' conditional doesn't seem to be working.
If I hardcode the size, as in the first 'disk' entry, it works fine .. same if I enter a valid size from Ansible Tower. I need to NOT configure additional disks unless the size is defined in the Tower survey.
Thanks!
---
- name: Create a VM from a template
hosts: localhost
gather_facts: no
tasks:
- name: Clone a template to a VM
vmware_guest:
hostname: "{{ lookup('env', 'VMWARE_HOST') }}"
username: "{{ lookup('env', 'VMWARE_USER') }}"
password: "{{ lookup('env', 'VMWARE_PASSWORD') }}"
validate_certs: 'false'
name: "{{ HOSTNAME }}"
template: RHEL-Server-7.7
datacenter: Production
folder: Templates
state: poweredon
hardware:
num_cpus: "{{ CPU_NUM }}"
memory_mb: "{{ MEM_MB }}"
disk:
- size_gb: 20
autoselect_datastore: true
- size_gb: "{{ DISK1_SIZE_GB }}"
autoselect_datastore: true
when: DISK1_SIZE_GB > 0
networks:
- name: "{{ NETWORK }}"
type: static
ip: "{{ IP_ADDR }}"
netmask: "{{ NETMASK }}"
gateway: "{{ GATEWAY }}"
dns_servers: "{{ DNS_SERVERS }}"
start_connected: true
wait_for_ip_address: yes
AFAIK this can't be accomplished in a single task. You were on the right track with when: DISK1_SIZE_GB is defined if disk: was a task and not a parameter though. Below is how I would approach this.
Create two survey questions:
DISK1_SIZE_GB - integer - required answer - enforce a non-zero
minimum value such as 20 (since you're deploying RHEL)
DISK2_SIZE_GB - integer - optional answer - no minimum or maximum
value
Create disk 1 in your existing vmware_guest task:
disk:
- size_gb: "{{ DISK1_SIZE_GB }}"
autoselect_datastore: true
Create a new vmware_guest_disk task which runs immediately afterwards and conditionally adds the second disk:
- name: Add second hard disk if necessary
vmware_guest_disk:
hostname: "{{ lookup('env', 'VMWARE_HOST') }}"
username: "{{ lookup('env', 'VMWARE_USER') }}"
password: "{{ lookup('env', 'VMWARE_PASSWORD') }}"
validate_certs: 'false'
name: "{{ HOSTNAME }}"
datacenter: Production
folder: Templates
state: poweredon
disk:
- size_gb: "{{ DISK2_SIZE_GB }}"
autoselect_datastore: true
when: DISK2_SIZE_GB is defined

Resources