parsing json data in ansible using shell - ansible

I am unable to read a list using shell module. It's working fo one element in list but fails for two or more elements.
tasks:
- name: Create a List variable and print it
set_fact:
UserRecords:
[
"[3/2/23 9:09:04:013 GMT+5:30] 0000 ApplicationMg A WSVR220I: Application stopped: ActivePackProdTest_war1",
"[3/2/23 8:09:04:013 GMT+5:30] 0000 ApplicationMg A WSVR220I: Application stopped: ActivePackProdTest_war"
]
- name: split list
shell: |
for i in "{{ UserRecords }}"
do
echo $i||awk '{print $2,new_var=$8" "$9" "$10}'|sed 's/[^[:alnum:]:" "]//g'
done
excepted output:
9:09:04:013 Application stopped: ActivePackProdTest_war1
8:09:04:013 Application stopped: ActivePackProdTest_war

Try using Jinja2 to achieve the desired result. Ansible uses Jinja2 templating to enable dynamic expressions and access to variables and facts. Hope this helps.
---
- hosts: 127.0.0.1
gather_facts: false
tasks:
- name: Create a List variable and print it
set_fact:
UserRecords:
[
"[3/2/23 9:09:04:013 GMT+5:30] 0000 ApplicationMg A WSVR220I: Application stopped: ActivePackProdTest_war1",
"[3/2/23 8:09:04:013 GMT+5:30] 0000 ApplicationMg A WSVR220I: Application stopped: ActivePackProdTest_war"
]
delegate_to: 127.0.0.1
- name: split list
debug:
msg: "{{ item.split(' ')[1] }} {{ item.split(':')[-2] }}:{{ item.split(':')[-1] }}"
loop: "{{ UserRecords }}"
delegate_to: 127.0.0.1
...

Related

how to assign in the variable for each server a line of the file with ansible?

I have a file with numbers, what I want the playbook to do is read the first line of the file and save it in the variable var_num for that server, then delete that first line, and the next server I have in inventory does the same. I'm launching it from the AWX. And Iand I don't launch 1 server and then another, a set of servers is launched. I want to launch a group of servers. could you help me? Thank you very much.
number.txt:
1001
1002
1005
1008
playbook.yml
---
- name: get number
hosts:
- server1
- server2
- server3
vars:
var_number:
tasks:
- name: read the number
shell: "head -n1 number.txt"
register: result
delegate_to: localhost
- set_fact:
var_number: "{{ result.stdout_lines }}"
delegate_to: localhost
- name: delete number
shell: "sed -i '1d' number.txt"
args:
warn: false
delegate_to: localhost
what it does:
server1 --> 1001
server2 --> 1001
server3 --> 1001
what I want it to do:
server1 --> 1001
server2 --> 1002
server3 --> 1005
Get the number in the first play and use it in the second play. Set serial: 1 for the first play to serve the hosts one by one. For example, given the inventory
shell> cat hosts
server1
server2
server3
and the file at the controller
shell> cat number.txt
1001
1002
1005
1008
The playbook
- name: get number
hosts: all
gather_facts: false
serial: 1
tasks:
- name: read the number
command: head -n1 number.txt
register: result
delegate_to: localhost
- set_fact:
var_num: "{{ result.stdout }}"
- name: delete the number
command: sed -i '1d' number.txt
delegate_to: localhost
- name: print number
hosts: all
gather_facts: false
tasks:
- debug:
var: var_num
gives (abridged)
PLAY [print number] **************************************************
TASK [debug] *********************************************************
ok: [server1] =>
var_num: '1001'
ok: [server2] =>
var_num: '1002'
ok: [server3] =>
var_num: '1005'
shell> cat number.txt
1008

How do I do the equivalent of combining a with_subelements loop and a with_nested loop in Ansible?

I'm writing a playbook to get a list of services deployed to a Mirantis' UCP/MKE cluster and check on all the Docker workers in that cluster, that each external swarm service port is open.
The playbook makes an API call from localhost to get an extensive JSON object of services deployed to the cluster, which is simplified using jmespath to just name, ID, ports.
As another play, my playbook runs a shell command on each worker in the cluster to obtain a list of open ports.
I'd like to loop through each port for each service and confirm if the port is open on each and every worker node.
My services/ports data object can look like this:
[
{
"ID": "aefgergergergergerg",
"Name": "application1_service",
"Ports": [
[
30950,
"tcp"
],
[
30951,
"tcp"
]
]
},
{
"ID": "sdfthtrhrthrthrthrthtrh",
"Name": "application2_service",
"Ports": [
[
31190,
"tcp"
]
]
},
...
]
(obtained via an API call and can be simplified with a jmespath query:
'[?Endpoint.Ports].{ ID: ID, Name: Spec.Name, Ports: Endpoint.Ports[?contains(#.PublishMode,`ingress`)].[PublishedPort, PublishMode, Protocol] }'
And my worker's open ports objects look like this:
ok: [worker1] => {
"msg": [
"tcp:31557",
"tcp:31501",
"tcp:31556",
"tcp:31500",
"tcp:30231",
"tcp:30230",
"tcp:30651",
"tcp:30650"
]
}
ok: [worker2] => {
"msg": [
"tcp:31557",
"tcp:31501",
"tcp:31556",
"tcp:31500",
"tcp:30231",
"tcp:30230",
"tcp:30651",
"tcp:30650"
]
}
ok: [worker3] => {
"msg": [
"tcp:31557",
"tcp:31501",
"tcp:31556",
"tcp:31500",
"tcp:30231",
"tcp:30230",
"tcp:30651",
"tcp:30650"
]
}
obtained with
iptables -L DOCKER-INGRESS | awk -F ' {2,}' '($1 == "ACCEPT") && ($6 ~ /dpt/) {print $6}' | sed 's/ dpt//g')
In my head, I want to combine a with_subelements loop (ports for each given service) with a with_nested loop (my subelements as the first list, and my open ports as the nested list), but I am sure this isn't quite possible.
This is the relevant part of my playbook (I've cut out the auth logic as it's not relevant)
- name: Ensure secrets are in the required collections
hosts: localhost
gather_facts: false
vars_files: vars.yaml
[SNIP]
- name: "Get a list of services from https://{{ endpoint }}/services"
ansible.builtin.uri:
url: "https://{{ endpoint }}/services"
body_format: json
headers:
Authorization: "Bearer {{ auth.json.auth_token }}"
validate_certs: "{{ validate_ssl_certs | default('yes') }}"
register: services
- name: "Create a simplified JSON object of services and ports"
ansible.builtin.set_fact:
services_ports: "{{ services.json | json_query(jmesquery) }}"
vars:
jmesquery: "{{ jmesquery_services }}"
- name: See what ports are open on which workers
hosts: workers
gather_facts: false
become: true
vars_files: vars.yaml
tasks:
- name: Get the list of open ports
shell: iptables -L DOCKER-INGRESS | awk -F ' {2,}' '($1 == "ACCEPT") && ($6 ~ /dpt/) {print $6}' | sed 's/ dpt//g'
register: iptables_rules
- name: debug
debug:
msg: "{{ iptables_rules.stdout_lines }}"
And relevant bit of vars.yaml:
---
jmesquery_services: '[?Endpoint.Ports].{ ID: ID, Name: Spec.Name, Ports: Endpoint.Ports[?contains(#.PublishMode,`ingress`)].[PublishedPort, PublishMode, Protocol] }'
How best to check each of these ports for a service against each open port on each worker?
You are on a right track with subelements lookup plugin, you should simply loop over the service/port pairs and check if such port exists in iptables_rules.stdout_lines.
Here is an example playbook with dummy data on how to so:
- hosts: localhost
gather_facts: false
become: false
tasks:
- name: check if all service ports are open
# Loop over service/port pairs
loop: "{{ lookup('subelements', services_ports, 'Ports') }}"
# Set variables for clarity
vars:
service_name: "{{ item[0]['Name'] }}"
iptables_port: "{{ item[1][1] ~ ':' ~ item[1][0] }}"
iptables_port_exists: "{{ iptables_port in iptables_rules.stdout_lines }}"
# Fail the module if port is not found
failed_when: "not iptables_port_exists"
# For demo, print out the service status
debug:
msg: "Service {{ service_name }} is {{ 'up' if iptables_port_exists else 'down' }} on port {{ iptables_port }}"
# Example data
vars:
services_ports:
- ID: aefgergergergergerg
Name: application1_service
Ports:
- ["30950", "tcp"]
- ["30951", "tcp"]
- ID: sdfthtrhrthrthrthrthtrh
Name: application2_service
Ports:
- ["31190", "tcp"]
iptables_rules:
stdout_lines: [
"tcp:30950",
"tcp:31190",
]
This produces output like so:
TASK [check if all service ports are open] *************************************
ok: [localhost] => (item=[{'ID': 'aefgergergergergerg', 'Name': 'application1_service'}, ['30950', 'tcp']]) => {
"msg": "Service application1_service is up on port tcp:30950"
}
failed: [localhost] (item=[{'ID': 'aefgergergergergerg', 'Name': 'application1_service'}, ['30951', 'tcp']]) => {
"msg": "Service application1_service is down on port tcp:30951"
}
ok: [localhost] => (item=[{'ID': 'sdfthtrhrthrthrthrthtrh', 'Name': 'application2_service'}, ['31190', 'tcp']]) => {
"msg": "Service application2_service is up on port tcp:31190"
}
fatal: [localhost]: FAILED! => {"msg": "One or more items failed"}
PS! I'm not familiar with jmespath, but you might need to use hostvars['localhost']['services_ports'] on the workers in order to access variables created on localhost

Not able to print output

I want to print storage filer version output generated by the na_ontap_command module using ansible-playbooks.
I tried to register the result in a variable and print it using a debug message, but I am getting error.
`---
- hosts: localhost
name: run ontap cli command
gather_facts: no
connection: local
vars_files:
- var_file.yml
tasks:
- name: run ontap cli command
na_ontap_command:
command: ['version']
https: true
validate_certs: false
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
register: command_result
- debug:
var: command_result.stdout_lines
`
My playbook should return the version of the storage filer NetApp Release 9.1P8
This is the debug I am getting:
>TASK [debug] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"command_result.stdout_lines": "VARIABLE IS NOT DEFINED!"
}
---
- hosts: localhost
name: run ontap cli command
gather_facts: no
connection: local
vars_files:
- var_file.yml
tasks:
- name: run ontap cli command
na_ontap_command:
command: ['version']
https: true
validate_certs: false
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
register: command_result
- debug:
var: command_result
Result after executing:
TASK [debug] ***********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"command_result": {
"changed": true,
"failed": false,
"msg": "<results xmlns=\"http://www.netapp.com/filer/user\" status=\"passed\"><cli-output>NetApp Release 9.1P8: Wed Aug 30 13:33:41 UTC 2017\n\n</cli-output><cli-result-value>1</cli-result-value></results>"
}
}
Try this:
register: output
- name: print CLI Output
debug:
msg:
- "output": "{{output.msg.split('\n')}}

Saving state of services in Linux services in Ansible

I am trying to save whether services are in running state or stopped. I applied the following login.
---
- hosts: all
vars:
myName: Nikunj
result: ""
tasks:
- name: Initialize empty Started and Stopped list of strings
set_fact:
started: []
stopped: []
- name: Saving context of the VM
shell: service tomcat status
ignore_errors: true
register: result
when: ' "running" in "{{result.stdout}}" '
set_fact:
started : "{{ started }} + ['tomcat']"
- debug:
msg: "{{started}}"
shell command here is giving me eroor.
---
- hosts: all
vars:
myName: Nikunj
result: ""
tasks:
- name: Initialize empty Started and Stopped list of strings
set_fact:
started: []
stopped: []
- name: Saving context of the VM
shell: service tomcat status
ignore_errors: true
register: result
- name: setting fact
set_fact:
started : "{{ started }} + ['tomcat']"
when: ' "running" in "{{result.stdout}}" '
- debug:
msg: "{{started}}"
This will work. set_fact is an individual module.

How do I register a variable and persist it between plays targeted on different nodes?

I have an Ansible playbook, where I would like a variable I register in a first play targeted on one node to be available in a second play, targeted on another node.
Here is the playbook I am using:
---
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: main
gather_facts: no
tasks:
- debug:
msg: {{ foo.stdout }}
But, when I try to access the variable in the second play, targeted on main, I get this message:
The task includes an option with an undefined variable. The error was: 'foo' is undefined
How can I access foo, registered on localhost, from main?
The problem you're running into is that you're trying to reference facts/variables of one host from those of another host.
You need to keep in mind that in Ansible, the variable foo assigned to the host localhost is distinct from the variable foo assigned to the host main or any other host.
If you want to access one hosts facts/variables from another host then you need to explicitly reference it via the hostvars variable. There's a bit more of a discussion on this in this question.
Suppose you have a playbook like this:
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: localhost
gather_facts: no
tasks:
- debug:
var: foo
This will work because you're referencing the host localhost and localhosts's instance of the variable foo in both plays.
The output of this playbook is something like this:
PLAY [localhost] **************************************************
TASK: [command] ***************************************************
changed: [localhost]
PLAY [localhost] **************************************************
TASK: [debug] *****************************************************
ok: [localhost] => {
"var": {
"foo": {
"changed": true,
"cmd": [
"echo",
"hello world"
],
"delta": "0:00:00.004585",
"end": "2015-11-24 20:49:27.462609",
"invocation": {
"module_args": "echo \"hello world\",
"module_complex_args": {},
"module_name": "command"
},
"rc": 0,
"start": "2015-11-24 20:49:27.458024",
"stderr": "",
"stdout": "hello world",
"stdout_lines": [
"hello world"
],
"warnings": []
}
}
}
If you modify this playbook slightly to run the first play on one host and the second play on a different host, you'll get the error that you encountered.
Solution
The solution is to use Ansible's built-in hostvars variable to have the second host explicitly reference the first hosts variable.
So modify the first example like this:
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: main
gather_facts: no
tasks:
- debug:
var: foo
when: foo is defined
- debug:
var: hostvars['localhost']['foo']
## alternatively, you can use:
# var: hostvars.localhost.foo
when: hostvars['localhost']['foo'] is defined
The output of this playbook shows that the first task is skipped because foo is not defined by the host main.
But the second task succeeds because it's explicitly referencing localhosts's instance of the variable foo:
TASK: [debug] *************************************************
skipping: [main]
TASK: [debug] *************************************************
ok: [main] => {
"var": {
"hostvars['localhost']['foo']": {
"changed": true,
"cmd": [
"echo",
"hello world"
],
"delta": "0:00:00.005950",
"end": "2015-11-24 20:54:04.319147",
"invocation": {
"module_args": "echo \"hello world\"",
"module_complex_args": {},
"module_name": "command"
},
"rc": 0,
"start": "2015-11-24 20:54:04.313197",
"stderr": "",
"stdout": "hello world",
"stdout_lines": [
"hello world"
],
"warnings": []
}
}
}
So, in a nutshell, you want to modify the variable references in your main playbook to reference the localhost variables in this manner:
{{ hostvars['localhost']['foo'] }}
{# alternatively, you can use: #}
{{ hostvars.localhost.foo }}
Use a dummy host and its variables
For example, to pass a Kubernetes token and hash from the master to the workers.
On master
- name: "Cluster token"
shell: kubeadm token list | cut -d ' ' -f1 | sed -n '2p'
register: K8S_TOKEN
- name: "CA Hash"
shell: openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
register: K8S_MASTER_CA_HASH
- name: "Add K8S Token and Hash to dummy host"
add_host:
name: "K8S_TOKEN_HOLDER"
token: "{{ K8S_TOKEN.stdout }}"
hash: "{{ K8S_MASTER_CA_HASH.stdout }}"
- name:
debug:
msg: "[Master] K8S_TOKEN_HOLDER K8S token is {{ hostvars['K8S_TOKEN_HOLDER']['token'] }}"
- name:
debug:
msg: "[Master] K8S_TOKEN_HOLDER K8S Hash is {{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}"
On worker
- name:
debug:
msg: "[Worker] K8S_TOKEN_HOLDER K8S token is {{ hostvars['K8S_TOKEN_HOLDER']['token'] }}"
- name:
debug:
msg: "[Worker] K8S_TOKEN_HOLDER K8S Hash is {{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}"
- name: "Kubeadmn join"
shell: >
kubeadm join --token={{ hostvars['K8S_TOKEN_HOLDER']['token'] }}
--discovery-token-ca-cert-hash sha256:{{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}
{{K8S_MASTER_NODE_IP}}:{{K8S_API_SERCURE_PORT}}
I have had similar issues with even the same host, but across different plays. The thing to remember is that facts, not variables, are the persistent things across plays. Here is how I get around the problem.
#!/usr/local/bin/ansible-playbook --inventory=./inventories/ec2.py
---
- name: "TearDown Infrastructure !!!!!!!"
hosts: localhost
gather_facts: no
vars:
aws_state: absent
vars_prompt:
- name: "aws_region"
prompt: "Enter AWS Region:"
default: 'eu-west-2'
tasks:
- name: Make vars persistant
set_fact:
aws_region: "{{aws_region}}"
aws_state: "{{aws_state}}"
- name: "TearDown Infrastructure hosts !!!!!!!"
hosts: monitoring.ec2
connection: local
gather_facts: no
tasks:
- name: set the facts per host
set_fact:
aws_region: "{{hostvars['localhost']['aws_region']}}"
aws_state: "{{hostvars['localhost']['aws_state']}}"
- debug:
msg="state {{aws_state}} region {{aws_region}} id {{ ec2_id }} "
- name: last few bits
hosts: localhost
gather_facts: no
tasks:
- debug:
msg="state {{aws_state}} region {{aws_region}} "
results in
Enter AWS Region: [eu-west-2]:
PLAY [TearDown Infrastructure !!!!!!!] ***************************************************************************************************************************************************************************************************
TASK [Make vars persistant] **************************************************************************************************************************************************************************************************************
ok: [localhost]
PLAY [TearDown Infrastructure hosts !!!!!!!] *********************************************************************************************************************************************************************************************
TASK [set the facts per host] ************************************************************************************************************************************************************************************************************
ok: [XXXXXXXXXXXXXXXXX]
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [XXXXXXXXXXX] => {
"changed": false,
"msg": "state absent region eu-west-2 id i-0XXXXX1 "
}
PLAY [last few bits] *********************************************************************************************************************************************************************************************************************
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "state absent region eu-west-2 "
}
PLAY RECAP *******************************************************************************************************************************************************************************************************************************
XXXXXXXXXXXXX : ok=2 changed=0 unreachable=0 failed=0
localhost : ok=2 changed=0 unreachable=0 failed=0
You can use an Ansible known behaviour. That is using group_vars folder to load some variables at your playbook. This is intended to be used together with inventory groups, but it is still a reference to the global variable declaration. If you put a file or folder in there with the same name as the group, you want some variable to be present, Ansible will make sure it happens!
As for example, let's create a file called all and put a timestamp variable there. Then, whenever you need, you can call that variable, which will be available to every host declared on any play inside your playbook.
I usually do this to update a timestamp once at the first play and use the value to write files and folders using the same timestamp.
I'm using lineinfile module to change the line starting with timestamp :
Check if it fits for your purpose.
On your group_vars/all
timestamp: t26032021165953
On the playbook, in the first play:
hosts: localhost
gather_facts: no
- name: Set timestamp on group_vars
lineinfile:
path: "{{ playbook_dir }}/group_vars/all"
insertafter: EOF
regexp: '^timestamp:'
line: "timestamp: t{{ lookup('pipe','date +%d%m%Y%H%M%S') }}"
state: present
On the playbook, in the second play:
hosts: any_hosts
gather_facts: no
tasks:
- name: Check if timestamp is there
debug:
msg: "{{ timestamp }}"

Resources