Add zero if the number of digits is less than three - ansible

I have baremetal servers where I want to set a specific port for the serial console via proxying, and I decided to make the port from the last octet of the ip, but sometimes the servers have two digits in the last octet, and in such a situation I want zero added for them.
Example:
---
- name: Test
hosts: test
gather_facts: no
tasks:
- name: IPV4 address lookup for node
set_fact:
ip: "{{ lookup('community.general.dig', '{{ inventory_hostname }}-ipmi.mydomain.com') }}"
delegate_to: localhost
- name: Set port for serial console
debug:
msg: "Console port will set 48{{ ip.split('.')[3] }}"
Output:
TASK [IPV4 address lookup for node] ************************************************************************************************************************************************************************
ok: [hostA] => {
"ansible_facts": {
"ip": "10.100.100.25"
},
"changed": false
}
ok: [hostB] => {
"ansible_facts": {
"ip": "10.101.100.203"
},
"changed": false
}
TASK [Set port for serial console] *************************************************************************************************************************************************************************
ok: [hostA] => {
"msg": "Console port will set 4825"
}
ok: [hostB] => {
"msg": "Console port will set 48203"
}
The problem is that I also need to set the port after 48K for hostA. Can this be done with Ansible?

Use format. Fro example, the playbook
- hosts: localhost
vars:
ip:
- 10.100.100.2
- 10.100.100.25
- 10.101.100.203
prefix: 48
tasks:
- debug:
msg: "{{ item }}:{{ prefix }}{{ console_port }}"
loop: "{{ ip }}"
vars:
console_port: "{{ '%03d'|format(item.split('.')|last|int) }}"
gives (abridged)
msg: 10.100.100.2:48002
msg: 10.100.100.25:48025
msg: 10.101.100.203:48203

You can use a jinja if/else loop to set the value according to your desired logic. You will notice in one case we prefix the IP part with "48", while in the other with "480":
using a set_fact task to calculate the port value:
- name: set the port with set_fact
set_fact:
console_port: "{% if ip.split('.')[3] | length == 3 %}48{{ ip.split('.')[3] }}{% elif ip.split('.')[3] | length == 2 %}480{{ ip.split('.')[3] }}{% endif %}"
Please note the current logic works only for 2 or 3 digits length, as you described it.

Related

Ansible regex replace in variable to cisco interface names

I'm currently working with a script to create interface descriptions based on CDP neighbor info, but it's placing the full names e.g., GigabitEthernet1/1/1, HundredGigabitEthernet1/1/1.
My regex is weak, but I would like to do a regex replace to keep only the first 3 chars of the interface name.
I think a pattern like (dredGigatbitEthernet|abitEthernet|ntyGigabitEthernet|etc) should work, but not sure how to put that into the playbook line below to modify the port value
nxos_config:
lines:
- description {{ item.value[0].port }} ON {{ item.value[0].host }}
E.g, I am looking for GigabitEthernet1/1/1 to end up as Gig1/1/1
Here is an example of input data:
{
"FastEthernet1/1/1": [{
"host": "hostname",
"port": "Port 1"
}]
}
Final play to make it correct using ansible net neighbors as the source
Thank you - I updated my play, adjusted for ansible net neighbors
- name: Set Interface description based on CDP/LLDP discovery
hosts: all
gather_facts: yes
connection: network_cli
tasks:
- debug:
msg: "{{ ansible_facts.net_neighbors }}"
- debug:
msg: >-
description
{{
item[0].port
| regex_search('(.{3}).*([0-9]+\/[0-9]+\/[0-9]+)', '\1', '\2')
| join
}}
ON {{ item.value[0].host }}"
loop: "{{ ansible_facts.net_neighbors | dict2items }}"
loop_control:
label: "{{ item.key }}"
Thanks for the input!
Given that you want the three first characters along with the last 3 digits separated by a slash, then the regex (.{3}).*([0-9]+\/[0-9]+\/[0-9]+) should give you two capturing groups containing those two requirements.
In Ansible, you can use regex_search to extract those groups, then join them back, with the help on the join Jinja filter.
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: >-
description
{{
item.key
| regex_search('(.{3}).*([0-9]+\/[0-9]+\/[0-9]+)', '\1', '\2')
| join
}}
ON {{ item.value[0].host }}"
loop: "{{ interfaces | dict2items }}"
loop_control:
label: "{{ item.key }}"
vars:
interfaces:
GigabitEthernet1/1/1:
- port: Port 1
host: example.org
HundredGigabitEthernet1/1/1:
- port: Port 2
host: example.com
This yields:
TASK [debug] ***************************************************************
ok: [localhost] => (item=eth0) =>
msg: description Gig1/1/1 ON example.org"
ok: [localhost] => (item=eth1) =>
msg: description Hun1/1/1 ON example.com"

Customize dynamic inventory

Groups configured in Ansible inventory:
Group_A has 30 servers
Group_B has 40 Servers
Group_C has 15 Servers
I want to take 10 servers from each group and make a new group without editing the inventory manually.
These 10 servers is a variable that can change dynamically. If that works I got another question what if the inventory itself is dynamic
[Group_C]
server-1
server-2
server-3
...
server-10
''' New group created From 3 grouped servers now will be used in a playbook '''
(ansible 2.8.3)
If the inventory is dynamic we don't know the hosts. Let's assume we could choose any of them. Let's create the list of the selected hosts first and then loop add_hosts. With the inventory
[Group_A]
A-0
A-1
..
A-29
[Group_B]
B-0
B-1
..
B-39
[Group_C]
C-0
C-1
..
C-14
the plays below
- name: Create Group_X
hosts: localhost
vars:
no_of_servers: 2
my_groups:
- Group_A
- Group_B
- Group_C
tasks:
- set_fact:
my_list: "{{ my_list|default([]) +
groups[item][0:no_of_servers] }}"
loop: "{{ my_groups }}"
- add_host:
name: "{{ item }}"
groups: Group_X
loop: "{{ my_list }}"
- debug:
msg: "{{ groups['Group_X'] }}"
- name: Use Group_X
hosts: Group_X
gather_facts: false
tasks:
- debug:
msg: "{{ inventory_hostname }} is member of {{ group_names }}"
run_once: true
give
ok: [localhost] => {
"msg": [
"A-0",
"A-1",
"B-0",
"B-1",
"C-0",
"C-1"
]
}
ok: [A-0] => {
"msg": "A-0 is member of [u'Group_A', u'Group_X']"
}
Random choice.
It is possible to make the selection of the hosts random with the simple plugin below
$ cat filter_plugins/list_methods.py
import random
def list_sample(l,n):
return random.sample(l,n)
class FilterModule(object):
def filters(self):
return {
'list_sample' : list_sample
}
With the modification below
- set_fact:
my_list: '{{ my_list|default([]) +
groups[item]|list_sample(no_of_servers) }}'
the plays give for example
ok: [localhost] => {
"msg": [
"A-8",
"A-9",
"B-8",
"B-2",
"C-2",
"C-5"
]
}
ok: [A-8] => {
"msg": "A-8 is member of [u'Group_A', u'Group_X']"
}

Reduce Ansible list of objects to a single string of concatenated object values

I am trying to parse the output of elasticache_facts ansible module, to extract the IPS and the ports of the memcached nodes in a string of the form "addr1: port1 addr2:port2 ..."(I want to store this string in a configmap to be used in an app).
Basically I want to take two fields "address" and "port" from a list of dicts like this:
list1:
- endpoint:
address: "addr1"
port: "port1"
- endpoint:
address: "addr2"
port: "port2"
and to concatenate them like above.
I have a ugly solution that goes like this:
# register the output of the facts to something I know
elasticache_facts:
region: "{{ terraform.region }}"
name: "{{ cluster }}-{{ env }}"
register: elasticache
#declare an empty var in vars file to be used as accumulator
memcache_hosts: ""
# iterate through the list of nodes and append the fields to my string; I will have some extra spaces(separators) but that's ok
set_fact:
memcache_hosts: "{{ memcache_hosts }} {{item.endpoint.address}}:{{item.endpoint.port}}"
with_items: "{{ elasticache.elasticache_clusters[0].cache_nodes}}"
Is there some less ugly way to filter the list to the desired format?
Maybe there is a magic filter I don't know about?
I can also obtain two lists, one with hosts, one with ports, zip them, make a dict out of that, but I found only some ugly to_json and then regex to make it a string.
I am also considering to write a custom filter in python, but seems also overdoing it.
Thanks for the help!
Here are two ways to achieve what you are looking for:
#!/usr/bin/env ansible-playbook
---
- name: Lets munge some data
hosts: localhost
become: false
gather_facts: false
vars:
my_list:
- address: '10.0.0.0'
port: '80'
- address: '10.0.0.1'
port: '88'
tasks:
- name: Quicky and dirty inline jinja2
debug:
msg: "{% for item in my_list %}{{ item.address }}:{{ item.port }}{% if not loop.last %} {% endif %}{% endfor %}"
# Note the to_json | from_json workaround for https://github.com/ansible/ansible/issues/27299
- name: Using JSON Query
vars:
jmes_path: "join(':', [address, port])"
debug:
msg: "{{ my_list | to_json | from_json | map('json_query', jmes_path) | join(' ') }}"
The above outputs:
PLAY [Lets munge some data] **********************************************************************************************************************************
TASK [Quicky and dirty inline jinja2] ************************************************************************************************************************
ok: [localhost] => {
"msg": "10.0.0.0:80 10.0.0.1:88"
}
TASK [Using JSON Query] **************************************************************************************************************************************
ok: [localhost] => {
"msg": "10.0.0.0:80 10.0.0.1:88"
}
PLAY RECAP ***************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

Processing embedded lists inside dict variables in Ansible

I got the example code in Ansible:
- name: testing
hosts: localhost
vars:
svc:
https: ['tcp/443']
app_svc: ['tcp/5543', 'udp/5543', 'tcp/3100']
tasks:
- name: print
debug:
msg: port={{item.key}} value={{item.value}}
with_dict:
- "{{svc}}"
And this outputs to:
ok: [127.0.0.1] => (item=None) => {
"msg": "port=app_svc value=[u'tcp/5543', u'udp/5543', u'tcp/3100']"
}
ok: [127.0.0.1] => (item=None) => {
"msg": "port=https value=[u'tcp/443']"
}
What I would like to achieve is, when there is more than one element in list of values it would split like this:
- name=https, prot=tcp, port=443
- name=app_svc, prot=tcp, port=5543
- name=app_svc, prot=udp, port=5543
- name=app_svc, prot=tcp, port=3100
with_dict stanza only displays me a whole list and I couldn't find a way do do it differently. Is it possible to do it like that without reorganizing the var section? Thanks in advance for input.
To see the syntax errors run
# ansible-lint <YOUR-PLAYBOOK>
Correct syntax should be
- hosts: localhost
gather_facts: no
vars:
svc:
https: ['tcp/443']
app_svc: ['tcp/5543', 'udp/5543', 'tcp/3100']
tasks:
- name: print
debug:
msg: "port={{ item.key }} value={{ item.value }}"
with_dict: "{{ svc }}"
gives
"msg": "port=app_svc value=[u'tcp/5543', u'udp/5543', u'tcp/3100']"
"msg": "port=https value=[u'tcp/443']"
Change the loop
- name: print
debug:
msg: "name={{ item.0.key }},
prot={{ item.1.split('/')[0] }},
port={{ item.1.split('/')[1] }}"
loop: "{{ lookup('subelements', svc|dict2items, 'value') }}"
to get the format
"msg": "name=app_svc, prot=tcp, port=5543"
"msg": "name=app_svc, prot=udp, port=5543"
"msg": "name=app_svc, prot=tcp, port=3100"
"msg": "name=https, prot=tcp, port=443"
dict2items is available since version 2.6 . Without "dict2items" transform the data first. See below (not tested).
https:
- {key: 'https', value: ['tcp/443']}
- {key: 'app_svc', value: ['tcp/5543', 'udp/5543', 'tcp/3100']}

How to set an Ansible variable for all plays/hosts?

This question is NOT answered. Someone mentioned environment variables. Can you elaborate on this?
This seems like a simple problem, but not in ansible. It keeps coming up. Especially in error conditions. I need a global variable. One that I can set when processing one host play, then check at a later time with another host. In a nutshell, so I can branch later in the playbook, depending on the variable.
We have no control over custom software installation, but if it is installed, we have to put different software on other machines. To top it off, the installations vary, depending on the VM folder. My kingdom for a global var.
The scope of variables relates ONLY to the current ansible_hostname. Yes, we have group_vars/all.yml as globals, but we can't set them in a play. If I set a variable, no other host's play/task can see it. I understand the scope of variables, but I want to SET a global variable that can be read throughout all playbook plays.
The actual implementation is unimportant but variable access is (important).
My Question: Is there a way to set a variable that can be checked when running a different task on another host? Something like setGlobalSpaceVar(myvar, true)? I know there isn't any such method, but I'm looking for a work-around. Rephrasing: set a variable in one tasks for one host, then later in another task for another host, read that variable.
The only way I can think of is to change a file on the controller, but that seems bogus.
An example
The following relates to oracle backups and our local executable, but I'm keeping it generic. For below - Yes, I can do a run_once, but that won't answer my question. This variable access problem keeps coming up in different contexts.
I have 4 xyz servers. I have 2 programs that need to be executed, but only on 2 different machines. I don't know which. The settings may be change for different VM environments.
Our programOne is run on the server that has a drive E. I can find which server has drive E using ansible and do the play accordingly whenever I set a variable (driveE_machine). It only applies to that host. For that, the other 3 machines won't have driveE_machine set.
In a later play, I need to execute another program on ONLY one of the other 3 machines. That means I need to set a variable that can be read by the other 2 hosts that didn't run the 2nd program.
I'm not sure how to do it.
Inventory file:
[xyz]
serverxyz[1:4].private.mystuff
Playbook example:
---
- name: stackoverflow variable question
hosts: xyz
gather_facts: no
serial: 1
tasks:
- name: find out who has drive E
win_shell: dir e:\
register: adminPage
ignore_errors: true
# This sets a variable that can only be read for that host
- name: set fact driveE_machine when rc is 0
set_fact:
driveE_machine: "{{inventory_hostname}}"
when: adminPage.rc == 0
- name: run program 1
include: tasks/program1.yml
when: driveE_machine is defined
# program2.yml executes program2 and needs to set some kind of variable
# so this include can only be executed once for the other 3 machines
# (not one that has driveE_machine defined and ???
- name: run program 2
include: tasks/program2.yml
when: driveE_machine is undefined and ???
# please don't say run_once: true - that won't solve my variable access question
Is there a way to set a variable that can be checked when running a task on another host?
No sure what you actually want, but you can set a fact for every host in a play with a single looped task (some simulation of global variable):
playbook.yml
---
- hosts: mytest
gather_facts: no
vars:
tasks:
# Set myvar fact for every host in a play
- set_fact:
myvar: "{{ inventory_hostname }}"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
# Ensure that myvar is a name of the first host
- debug:
msg: "{{ myvar }}"
hosts
[mytest]
aaa ansible_connection=local
bbb ansible_connection=local
ccc ansible_connection=local
result
PLAY [mytest] ******************
META: ran handlers
TASK [set_fact] ******************
ok: [aaa -> aaa] => (item=aaa) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "aaa"}
ok: [aaa -> bbb] => (item=bbb) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "bbb"}
ok: [aaa -> ccc] => (item=ccc) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "ccc"}
TASK [debug] ******************
ok: [aaa] => {
"msg": "aaa"
}
ok: [bbb] => {
"msg": "aaa"
}
ok: [ccc] => {
"msg": "aaa"
}
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#fact-caching
As shown elsewhere in the docs, it is possible for one server to reference variables about another, like so:
{{ hostvars['asdf.example.com']['ansible_os_family'] }}
This even applies to variables set dynamically in playbooks.
This answer doesn't pre-suppose your hostnames, nor how many hosts have a "drive E:". It will select the first one that is reachable that also has a "drive E:". I have no windows boxes, so I fake it with a random coin toss for whether a host does or doesn't; you can of course use your original win_shell task, which I've commented out.
---
- hosts: all
gather_facts: no
# serial: 1
tasks:
# - name: find out who has drive E
# win_shell: dir e:\
# register: adminPage
# ignore_errors: true
- name: "Fake finding hosts with drive E:."
# I don't have hosts with "drive E:", so fake it.
shell: |
if [ $RANDOM -gt 10000 ] ; then
exit 1
else
exit 0
fi
args:
executable: /bin/bash
register: adminPage
failed_when: false
ignore_errors: true
- name: "Dict of hosts with E: drives."
run_once: yes
set_fact:
driveE_status: "{{ dict(ansible_play_hosts_all |
zip(ansible_play_hosts_all |
map('extract', hostvars, ['adminPage', 'rc'] ) | list
))
}}"
- name: "List of hosts with E: drives."
run_once: yes
set_fact:
driveE_havers: "{%- set foo=[] -%}
{%- for dE_s in driveE_status -%}
{%- if driveE_status[dE_s] == 0 -%}
{%- set _ = foo.append( dE_s ) -%}
{%- endif -%}
{%- endfor -%}{{ foo|list }}"
- name: "First host with an E: drive."
run_once: yes
set_fact:
driveE_first: "{%- set foo=[] -%}
{%- for dE_s in driveE_status -%}
{%- if driveE_status[dE_s] == 0 -%}
{%- set _ = foo.append( dE_s ) -%}
{%- endif -%}
{%- endfor -%}{{ foo|list|first }}"
- name: Show me.
run_once: yes
debug:
msg:
- "driveE_status: {{ driveE_status }}"
- "driveE_havers: {{ driveE_havers }}"
- "driveE_first: {{ driveE_first }}"
It's working for me, you can directly use register var no need to use set_fact.
---
- hosts: manager
tasks:
- name: dir name
shell: cd /tmp && pwd
register: homedir
when: "'manager' in group_names"
- hosts: all
tasks:
- name: create a test file
shell: touch "{{hostvars['manager.example.io'['homedir'].stdout}}/t1.txt"
i have used "set_fact" module in the ansible tasks prompt menu, which is helped to pass user input into all the inventory hosts.
#MONITORING PLAYBOOK
- hosts: all
gather_facts: yes
become: yes
tasks:
- pause:
prompt: "\n\nWhich monitoring you want to perform?\n\n--------------------------------------\n\n1. Memory Utilization:\n2. CPU Utilization:\n3. Filesystem Utili
zation:\n4. Exist from Playbook: \n5. Fetch Nmon Report \n \nPlease select option: \n--------------------------------------\n"
register: menu
- set_fact:
option: "{{ menu.user_input }}"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
#1 Memory Monitoring
- pause:
prompt: "\n-------------------------------------- \n Enter monitoring Incident Number = "
register: incident_number_result
when: option == "1"
- name: Standardize incident_number variable
set_fact:
incident_number: "{{ incident_number_result.user_input }}"
when: option == "1"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
ansible playbook result is
[ansibusr#ansiblemaster monitoring]$ ansible-playbook monitoring.yml
PLAY [all] **************************************************************************
TASK [Gathering Facts] **************************************************************************
ok: [node2]
ok: [node1]
[pause]
Which monitoring you want to perform?
--------------------------------------
1. Memory Utilization:
2. CPU Utilization:
3. Filesystem Utilization:
4. Exist from Playbook:
5. Fetch Nmon Report
Please select option:
--------------------------------------
:
TASK [pause] **************************************************************************
ok: [node1]
TASK [set_fact] **************************************************************************
ok: [node1 -> node1] => (item=node1)
ok: [node1 -> node2] => (item=node2)
[pause]
--------------------------------------
Enter monitoring Incident Number = :
INC123456
TASK [pause] **************************************************************************
ok: [node1]
TASK [Standardize incident_number variable] ****************************************************************************************************************************
ok: [node1 -> node1] => (item=node1)
ok: [node1 -> node2] => (item=node2)

Resources