Ansible vmware_vm_shell SED command adding extra characters - shell

I am having trouble getting this correct.
I gather the vm facts and have a test debug that gives me the correct mac address for the vm. However when I add the query to a sed command I get extra characters added.
this works
- name: Get MAC address of VMs to add to eth0 configuration
debug:
msg: "{{ vm_guest_facts.results | json_query(s_query) }}"
vars:
s_query: "[?instance.hw_name == '{{ item }}'].instance.hw_eth0.macaddress"
with_items: "{{ inventory_hostname }}"
output
ok: [server1] => (item=server1) => {
"msg": [
"00:50:56:80:e0:a1"
]
this fails
- name: fix network phase 2 - replace template MAC
vars:
s_query: "[?instance.hw_name == '{{ item }}'].instance.hw_eth0.macaddress"
vmware_vm_shell:
hostname: '{{ deploy_vsphere_host }}'
username: '{{ deploy_vsphere_user }}'
password: '{{ deploy_vsphere_password }}'
datacenter: "{{ vsphere_datacenter }}"
validate_certs: no
vm_id: "{{ item }}"
vm_username: xxx
vm_password: xxx
vm_shell: '/bin/sed'
vm_shell_args: " -i.bak 's/^HWADDR.*/HWADDR={{ vm_guest_facts.results | json_query(s_query) }}/' /etc/sysconfig/network-scripts/ifcfg-eth0"
with_items:
- "{{ inventory_hostname }}"
delegate_to: localhost
output (snipped)
"vm_id": "server1",
"vm_id_type": "vm_name",
"vm_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"vm_shell": "/bin/sed",
"vm_shell_args": " -i.bak 's/^HWADDR.*/HWADDR=[u'00:50:56:80:e0:a1']/' /etc/sysconfig/network-scripts/ifcfg-eth0",
"vm_shell_cwd": null,
"vm_shell_env": null,
"vm_username": "xxx",
"wait_for_process": false
The mac address in the last line is getting the extra [u at the beginning and ] at the end.
Is there a way of fixing this or could someone help me with a sed line to remove those extra characters in an extra step.
Thanks,
Kane.

Any task that is with_items always has a list of results, and you can see that in the msg: output containing the python list characters [ and ]. Thus, vm_guest_facts.results | json_query() needs either [0] or | first appended to it in order to resolve it to one thing

I gave up tried [0] in multiple places and multiple times.
Here is the cheat / dirty fix for this particular issue:
" -i.bak 's/^HWADDR.*/HWADDR={{ vm_guest_facts.results | json_query(s_query)| regex_replace(']') | regex_replace('\\[u') }}/' /etc/sysconfig/network-scripts/ifcfg-eth0"

Related

List name server from resolv.conf with hostname in one line per host

I need to get the DNS server(s) from my network, I tried using:
- hosts: localhost
gather_facts: no
tasks:
- name: check resolv.conf exists
stat:
path: /etc/resolv.conf
register: resolv_conf
- name: check nameservers list in resolv.conf
debug:
msg: "{{ contents }}"
vars:
contents: "{{ lookup('file', '/etc/resolv.conf') | regex_findall('\\s*nameserver\\s*(.*)') }}"
when: resolv_conf.stat.exists == True
But this does not quite gives the result I need.
Will it be possible to write a playbook in such a way that the result looks like the below?
hostname;dns1;dns2;dnsN
The declaration below gives the list of nameservers
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
You can join the hostname and the items on the list
msg: "{{ inventory_hostname }};{{ nameservers|join(';') }}"
Notes
Example of a complete playbook for testing
- hosts: localhost
vars:
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('split', ' ')|
map('last')|list }}"
tasks:
- debug:
var: nameservers
- debug:
msg: |
{{ inventory_hostname }};{{ nameservers|join(';') }}
The simplified declaration below works fine if there is no nameserver.* in the comments
nameservers: "{{ lookup('file', '/etc/resolv.conf')|
regex_findall('\\s*nameserver\\s*(.*)') }}"
Unfortunately, the Linux default file /etc/resolv.conf contains the comment:
| # run "systemd-resolve --status" to see details about the actual nameservers.
This regex will match nameservers.
nameservers:
- s.
You can solve this problem by putting at least one space behind the keyword nameserver.
regex_findall('\\s*nameserver\\s+(.*)') }}"
However, this won't help if there is the keyword nameserver in the comment.
Q: "No filter named 'split'"
A: There is no filter split in Ansible less than 2.11. Use regex_replace instead
nameservers: "{{ lookup('file', '/etc/resolv.conf').splitlines()|
select('match', '^nameserver.*$')|
map('regex_replace', '^(.*) (.*)$', '\\2')|list }}"
Since your regex_findall already creates you a list with all DNS servers, you just need to add the hostname to that list and join the whole list with a semicolon.
- name: check nameservers list in resolv.conf
debug:
msg: >-
{{
(
[ ansible_hostname ] +
lookup('file', '/etc/resolv.conf', errors='ignore')
| regex_findall('\s*nameserver\s*(.*)')
) | join(';')
}}
Which will result in something like (b176263884e6 being the actual hostname of a container):
TASK [check nameservers list in resolv.conf] *****************************
ok: [localhost] =>
msg: b176263884e6;1.1.1.1;4.4.4.4;8.8.8.8
Note that you don't even need the stat task, as you can ignore errors of the lookup with errors='ignore'.
This will, then, give you only the hostname, along with a warning:
TASK [check nameservers list in resolv.conf] *****************************
[WARNING]: Unable to find '/etc/resolv.conf' in expected paths
(use -vvvvv to see paths)
ok: [localhost] =>
msg: b176263884e6

Ansible trim string

I'm trying write role to install MySql 8, and get problem with this:
- name: Extract root password from logs into {{ mysql_root_old_password }} variable
ansible.builtin.slurp:
src: "{{ mysql_logfile_path }}"
register: mysql_root_old_password
#when: "'mysql' in ansible_facts.packages"
- name: Extract root password from logs into {{ mysql_root_old_password }} variable
set_fact:
mysql_root_old_password: "{{ mysql_root_old_password.content | b64decode | regex_findall('generated for root#localhost: (.*)$', 'multiline=true') }}"
#when: "'mysqld' in ansible_facts.packages"
- name: Get Server template
ansible.builtin.template:
src: "{{ item.name }}.j2"
dest: "{{ item.path }}"
loop:
- { name: "my.cnf", path: "/root/.my.cnf" }
notify:
- Restart mysqld
on the .my.cnf I get password with quotes and brackets:
[client]
user=root
password=['th6k(gZeJSt4']
How to trim that?
What I try:
- name: trim password
set_fact:
mysql_root_old_password2: "{{ mysql_root_old_password | regex_findall('[a-zA-Z0-9,()!##$%^&*]{12}')}}"
Thanks.
The result of regex_findall is a list because there might be more matches. Take the last item
- set_fact:
mysql_root_old_password: "{{ mysql_root_old_password.content|
b64decode|
regex_findall('generated for root#localhost: (.*)$', 'multiline=true')|
last }}"
From your description
on the .my.cnf I get password with quotes and brackets ... How to trim that
I understand that you like to read a INI file like my.cnf.ini
[client]
user=root
password=['A1234567890B']
where the value of the key password looks like a list with one element in YAML and the structure doesn't change, but you are interested in the value without leading and trailing square brackets and single quotes only.
To do so there are several possibilities.
Via Ansible Lookup plugins
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Extract root password from INI file
debug:
msg: "{{ lookup('ini', 'password section=client file=my.cnf.ini') }}"
register: result
- name: Show result with type
debug:
msg:
- "{{ result.msg }}"
- "result.msg is of type {{ result.msg | type_debug }}"
- "Show password only {{ result.msg[0] }}" # the first list element
Such approach will work on the Control Node.
Like all templating, lookups execute and are evaluated on the Ansible control machine.
Further Q&A
How to read a line from a file into an Ansible variable
What is the difference between .ini and .conf?
Further Documentation
ini lookup – read data from an INI file
Via Ansible shell module, sed and cut.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Extract root password from INI file
shell:
cmd: echo $(sed -n 's/^password=//p' my.cnf.ini | cut -d "'" -f 2)
register: result
- name: Show result
debug:
msg: "{{ result.stdout }}"
Please take note regarding "I get password with quotes and brackets ... ['<pass>'] ... How to trim that?" that from perspective of the SQL service, .cnf file, [' and '] are actually part of the password!
- name: Server template
template:
src: "my.cnf.ini"
dest: "/root/.my.cnf"
If that is correct they will be necessary for proper function of the service.

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"

Playbook cannot find "{{ item }}" from loop variable inside role

The goal is to remove one or more snapshots to specified hosts. First and second role is working.
The issue is the third role.
If name of snapshot is defined, use second role.
If no name of snapshot is defined use the third role with the names from variable "days" and loop it trough the list. The role will then remove snapshots with those names, if they exist.
My playbook:
- name: Managing Snapshot(s)
hosts: "{{ target }}"
become: false
gather_facts: no
roles:
- confirm
- { role: remove-snapshot, snapshot_state: "{{ sss | default('absent') }}", snapshot_name: "{{ ssn }}", when: ssn is defined }
- { role: remove-snapshot, snapshot_state: "{{ sss | default('absent') }}", snapshot_name: "{{ item }}", loop: "{{ days }}", when: ssn is undefined }
Command i run for third role:
ansible-playbook snapshot-remove.yml --ask-vault-pass -e target=all -e sss=absent
Task role is running:
- name: remove Snapshot
vars:
ansible_python_interpreter: /usr/bin/python3
vmware_guest_snapshot:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
datacenter: "{{ vcenter_datacenter }}"
folder: "/vm"
name: "{{ vm_name | default(inventory_hostname) }}"
validate_certs: false
state: "{{ snapshot_state | default('absent') }}"
snapshot_name: "{{ snapshot_name | default('Using_Default_Text') }}"
description: "{{ lookup('env','USER') }} {{ lookup('pipe', 'date +\"%F\"') }}"
delegate_to: localhost
Variable "days" that is inside role/remove-snapshot/vars/main.yml:
vars:
days:
- lookup('pipe', 'date +\"%F\"')
- lookup('pipe', 'date -d "-1 days" +\"%F\"')
- lookup('pipe', 'date -d "-2 days" +\"%F\"')
- lookup('pipe', 'date -d "-3 days" +\"%F\"')
Error i receive:
fatal: [HOST-MGM]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: {{ item }}: 'item' is undefined\n\nThe error appears to be in '/home/user/linux-patching/ansible/roles/remove-snapshot/tasks/main.yml': line 1, 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: remove Snapshot\n ^ here\n"
}
I have tried using vars, include_vars and vars_files inside third role and received the same error. Creating a new role might solve the issue but i would prefer not creating one more role.
Decided to solve my problem in a different way.
Decided to add date to description of snapshot for when snapshot was created and the name of snapshot will be the default name "OS_Patching".
With this i will be able to remove snapshots created from another playbook whos purpose it to create snapshot and patch the host.
New code:
- name: Managing Snapshot(s)
hosts: "{{ target }}"
become: false
gather_facts: no
roles:
- confirm
- {
role: snapshot,
snapshot_state: "{{ sss | default('absent') }}",
snapshot_name: "{{ ssn | default('OS_Patching') }}"
}
Command:
ansible-playbook snapshot.yml --ask-vault-pass -e target=<hosts> -e sss=<state> -e ssn=<name>
They way i wanted to do was more complicated, this playbook should be easier for managing snapshots.

Ansible get first element from list

Suppose I have the following vars_file:
mappings:
- primary: 1.1.1.1
secondary: 2.2.2.2
- primary: 12.12.12.12
secondary: 11.11.11.11
and hosts file
1.1.1.1
12.12.12.12
5.5.5.5
and the following playbook task
- name: Extract secondary from list
debug:
msg: "{{ (mappings | selectattr('primary', 'search', inventory_hostname) | list | first | default({'secondary':None})).secondary }}"
The current task works and will give empty string when no match are found, but I would like to know if there is a better way/cleaner way of doing it without passing a dictionary to the default constructor.
An option would be to use json_query
- debug:
msg: "{{ mappings | json_query(\"[?primary=='\" + inventory_hostname + \"'].secondary\") }}"
, but selectattr works too
- debug:
msg: "{{ mappings | selectattr('primary', 'equalto', inventory_hostname) | map(attribute='secondary') | list }}"

Resources