How to set default service port/protocol with firewalld module using Ansible? - linux

I'm looking for a method to set default port/protocol in my Ansible task.
# Add port in firewalld
- name: Open port in firewalld
firewalld:
port: "{{ foo_setted_port_var | default (8080/tcp) }}"
state: enabled
permanent: yes
immediate: yes
zone: "{{ setted_firewalldzone_var | default (public) }}"
when I put on line port : [...] default (8080/tcp) [...]
output is : The error was: 'tcp' is undefined
when I put on line port : [...] default (8080) [...]
output is : improper port format (missing protocol?)
When I precise in my foo_setted_port_var 8080/tcp it works. However I really want to set my port by default in my task code before in my var file.
How to fix it?

According providing default values and firewalld module – Manage arbitrary ports/services with firewalld the syntax should be
# Add port in firewalld
- name: Open port in firewalld
firewalld:
port: "{{ foo_setted_port_var | default('8080/tcp') }}"
state: enabled
permanent: yes
immediate: yes
zone: "{{ setted_firewalldzone_var | default('public') }}"
Without the quotes (') the term tcp or public would be interpreted as variable name and tried to be looked up for content.
You can see this from the error message
'tcp' is undefined
and observe the behavior with the following test.
---
- hosts: localhost
become: false
gather_facts: false
vars:
PUBLIC: "public"
tasks:
- name: Show values
debug:
msg:
- "{{ TXT | default(PUBLIC) }}"
- "{{ TXT | default('PUBLIC') }}"
resulting into an output of
TASK [Show values] ******
ok: [localhost] =>
msg:
- public
- PUBLIC
The other error message
improper port format
relates to Parameter: port which needs to be string and not int.

Related

How can I pass a varialbe set by the set_fact module to the Jinja2 template?

I have a role to setup NATS cluster,, I've used host_vars to define which node is the master node like below:
is_master: true
Then in the setup-nats.yml task, I used the following to extract the master node's IP address based on the host_var I've set and then used it as a variable for the Jinja2 template, however, the variable doesn't get passed down to the template and I get the `variable 'master_ip' is undefined.
- name: Set master IP
set_fact:
set_master_ip: "{{ ansible_facts['default_ipv4']['address'] }}"
cacheable: yes
when: is_master
- name: debug
debug:
msg: "{{ set_master_ip }}"
run_once: true
- name: generate nats-server.conf for the slave nodes
template:
src: nats-server-slave.conf.j2
dest: /etc/nats-server.conf
owner: nats
group: nats
mode: 0644
when:
- is_master == false
vars:
master_ip: "{{ set_master_ip }}"
notify: nats-server
The variable is used like below in the Jinja2 template:
routes = [
nats-route://ruser:{{ nats_server_password }}#{{ master_ip }}:6222
]
}
Questions:
Is this approach according to the best practices?
What is the correct way of doing the above so the variable is passed down to the template?
Test Output:
I'm using Molecule to test my Ansible and even though in the debug task the IP address is visible, it doesn't get passed down to the template:
TASK [nats : Set master IP] ****************************************************
ok: [target1]
skipping: [target2]
skipping: [target3]
TASK [nats : debug] ************************************************************
ok: [target1] =>
msg: 10.0.2.15
TASK [nats : generate nats-server.conf for the slave nodes] ********************
skipping: [target1]
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.errors.AnsibleUndefinedVariable: {{ set_master_ip }}: 'set_master_ip' is undefined
fatal: [target2]: FAILED! => changed=false
msg: 'AnsibleUndefinedVariable: {{ set_master_ip }}: ''set_master_ip'' is undefined'
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.errors.AnsibleUndefinedVariable: {{ set_master_ip }}: 'set_master_ip' is undefined
fatal: [target3]: FAILED! => changed=false
msg: 'AnsibleUndefinedVariable: {{ set_master_ip }}: ''set_master_ip'' is undefined'
Any help is appreciated, thanks in advance.
UPDATE: I suspect the issue has something to do with the variable scope being in the host context but cannot find a way to fix it ( I might be wrong though).
Far from being best practice IMO but answering your direct question. Your problem is not passing the variable to your template but the fact it is not assigned to all hosts in your play loop (and hence is undefined on any non master node). The following (untested) addresses that issue keeping the same task structure.
- name: Set master IP for all nodes
ansible.builtin.set_fact:
master_ip: "{{ hostvars | dict2items | map(attribute='value'
| selectattr('is_master', 'defined') | selectattr('is_master')
| map(attribute='ansible_facts.default_ipv4.address') | first }}"
cacheable: yes
run_once: true
- name: Show calculated master IP (making sure it is assigned everywhere)
ansible.builtin.debug:
msg: "{{ master_ip }}"
- name: generate nats-server.conf for the slave nodes
ansible.builtin.template:
src: nats-server-slave.conf.j2
dest: /etc/nats-server.conf
owner: nats
group: nats
mode: 0644
when: not is_master | bool
notify: nats-server
Ideas for enhancement (non exhaustive):
Select your master based on a group membership in the inventory rather than on a host attribute. This makes gathering the ip easier (e.g. master_ip: "{{ hostvars[groups.master | first].ansible_facts.default_ipv4.address }}"
Set the ip as a play var or directly inside the inventory for the node group rather than in a set_fact task.

Ansible: "unknown type long" when updating Private DNS Zone in Azure

I try to update serial number for a zone in Private DNS in Azure. To do that, I run Ansible code:
- name: Increment DNS serial
azure_rm_dnsrecordset:
resource_group: "{{ my_rg }}"
zone_name: "{{ my_domain }}"
relative_name: "#"
record_type: "SOA"
records:
- serial_number: "{{ new_serial }}"
register: dns_update
until: dns_update is succeeded
Ansible however fails with type mismatch error:
The full traceback is:
fatal: [localhost]: FAILED! => {
"changed": false,
"invocation": {
"module_args": {
...
"record_mode": "purge",
"record_type": "SOA",
"records": [
{
"serial_number": "2"
}
],
"relative_name": "#",
"resource_group": "my-rg",
"state": "present",
...
}
},
"msg": "implementation error: unknown type long requested for serial_number"
}
The error message is a bit confusing: should I understand it as Ansible trying to send 2 as long while Azure API expects string, or vice versa, Azure expects long but Ansible sends string?
Which type conversion should I apply?
Except above issue, is this proper way to update serial?
I bypassed this by switching from azure_rm_dnsrecordset Ansible module to az call, replacing the template as below:
- name: Increment DNS serial
command: >
az network private-dns record-set soa update
--resource-group "{{ my_rg }}"
--zone-name "{{ my_domain }}"
--serial-number "{{ new_serial }}"
This is accepted by Azure at least.
It does not update serial for me however. I can update other SOA paramters like refresh time or minimum TTL, but not the serial number; but this is different issue.

Skip current host and print the rest in a config file ansible

Folks, I'm trying to achieve the below while running my task to edit an XML
Requirement:
Skip current host in which config file is created and print the rest of host from the inventory group to the config file
<ManagerNode ip = "**node IP**" port = "**node port**"/> <haNodes>IP2:port,IP3:port</haNodes> <!-- Comma Seperated Node IPs with port, ,Except the Same Node -->
Can anyone help with achieving this goal?
Q: "Skip current host ... and print the rest of hosts from the inventory group."
A: Create a list of all IPs and use filter difference to remove the current IP. For example the inventory
shell> cat hosts
[ha]
test_01 IP=10.1.0.11
test_02 IP=10.1.0.12
test_03 IP=10.1.0.13
[ha:vars]
port=4567
and the playbook
shell> cat playbook.yml
- hosts: ha
tasks:
- set_fact:
all_IP: "{{ groups.ha|map('extract', hostvars, 'IP')|list }}"
run_once: true
- debug:
msg: "{{ all_IP|difference([IP])|
product([port])|
map('join', ':')|
list }}"
give (abridged)
shell> ansible-playbook -i hosts playbook.yml
ok: [test_01] =>
msg:
- 10.1.0.12:4567
- 10.1.0.13:4567
ok: [test_02] =>
msg:
- 10.1.0.11:4567
- 10.1.0.13:4567
ok: [test_03] =>
msg:
- 10.1.0.11:4567
- 10.1.0.12:4567
Limit the play to test_01 gives abridged
shell> ansible-playbook -i hosts -l test_01 playbook.yml
ok: [test_01] =>
msg:
- 10.1.0.12:4567
- 10.1.0.13:4567
defined a variable
tg_hosts: : "{{ groups['tgzone']|map('extract',hostvars,'ansible_host')|list }}"
used template as:
{{ tg_hosts | difference([ansible_host])| list | join(':port,') + ':port' }}

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

Ansible loops for assigning sequential integers as hostnames

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.

Resources