Filter out content from Ansible output - linux

I want to filter out ip address alone from the ansible output. When I tried to filter out with the awk command, it failed. Please see my code, output, and required output.
- name: Gather all VMs from a specific folder
community.vmware.vmware_vm_info:
hostname: hostname_local
username: vmwarelogin
password: passwordvmware
folder: "/VMFS/"
validate_certs: False
delegate_to: localhost
register: vm_info
- debug:
var: ip
vars:
ip: "{{ vm_info.virtual_machines|
selectattr('guest_name', 'eq', 'My-Machine')|
map(attribute='ip_address')|first }}"
register: ip
- name: add ip
shell: echo "{{ip}}"| awk '{print $2}'
Output after running the above code
{'ip': '192.168.1.32', 'failed': False, 'changed': False}
Expected output is
192.168.1.32
Any help would be appreciated to use this IP address as a variable for other places in the same playbook

If I understand what you want to convey using YAML tag you are implying
{'ip': '192.168.1.32', 'failed': False, 'changed': False}
should be treated as YAML file. If this is case you should if allowed use YAML parser, if you want parser which can be used as part of pipelined command then I suggest trying yq. If you are forced to use AWK for this then be warned that it is best suited for working with entities which belong to Chomsky Type-3 while YAML is not Chomsky Type-3 contraption. Anyway it might suffice for your case. I would propose following heurestic: grab 1st thing which looks like IP address in decimal notation, this could be done following way in GNU AWK let say you are using standard input to deliver
{'ip': '192.168.1.32', 'failed': False, 'changed': False}
then
awk 'BEGIN{FPAT="[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}"}{print $1}'
output
192.168.1.32
Explanation: I use field pattern (FPAT) to inform GNU AWK that field is 1 to 3 digits followed by literal dot followed by 1 to 3 digits followed by dot followed by 1 to 3 digits followed by dot followed by 1 to 3 digits. Note that this does consider solely IP addresses in decimal notation and could also give false positives (e.g. it will find 999.999.999.999) but hopefully your input is not such malicious. I print 1st such field found for each line.
(tested in gawk 4.2.1)

Regarding how to
... use this IP address as a variable for other places in the same playbook?
and reviewing the provided example it looks that you can simplify your case as follows:
---
- hosts: localhost
become: false
gather_facts: false
vars:
IP: "192.168.2.1"
tasks:
- name: Show vars
debug:
var: IP
register: result
- name: Show result
debug:
var: result
- name: Show IP
debug:
msg: "{{ result.IP }}"
Just by using finally echo "{{ ip.ip }}" in your case.
I want to filter out IP address alone from the Ansible output.
There is no need for awk at all as the content is already there and direct accessible.
Expected output is ...
The result would be as required
TASK [Show vars] ***
ok: [localhost] =>
IP: 192.168.2.1
TASK [Show result] ***
ok: [localhost] =>
result:
IP: 192.168.2.1
changed: false
failed: false
TASK [Show IP] ***
ok: [localhost] =>
msg: 192.168.2.1
Further Documentation
Registering variables
Dictionary variables
debug module – Print statements during execution

Related

Validates arguments passed to ansible-playbook with a defined argument specification

We want to run the following playbook with the yml file - ansible-playbook install_os.yml
The playbook install_os.yml is working well , but now we want to add the validation of the arguments <machine name>,<machine IP>.
As the following:
ansible-playbook install_os.yml --limit RHEL01,73.22.3.44
From my point both arguments should be identify as strings (without verify of valid IP) and between <machine name> to <machine IP>, we should set the , separator
So, is it possible to validate the strings? And exit if one of them or both them are not defined?
You can access the limit specified in arguments to ansible-playbook with the help of the special variable ansible_limit.
From there on, you can assert the --limit values based on your business needs.
For example:
- hosts: all
gather_facts: no
tasks:
- assert:
that:
## We have exactly one comma, separating two hosts
- ansible_limit | split(',') | length == 2
## We have a string before the comma
- (ansible_limit | split(',')).0 is string
## We have a non-empty string before the comma
- (ansible_limit | split(',')).0 | length > 0
## We have a string after the comma
- (ansible_limit | split(',')).1 is string
## We have a non-empty string after the comma
- (ansible_limit | split(',')).1 | length > 0
## 'all', which has a wildcard meaning,
## is not one of the two hosts separated by the comma
- "'all' not in ansible_limit | split(',')"
## We do not have any character having a special meaning in the limit
## see: https://docs.ansible.com/ansible/latest/user_guide/intro_patterns.html#common-patterns
- "'#' not in ansible_limit"
- "':' not in ansible_limit"
- "'!' not in ansible_limit"
- "'&' not in ansible_limit"
- "'*' not in ansible_limit"
run_once: true
This would probably limit it to the use case you want.
This said, mind that --limit is an existing flag with its own behaviour, so, based on what you are aiming for, you could also be better with an extra parameter passed in command line.

Ansible search and replace without regex only string literals to avoid escaping

i like to search and replace string which makes me to escape allot of if i use the regexp search / replace :
in the mystuff.txt (soory about the blah blah but is allot of personal text ) i have allot of string but i like to search and replace those:
Blah Blah ${DBNname.db_name} Blah Blah
Blah Blah ${DBNname.db_name} Blah Blah
Blah Blah ${DBName.ip}
In ansible playbook i have this :
- name: search
replace:
path: "foo/mystuff.txt"
regexp: "{{ item.split('|')[0] }}"
replace: "{{ item.split('|')[1] }}"
with_items: "{{ config_list }}"
And in command line i passing :
config_list =['\\\${DBNname\\\.db_name}|MySql','\\\${DBName\\\.ip}|127.0.0.1']
and this is working fine .
As you can see i have allot of escaping going on i like to avoid it as the paramters can be long.
Are you required to pass in your config_list variable on the command line? Because if you put it in a file instead, the escaping is much simpler. For example, if I place the following content in config_list.yml:
config_list:
- search: '\${DBNname\.db_name}'
replace: 'MySql'
- search: '\${DBNname\.ip}'
replace: '127.0.0.1'
I can run this playbook:
- hosts: localhost
gather_facts: false
tasks:
- replace:
path: mystuff.txt
regexp: '{{ item.search }}'
replace: '{{ item.replace }}'
loop: '{{ config_list }}'
Like this:
ansible-playbook playbook.yml -e #config_list.yml
And it transforms this:
${DBNname.db_name}
${DBNname.ip}
Into:
MySql
127.0.0.1
If you really need to pass parameters on the command line using the format shown in your question, this seems
to work:
$ ansible-playbook playbook.yml -e '{"config_list": ["\\${DBNname\\.db_name}|MySql", "\\${DBNname\\.ip}|127.0.0.1"]}'
If you write the playbook like this:
- hosts: localhost
gather_facts: false
tasks:
- replace:
path: mystuff.txt
regexp: '{{ x.0 }}'
replace: '{{ x.1 }}'
vars:
x: "{{ item.split('|') }}"
loop: '{{ config_list }}'
The above playbook also works with this input:
ansible-playbook playbook.yml -e 'config_list="[\"\\${DBNname\\.db_name}|MySql\", \"\\${DBNname\\.ip}|127.0.0.1\"]"'

How can I add new line "\n" character to Ansible variable

How can I add new line "\n" characters to Ansible variable mailbody
I set the below variable to ansible's mail module body attribute.
- set_fact:
mailbody: "{{ mailbody | default('') + 'PROFILE_NAME:' + PROFILE_NAME ~ '\n\nSERVER_NAME:' + SERVER_NAME ~ '\n\nNODE_NAME:' + NODE_NAME ~ '\n\n\n\n' }}"
But the body of the email has '\n\n' printed instead of new lines.
Can you please suggest what changes are needed?
Please try something like this :
as stated on the documentation I share in comment "The difference between single quotes and double quotes is that in double quotes you can use escapes : foo: "a \t TAB and a \n NEWLINE"
so
1.first thing to try:
mailbody: "{{ mailbody | default('') + 'PROFILE_NAME:' + PROFILE_NAME ~}} {{ 'SERVER_NAME:' + SERVER_NAME ~ }}\n\n{{'NODE_NAME:' + NODE_NAME ~ }}\n\n\n\n"
2.if that doesn help then:
mailbody: >
"{{ mailbody | default('') + 'PROFILE_NAME:' + PROFILE_NAME ~}}"
"{{ 'SERVER_NAME:' + SERVER_NAME ~ }}"
"{{'NODE_NAME:' + NODE_NAME ~ }}"
3.if still won't work:
mailbody: |
"{{ mailbody | default('') + 'PROFILE_NAME:' + PROFILE_NAME ~}}"
"{{ 'SERVER_NAME:' + SERVER_NAME ~ }}"
"{{'NODE_NAME:' + NODE_NAME ~ }}"
4.if this still isn't working try removing double quotes to this last 2 and 3.
I created an example playbook using the 4th suggestion from MikZuit's answer with the literal block syntax and it works like a charm. It sets the variable with set_fact which I tried to both debug and copy (print to a file). This is it:
- name: variable test
hosts: 127.0.0.1
connection: local
vars:
mailbody: "I am a mail body."
PROFILE_NAME: "my profile"
SERVER_NAME: "myserver.example.org"
NODE_NAME: "I feel on edge today."
tasks:
- name: variable test | set variable
ansible.builtin.set_fact:
mailbody: |
{{ mailbody | default('') }}
PROFILE_NAME: {{ PROFILE_NAME }}
SERVER_NAME: {{ SERVER_NAME }}
NODE_NAME: {{ NODE_NAME }}
- name: variable test | print variable to stdout
ansible.builtin.debug:
var: mailbody
- name: variable test | print variable to file
ansible.builtin.copy:
content: "{{ mailbody }}"
dest: "/tmp/mailbody.txt"
When I run the playbook the debug output looks like this:
ok: [localhost] => {
"mailbody": "I am a mail body.\n\nPROFILE_NAME: my profile\n\nSERVER_NAME: myserver.example.org\n\nNODE_NAME: I feel on edge today.\n"
}
Note that the debug module prints the output as JSON and escapes any special characters, unlike the copy module which, in this playbook, prints the following output to /tmp/mailbody.txt:
I am a mail body.
PROFILE_NAME: my profile
SERVER_NAME: myserver.example.org
NODE_NAME: I feel on edge today.
Some general notes:
Since v2.10 or so, Ansible has been strongly modularized. This means that task names should now be fully qualified collection names (e.g. ansible.builtin.copy instead of copy) in order to avoid possible name collisions with other collections. Also it does not accept any plays or tasks without a name anymore.
When using the literal block syntax, it will truncate trailing newlines (like the empty line after NODE_NAME: {{ NODE_NAME }}) down to at most one. Btw, you don't need any quotes inside these blocks except for literals in evaluated expressions, e.g. {{ mailbody | default('') }}.
Quoting in Ansible works very similar to quoting in Linux shells. Double quotes can evaluate expressions and escaped characters while single quotes pass everything "as is".
Concerning the unexpected end of print statement error in Ashar's comment: the error occurs because the concatenation operator (~) expects a second operand but instead finds closed curly braces which mark the end of the expression. Just delete all the tildes shown in your error message and you should be good to go.

Delete only the first occurence of the line in a file using Ansible

I am using ansible v2.2.1 and I need to remove only the first occurrence of a line in file. So my ansible playbook is as follows.
---
- hosts: localhost
tasks:
- name: read file content
command: cat occurence.txt
register: fc
- debug: var=fc
- debug: msg="{{ item }}"
with_items:
- "{{ fc.stdout_lines }}"
- name: first occurence in file
shell: sed -i '0,/{{ item }}/{/{{ item }}/d;}' occurence.txt
with_items:
- "{{ fc.stdout_lines }}"
register: remove
- debug: var=remove
And occurence.txt file has following content
this is apple
this is apple
this is banana
this is apple
this is orange
How can I delete only the first occurrence of line "this is apple" and leave rest of the lines?
I'm liking Yunnosch's interpretation of the question. An alternative would be this:
sed '0,/^this is apple$/{//d}'
From the beginning of the file to the first occurrence of the apple line, delete only lines matching the previous match.
Find line, when found replace by (initially empty) hold space.
For later occurences, swapping to the identical hold space will basically print unchanged line, even if it, strictly speaking, it is the previous identical line.
Delete in case of (only the first) empty line coming from hold space.
sed "/^this is apple$/{x;/^$/d}"
I solved it as follows by using set_fact variable.
---
- hosts: localhost
tasks:
- name: read file content
command: cat occurence.txt
register: fc
- debug: var=fc
- debug: msg="{{ item }}"
with_items:
- "{{ fc.stdout_lines }}"
- set_fact: value="this is apple"
- debug: var=value
- name: first occurrence in file
shell: sed -i '0,/{{ value }}/{/{{ value }}/d;}' occurence.txt
register: remove
- debug: var=remove

How to remove the line breaker character '\n' from the result of lookup() module in Ansible?

I am using [file lookup] which reads the whole file and stores the content in a variable. My play looks something like this:
- name: Store foo.xml contents in a variable
set_fact:
foo_content: "{{ lookup('file', 'foo.xml' ) | replace('\n', '')}}"
So the above code reads the foo.xml file and stores it in the variable, but the problem is when the foo.xml has line breaks in it, it also includes the line break in the variable.
My foo.xml is this file:
<?xml version="1.0" encoding="utf-8"?>
<initialize_param>
<secrets>
<my_secret id="99">3VMjII6Hw+pd1zHV5THSI712y421USUS8124487128745812sajfhsakjfasbfvcasvnjasjkvbhasdfasgfsfaj5G8A9+n8CkLxk7Dqu0G8Jclg0eb1A5xeFzR3rrJHrb2GBBa7PJNVx8tFJP3AtF6ek/F/WvlBIs2leX2fq+/bGryKlySuFmbcwBsThmPJC5Z5AwPJgGZx</my_secret>
</secrets>
</initialize_param>
The output removes line break \n but also incudes the tabs \r & \t
I need to got rid of the \n , need to get rid of extra formatting too (\r & \t), Moreover after the replace filter I get the error while firing a DB Update query as
stderr: /bin/sh: 1: cannot open ?xml: No such file
Use the Jinja trim filter:
"{{ lookup('file', 'foo.xml' ) | trim }}"
You can do that with the replace filter?
contents: "{{ lookup('file', '/etc/foo.txt') | replace('\n', '')}}"
You may use the regex_replace filter since the trim doesn't clear other line break characters as you mentioned in the question.
"{{ some_stdout_to_clear | regex_replace('[\\r\\n\\t]+','') }}"

Resources