Get list of ip addesses from multiple citrix hypervisors vm's using ansible - ansible

I am using ansible and the xenserver_guest_info module to create a list of ip addresses from multiple virtual machines on my citrix hypervisor server. Currently I just want to print a list of ip's but storing these ip's in an ansible list would be even better. I plan to loop through this list of ip's and run commands on all these vm's using ansible.
Here is my ansible playbook currently. It loops through the dictionary output that the xenserver module returns, trying to extract the ip address.:
- name: Manage VMs
connection: local
hosts: localhost
# Hypervisor server info from vars file
vars_files:
- xen_vars.yml
tasks:
- name: Gather facts
xenserver_guest_info:
hostname: "{{ xen_address}}"
username: "{{ admin_username }}"
password: "{{ admin_password }}"
name: "{{ item }}"
loop: "{{ xen_machines }}"
register: facts
# - name: Get IP's of VM's
- debug:
msg: "{{item.instance.networks}}"
loop: "{{facts.results}}"
It produces the following output given a list of two vm's on my server:
PLAY [Manage VMs] **************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [Gather facts] ************************************************************
ok: [localhost] => (item=Ubuntu 20)
ok: [localhost] => (item=Ubuntu 20 2)
TASK [debug] *******************************************************************
ok: [localhost] => (item={'failed': False, 'changed': False, 'instance': {'state': 'poweredoff', 'name': 'Ubuntu 20', 'name_desc': '', 'uuid': 'cf5db672-67cf-7e8c-6951-f5959ab62e26', 'is_template': False, 'folder': '', 'hardware': {'num_cpus': 1, 'num_cpu_cores_per_socket': 1, 'memory_mb': 1024}, 'disks': [{'size': 21474836480, 'name': 'Ubuntu 20 0', 'name_desc': 'Created by template provisioner', 'sr': 'Local storage', 'sr_uuid': 'd7bb817b-281e-fd9c-33a3-54db8935d596', 'os_device': 'xvda', 'vbd_userdevice': '0'}], 'cdrom': {'type': 'iso', 'iso_name': 'ubuntu-20.04.1-desktop-amd64.iso'}, 'networks': [{'name': 'Pool-wide network associated with eth0', 'mac': 'a2:07:be:29:5f:ad', 'vif_device': '0', 'mtu': '1500', 'ip': '', 'prefix': '', 'netmask': '', 'gateway': '', 'ip6': [], 'prefix6': '', 'gateway6': ''}], 'home_server': 'citrix-mwyqyqaa', 'domid': '-1', 'platform': {'timeoffset': '0', 'videoram': '8', 'hpet': 'true', 'secureboot': 'false', 'device-model': 'qemu-upstream-compat', 'apic': 'true', 'device_id': '0001', 'vga': 'std', 'nx': 'true', 'pae': 'true', 'viridian': 'false', 'acpi': '1'}, 'other_config': {'base_template_name': 'Ubuntu Focal Fossa 20.04', 'import_task': 'OpaqueRef:3b6061a0-a204-4ed4-be20-842a359a70fa', 'mac_seed': 'f6ae87b5-2f00-0717-559e-8b624fe92f35', 'install-methods': 'cdrom,nfs,http,ftp', 'linux_template': 'true'}, 'xenstore_data': {'vm-data': '', 'vm-data/mmio-hole-size': '268435456'}, 'customization_agent': 'custom'}, 'invocation': {'module_args': {'hostname': '192.168.0.187', 'username': 'root', 'password': 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER', 'name': 'Ubuntu 20', 'validate_certs': True, 'uuid': None}}, 'item': 'Ubuntu 20', 'ansible_loop_var': 'item'}) => {
"msg": [
{
"gateway": "",
"gateway6": "",
"ip": "192.168.0.2",
"ip6": [],
"mac": "a2:07:be:29:5f:ad",
"mtu": "1500",
"name": "Pool-wide network associated with eth0",
"netmask": "",
"prefix": "",
"prefix6": "",
"vif_device": "0"
}
]
}
ok: [localhost] => (item={'failed': False, 'changed': False, 'instance': {'state': 'poweredoff', 'name': 'Ubuntu 20 2', 'name_desc': '', 'uuid': 'b087832e-81f1-c091-1363-8b8ba8442c8e', 'is_template': False, 'folder': '', 'hardware': {'num_cpus': 1, 'num_cpu_cores_per_socket': 1, 'memory_mb': 1024}, 'disks': [{'size': 21474836480, 'name': 'Ubuntu 20 0', 'name_desc': 'Created by template provisioner', 'sr': 'Local storage', 'sr_uuid': 'd7bb817b-281e-fd9c-33a3-54db8935d596', 'os_device': 'xvda', 'vbd_userdevice': '0'}], 'cdrom': {'type': 'iso', 'iso_name': 'ubuntu-20.04.1-desktop-amd64.iso'}, 'networks': [{'name': 'Pool-wide network associated with eth0', 'mac': 'e6:cd:ca:ff:c3:e0', 'vif_device': '0', 'mtu': '1500', 'ip': '', 'prefix': '', 'netmask': '', 'gateway': '', 'ip6': [], 'prefix6': '', 'gateway6': ''}], 'home_server': 'citrix-mwyqyqaa', 'domid': '-1', 'platform': {'timeoffset': '0', 'videoram': '8', 'hpet': 'true', 'secureboot': 'false', 'device-model': 'qemu-upstream-compat', 'apic': 'true', 'device_id': '0001', 'vga': 'std', 'nx': 'true', 'pae': 'true', 'viridian': 'false', 'acpi': '1'}, 'other_config': {'base_template_name': 'Ubuntu Focal Fossa 20.04', 'import_task': 'OpaqueRef:3b6061a0-a204-4ed4-be20-842a359a70fa', 'mac_seed': '3c56a628-0f68-34f9-fe98-4bf2214a5891', 'install-methods': 'cdrom,nfs,http,ftp', 'linux_template': 'true'}, 'xenstore_data': {'vm-data': '', 'vm-data/mmio-hole-size': '268435456'}, 'customization_agent': 'custom'}, 'invocation': {'module_args': {'hostname': '192.168.0.187', 'username': 'root', 'password': 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER', 'name': 'Ubuntu 20 2', 'validate_certs': True, 'uuid': None}}, 'item': 'Ubuntu 20 2', 'ansible_loop_var': 'item'}) => {
"msg": [
{
"gateway": "",
"gateway6": "",
"ip": "192.168.0.3",
"ip6": [],
"mac": "e6:cd:ca:ff:c3:e0",
"mtu": "1500",
"name": "Pool-wide network associated with eth0",
"netmask": "",
"prefix": "",
"prefix6": "",
"vif_device": "0"
}
]
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I tried accessing just the ip but it hasn't been working well since it is stored in a list.
Again my end goal is to just have ansible spit out a list of ip's like so:
192.168.0.2
192.168.0.3
Or even better to store these in an ansible list for later usage. Any help would be much appreciated.

If the only real problem you have is that the IPs are in a list, and you know (because of the way you have configured your VMs) that the list always has only one entry, then just get the first item in the list:
item.instance.networks[0].ip
If you want to then use these IP addresses to do some Ansible work on the VMs, I suggest that you use add_host to build a new inventory for a second play in your playbook:
- name: Manage VMs
connection: local
hosts: localhost
# Hypervisor server info from vars file
vars_files:
- xen_vars.yml
tasks:
- name: Gather facts
xenserver_guest_info:
hostname: "{{ xen_address}}"
username: "{{ admin_username }}"
password: "{{ admin_password }}"
name: "{{ item }}"
loop: "{{ xen_machines }}"
register: facts
# - name: Get IP's of VM's
- debug:
msg: "{{item.instance.networks[0].ip}}"
loop: "{{facts.results}}"
- name: Build inventory of VMs
add_host:
name: "{{ item.instance.networks[0].ip }}"
groups: vms
# You can add other variables per-host here if you want
loop: "{{ xen_machines }}"
loop_control:
label: "{{ item.instance.name }}"
- name: Do stuff directly to the VMs
hosts: vms # This is the group you just created
connection: ssh
tasks:
- debug:
msg: "{{ Hello from a VM }}"

The tasks below
- set_fact:
ips: "{{ facts.results|
json_query('[].instance.networks[].ip') }}"
- debug:
var: ips
should give
ips:
- 192.168.0.2
- 192.168.0.3

Related

ansible: "variable is not defined" even with ignore_erros: True & failed_when: 0 > 1 | How to define variable when task is failed/ignored/skipped?

Trying to get a variable defined when the task is partially failed/ignored. When a VM is not found in Vsphere it throws an error: "msg": "Unable to gather information for non-existing VM vm2"
I tried with and without ignore_errors: True and failed_when: 0 > 1 but no change.
I need the variable vm_fact to be defined even when a non-existing VM is in the decom_list. The plan is to change the list after i have identified which VM's are non-existing.
Please note: this task does work when all vm's are present in the system. When one is missing the task fails. I need the task not to fail so that i can clean the decom_list and then run it again.
- name: check/power(on/off) hosts on vsphere
hosts: localhost
ignore_errors: True
vars:
decom_list:
- vm1
- vm2
- vm3
tasks:
- name: check hosts on vsphere
vmware_guest_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ item }}"
schema: vsphere
datacenter: DC1
loop: "{{ decom_list }}"
register: vm_fact
ignore_errors: True
failed_when: 0 > 1
Whatever i try does not work because my vm_fact is not defined.
- debug:
var: "{{ vm_fact }}"
error:
TASK [debug] **************************************************************************************************************************
ok: [localhost] => {
"<class 'dict'>": "VARIABLE IS NOT DEFINED!"
}
Just to confirm i did try all i know.
When i try it with a loop:
- debug:
var: "{{ item['item'] }}"
loop: "{{ vm_fact['results'] }}"
when: '"Unable to gather information for non-existing VM" in item.msg'
I get error:
ok: [localhost] => (item={'failed': False, 'msg': 'Unable to gather information for non-existing VM vm2', 'invocation': {'module_args': {'hostname': 'hostname.domain', 'username': 'username', 'password': 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER', 'name': 'vm2', 'schema': 'vsphere', 'datacenter': 'DC1', 'port': 443, 'validate_certs': True, 'name_match': 'first', 'use_instance_uuid': False, 'tags': False, 'tag_details': False, 'proxy_host': None, 'proxy_port': None, 'uuid': None, 'moid': None, 'folder': None, 'properties': None}}, 'changed': False, 'failed_when_result': False, 'item': 'vm2', 'ansible_loop_var': 'item'}) => {
"ansible_loop_var": "item",
"item": {
"ansible_loop_var": "item",
"changed": false,
"failed": false,
"failed_when_result": false,
"invocation": {
"module_args": {
"datacenter": "DC1",
"folder": null,
"hostname": "hostname.domain",
"moid": null,
"name": "vm2",
"name_match": "first",
"password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"port": 443,
"properties": null,
"proxy_host": null,
"proxy_port": null,
"schema": "vsphere",
"tag_details": false,
"tags": false,
"use_instance_uuid": false,
"username": "username",
"uuid": null,
"validate_certs": true
}
},
"item": "vm2",
"msg": "Unable to gather information for non-existing VM vm2"
},
"vm2": "VARIABLE IS NOT DEFINED!"
}
ok: [localhost] => (item={'failed': False, 'msg': 'Unable to gather information for non-existing VM vm3', 'invocation': {'module_args': {'hostname': 'hostname.domain', 'username': 'username', 'password': 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER', 'name': 'vm3', 'schema': 'vsphere', 'datacenter': 'DC1', 'port': 443, 'validate_certs': True, 'name_match': 'first', 'use_instance_uuid': False, 'tags': False, 'tag_details': False, 'proxy_host': None, 'proxy_port': None, 'uuid': None, 'moid': None, 'folder': None, 'properties': None}}, 'changed': False, 'failed_when_result': False, 'item': 'vm3', 'ansible_loop_var': 'item'}) => {
"ansible_loop_var": "item",
"item": {
"ansible_loop_var": "item",
"changed": false,
"failed": false,
"failed_when_result": false,
"invocation": {
"module_args": {
"datacenter": "DC1",
"folder": null,
"hostname": "hostname.domain",
"moid": null,
"name": "vm3",
"name_match": "first",
"password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"port": 443,
"properties": null,
"proxy_host": null,
"proxy_port": null,
"schema": "vsphere",
"tag_details": false,
"tags": false,
"use_instance_uuid": false,
"username": "username",
"uuid": null,
"validate_certs": true
}
},
"item": "vm3",
"msg": "Unable to gather information for non-existing VM vm3"
},
"vm3": "VARIABLE IS NOT DEFINED!"
}
fatal: [localhost]: FAILED! => {"msg": "The conditional check '\"Unable to gather information for non-existing VM\" in item.msg' failed. The error was: error while evaluating conditional (\"Unable to gather information for non-existing VM\" in item.msg): 'dict object' has no attribute 'msg'\n\nThe error appears to be in 'location': line 33, column 24, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n # - debug: var=vm_fact\n ^ here\n\nThere appears to be both 'k=v' shorthand syntax and YAML in this task. Only one syntax may be used.\n"}
Tried:
- debug:
msg: "{{ vm_fact['results']|json_query('msg') }}"
- debug:
msg: "{{ vm_fact['results']|to_json|from_json|map(attribute='msg') }}"
- debug:
msg: "{{ vm_fact['results']|map(attribute='msg') }}"
- debug:
msg: "{{ vm_fact['results']|dict2items|map(attribute='msg') }}"
- debug:
msg: "{{ vm_fact['results']|flatten|map(attribute='msg') }}"
error:
TASK [debug] **************************************************************************************************************************
ok: [localhost] => {
"msg": ""
}
TASK [debug] **************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'msg'\n\nThe error appears to be in 'location': line 42, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
...ignoring
TASK [debug] **************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'msg'\n\nThe error appears to be in 'location': line 45, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
...ignoring
TASK [debug] **************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "Unexpected templating type error occurred on ({{ vm_fact['results']|dict2items|map(attribute='msg') }}): dict2items requires a dictionary, got <class 'list'> instead."}
...ignoring
TASK [debug] **************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'msg'\n\nThe error appears to be in 'lcoation': line 51, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
...ignoring
You are, frankly, making things very difficult for yourself. Ansible loops through hosts, use it.
decom.inv:
[decom]
vm1
vm2
vm3
decom.yml:
- name: check/power(on/off) hosts on vsphere
hosts: decom
gather_facts: no
tasks:
- name: check hosts on vsphere
vmware_guest_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ ansible_hostname }}"
schema: vsphere
datacenter: DC1
register: vm_fact
ignore_errors: True
failed_when: false
delegate_to: localhost
- name: Show results
debug:
var: vm_fact

Filter result with selectattr in an Ansible playbook

I'm trying to get the playbook show only the filtered result.
But instead it showing filtered and non-filtered result.
e.g: I just want it to show only the files that not owned by root in the output.
- set_fact:
target_file: /var/log/apache2/
- name: verify that the logs ownership/perms are belong to system administrators and service accounts.
block:
- find:
paths: "{{ target_file }}"
patterns: "*.log"
register: ownership
failed_when: ownership.files | selectattr('pw_name', '!=', 'root')
- debug:
var: ownership
- find:
paths: "{{ target_file }}"
patterns: "*.log"
register: group
failed_when: group.files | selectattr('gr_name', '!=', 'adm')
- find:
paths: "{{ target_file }}"
patterns: "*.log"
register: permissions
failed_when: permissions.files | selectattr('mode', '!=', '0640')
- set_fact:
stig_text: "PASSED"
rescue:
- name: change the permission and ownership of the files
become: true
file:
path: "{{ item.path }}"
owner: root
group: adm
mode: 0640
with_items: "{{ ownership.files }}"
register: change_perms
- set_fact:
stig_text: "PASSED"
when: change_perms.changed == true
Debug output:
TASK [debug] ************************************************************
ok: [localhost] => {
"ownership": {
"changed": false,
"examined": 18,
"failed": false,
"failed_when_result": false,
"files": [
{
"atime": 1652373886.9567592,
"ctime": 1652373886.9567592,
"dev": 2,
"gid": 0,
"gr_name": "root",
"inode": 3096224743982700,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0644",
"mtime": 1652373886.9567592,
"nlink": 1,
"path": "/var/log/alternatives.log",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 49270,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
},
{
"atime": 1623178394.0,
"ctime": 1652791696.643692,
"dev": 2,
"gid": 0,
"gr_name": "root",
"inode": 3377699720693451,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0600",
"mtime": 1623117753.0,
"nlink": 1,
"path": "/var/log/ubuntu-advantage.log",
"pw_name": "root",
"rgrp": false,
"roth": false,
"rusr": true,
"size": 0,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
}
],
"matched": 6,
"msg": ""
}
}
Your code structure is a very strange and inefficient approach to this problem.
This is my best guess at what you were trying to do:
- name: Verify the ownership and permissions on logs
vars:
target_file: /var/log/apache2/
block:
- name: Find all log files under {{ target_file }}
find:
paths: "{{ target_file }}"
patterns: "*.log"
register: log_files
- name: Fix permissions and ownership
file:
path: "{{ item.path }}"
owner: root
group: adm
mode: "0640"
become: true
loop: "{{ logs_bad_owner | union(logs_bad_group) | union(logs_bad_perms) }}"
loop_control:
label: "{{ item.path }}"
vars:
logs_bad_owner: "{{ log_files.files | selectattr('pw_name', '!=', 'root') }}"
logs_bad_group: "{{ log_files.files | selectattr('gr_name', '!=', 'adm') }}"
logs_bad_perms: "{{ log_files.files | selectattr('mode', '!=', '0640') }}"
- set_fact:
stig_text: PASSED
rescue:
- set_fact:
stig_text: FAILED
This avoids unnecessary work like running the same find multiple times or looping over the entire set of files when you're only interested in the ones that need fixing.
Because your debug is just after a command that would fail if pw_name is not root, due to your failed_when condition, the debug just won't happen if there is any problematic file, and enters the rescue tasks instead, so when you do see the any debug task result, it means there was no problematic file, in terms on ownership, really.
Now the reason being pointed, you have to understand that Ansible is all about idempotency:
An operation is idempotent if the result of performing it once is exactly the same as the result of performing it repeatedly without any intervening actions.
Source: https://docs.ansible.com/ansible/latest/reference_appendices/glossary.html
So, you don't even have to do anything complex here you just have to set the rights and ownership to what it should:
- name: change the permission and ownership of the files
file:
path: "{{ item }}"
owner: root
group: adm
mode: 0640
with_fileglob:
- /var/log/apache2/*.log
register: permission_audit
And then, if you want to make an audit report, you can do something like:
- name: List of security remediation run during this audit
debug:
var: >-
permission_audit.results
| selectattr('changed')
| map(attribute='diff')
Which would yield something like:
TASK [List of security remediation run during this audit] ********************
ok: [localhost] =>
permission_audit.results | selectattr('changed') | map(attribute='diff'):
- after:
mode: '0640'
path: /var/log/apache2/access.1.log
before:
mode: '0777'
path: /var/log/apache2/access.1.log
- after:
mode: '0640'
path: /var/log/apache2/access.2.log
before:
mode: '0777'
path: /var/log/apache2/access.2.log
If you want to show the success or the failure of the audit in the result of the playbook you can even go one step further and assert the length of this list:
- name: Security audit result
assert:
that: permission_changes | length == 0
fail_msg: Audit on Apache log file rights failed
success_msg: Audit on Apache log file rights passed
vars:
permission_changes: >-
{{
permission_audit.results
| selectattr('changed')
| map(attribute='diff')
}}
And now our playbook will either end in
TASK [Security audit result] **********************************************
ok: [localhost] => changed=false
msg: Audit on Apache log file rights passed
PLAY RECAP ****************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
Or in
TASK [Security audit result] **********************************************
fatal: [localhost]: FAILED! => changed=false
assertion: permission_changes | length == 0
evaluated_to: false
msg: Audit on Apache log file rights failed
PLAY RECAP ****************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=1
Given ten log files; two having rights issues and the two tasks:
- file:
path: "{{ item }}"
owner: root
group: adm
mode: 0640
with_fileglob:
- /var/log/apache2/*.log
register: permission_audit
- name: List of security remediation run during this audit
debug:
var: >-
permission_audit.results
| selectattr('changed')
| map(attribute='diff')
This would yield:
TASK [file] *****************************************************************
changed: [localhost] => (item=/var/log/apache2/access.1.log)
ok: [localhost] => (item=/var/log/apache2/access.10.log)
ok: [localhost] => (item=/var/log/apache2/access.4.log)
ok: [localhost] => (item=/var/log/apache2/access.9.log)
ok: [localhost] => (item=/var/log/apache2/access.7.log)
ok: [localhost] => (item=/var/log/apache2/access.3.log)
ok: [localhost] => (item=/var/log/apache2/access.5.log)
ok: [localhost] => (item=/var/log/apache2/access.8.log)
ok: [localhost] => (item=/var/log/apache2/access.6.log)
changed: [localhost] => (item=/var/log/apache2/access.2.log)
TASK [List of security remediation run during this audit] *******************
ok: [localhost] =>
permission_audit.results | selectattr('changed') | map(attribute='diff'):
- after:
mode: '0640'
path: /var/log/apache2/access.1.log
before:
mode: '0777'
path: /var/log/apache2/access.1.log
- after:
mode: '0640'
path: /var/log/apache2/access.2.log
before:
mode: '0777'
path: /var/log/apache2/access.2.log
And if you rerun those same two tasks again, then it ends up in:
TASK [file] *****************************************************************
ok: [localhost] => (item=/var/log/apache2/access.1.log)
ok: [localhost] => (item=/var/log/apache2/access.10.log)
ok: [localhost] => (item=/var/log/apache2/access.4.log)
ok: [localhost] => (item=/var/log/apache2/access.9.log)
ok: [localhost] => (item=/var/log/apache2/access.7.log)
ok: [localhost] => (item=/var/log/apache2/access.3.log)
ok: [localhost] => (item=/var/log/apache2/access.5.log)
ok: [localhost] => (item=/var/log/apache2/access.8.log)
ok: [localhost] => (item=/var/log/apache2/access.6.log)
ok: [localhost] => (item=/var/log/apache2/access.2.log)
TASK [List of security remediation run during this audit] *******************
ok: [localhost] =>
permission_audit.results | selectattr('changed') | map(attribute='diff'): []
Now, with an empty list on the last debug, we know our security audit passed everything with a green check.

Ansible Filter with_items list

I am using Ansible 2.10.7 and I need to filter a specific item in the with_items list.
Before using with_items msg looks like this:
"ansible_facts": {
"cvp_info": {
"version": "2020.2.3"
},
"devices": [
{
"architecture": "",
"bootupTimeStamp": 1615810038.137913,
"bootupTimestamp": 1615810038.137913,
"complianceCode": "",
Playbook:
---
- name: Playbook to demonstrate cv_container module.
hosts: cvp_servers
connection: local
gather_facts: no
collections:
- arista.cvp
vars:
vars_files:
- vars.yml
tasks:
- name: "collecting facts from CVP {{inventory_hostname}}"
arista.cvp.cv_facts:
facts:
devices
- name: "Print out facts from CVP"
debug:
msg: "{{item.name}}"
with_items: "{{devices}}"
After using the with_items: "{{devices}}", I see it is filtering the big list and then I get this output which I want to filter:
ok: [hq] => (item={'hostname': 'rd-sw055', 'danzEnabled': False, 'mlagEnabled': False, 'streamingStatus': 'active', 'status': 'Registered','bootupTimeStamp': 1605618537.210405, 'internalBuildId': '8c8dfbf2-a4d1-420a-9c9c-59f6aa67a14e', 'taskIdList': [], 'tempAction': None, 'memTotal': 0, 'memFree': 0, 'sslConfigAvailable': False, 'sslEnabledByCVP': False, 'lastSyncUp': 0, 'type': 'netelement', 'dcaKey': None, 'containerName': 'HQ',
'name': 'rd-sw055','deviceSpecificConfiglets': ['rd-sw055'], 'imageBundle': ''}) => {
"msg": "rd-sw055"
ok: [hq] => (item={'hostname': 'rd-sw01', 'danzEnabled': False, 'mlagEnabled': False, 'streamingStatus': 'active', 'status': 'Registered','bootupTimeStamp': 1605618537.210405, 'internalBuildId': '8c8dfbf2-a4d1-420a-9c9c-59f6aa67a14e', 'taskIdList': [], 'tempAction': None, 'memTotal': 0, 'memFree': 0, 'sslConfigAvailable': False, 'sslEnabledByCVP': False, 'lastSyncUp': 0, 'type': 'netelement', 'dcaKey': None, 'containerName': 'HQ',
'name': 'rd-sw01','deviceSpecificConfiglets': ['rd-sw01'], 'imageBundle': ''}) => {
"msg": "rd-sw01"
I want it to show only the item with the 'name': 'rd-sw01' how can I do it?
I have tried using
loop_control:
label: '{{ item.name }}'
At the end of the playbook but this will only show name value and not the whole item values.
End result wanted:
ok: [hq] => (item={'hostname': 'rd-sw01', 'danzEnabled': False, 'mlagEnabled': False, 'streamingStatus': 'active', 'status': 'Registered','bootupTimeStamp': 1605618537.210405, 'internalBuildId': '8c8dfbf2-a4d1-420a-9c9c-59f6aa67a14e', 'taskIdList': [], 'tempAction': None, 'memTotal': 0, 'memFree': 0, 'sslConfigAvailable': False, 'sslEnabledByCVP': False, 'lastSyncUp': 0, 'type': 'netelement', 'dcaKey': None, 'containerName': 'HQ',
'name': 'rd-sw01','deviceSpecificConfiglets': ['rd-sw01'], 'imageBundle': ''}) => {
"msg": "rd-sw01"
You do want a when condition here:
- debug:
var: item
loop: "{{ devices }}"
when: item.name == 'rd-sw01'
loop_control:
label: "{{ item.name }}"
Or even, simpler, skip the loop:
- debug:
var: devices | selectattr("name", "eq", "rd-sw01")

Ansible try to query an AWS TG using community-aws module to get TG status

I'm trying to query an AWS TG using the community-aws module to get TG status,
I want to query a specific TG every few seconds until I see that the state of that TG is "healthy".
So far I have this task in ansible:
- name: Gather information about the target group attached to a particular LB
vars:
ansible_python_interpreter: /usr/bin/python3.6
register: target_health
community.aws.elb_target_group_info:
region: "{{AWS_REGION}}"
target_group_arns: "{{TARGET_GROUP_ARN}}"
collect_targets_health: yes
delegate_to: 127.0.0.1
- debug: msg="return_target_health ={{target_health}}"
- name: iterate items
debug:
msg: "{{ item.targets_health_description }}"
with_items: "{{ target_health.target_groups }}"
The playbook output:
TASK [service : debug] ****************************************************
ok: [service.devbed-vpc.] => {
"msg": "return_target_health ={'target_groups': [{'target_group_arn': 'arn:aws:elasticloadbalancing:us-east-1:4795703XXXXX:targetgroup/Testbed-Vee-8124-TG/b8b282d82426331c', 'target_group_name': 'Testbed-Vee-8124-TG', 'protocol': 'HTTP', 'port': 8124, 'vpc_id': 'vpc-19333d7f', 'health_check_protocol': 'HTTP', 'health_check_port': '8124', 'health_check_enabled': True, 'health_check_interval_seconds': 10, 'health_check_timeout_seconds': 5, 'healthy_threshold_count': 5, 'unhealthy_threshold_count': 2, 'health_check_path': '/health', 'matcher': {'http_code': '200'}, 'load_balancer_arns': ['arn:aws:elasticloadbalancing:us-east-1:4795703XXXXX:loadbalancer/app/Testbed-Vee-ALB/e2b8546cb7196017'], 'target_type': 'instance', 'protocol_version': 'HTTP1', 'stickiness_enabled': 'false', 'deregistration_delay_timeout_seconds': '300', 'stickiness_type': 'lb_cookie', 'stickiness_lb_cookie_duration_seconds': '86400', 'slow_start_duration_seconds': '0', 'load_balancing_algorithm_type': 'round_robin', 'tags': {'Env': 'Testbed'}, 'targets_health_description': [{'target': {'id': 'i-0b9b6e5a2775bXXXX', 'port': 8124}, 'health_check_port': '8124', 'target_health': {'state': 'healthy'}}, {'target': {'id': 'i-0feb307f8bdf6XXXX', 'port': 8124}, 'health_check_port': '8124', 'target_health': {'state': 'healthy'}}]}], 'failed': False, 'changed': False}"
TASK [service : iterate items] ********************************************
ok: [service.devbed-vpc.] => (item={'target_group_arn': 'arn:aws:elasticloadbalancing:us-east-1:4795703XXXXX:targetgroup/Testbed-Vee-8124-TG/b8b282d82426331c', 'target_group_name': 'Testbed-Vee-8124-TG', 'protocol': 'HTTP', 'port': 8124, 'vpc_id': 'vpc-19333d7f', 'health_check_protocol': 'HTTP', 'health_check_port': '8124', 'health_check_enabled': True, 'health_check_interval_seconds': 10, 'health_check_timeout_seconds': 5, 'healthy_threshold_count': 5, 'unhealthy_threshold_count': 2, 'health_check_path': '/health', 'matcher': {'http_code': '200'}, 'load_balancer_arns': ['arn:aws:elasticloadbalancing:us-east-1:4795703XXXXX:loadbalancer/app/Testbed-Vee-ALB/e2b8546cb7196017'], 'target_type': 'instance', 'protocol_version': 'HTTP1', 'stickiness_enabled': 'false', 'deregistration_delay_timeout_seconds': '300', 'stickiness_type': 'lb_cookie', 'stickiness_lb_cookie_duration_seconds': '86400', 'slow_start_duration_seconds': '0', 'load_balancing_algorithm_type': 'round_robin', 'tags': {'Env': 'Testbed'}, 'targets_health_description': [{'target': {'id': 'i-0b9b6e5a2775bXXXX', 'port': 8124}, 'health_check_port': '8124', 'target_health': {'state': 'healthy'}}, {'target': {'id': 'i-0feb307f8bdf6XXXX', 'port': 8124}, 'health_check_port': '8124', 'target_health': {'state': 'healthy'}}]}) => {
"msg": [
{
"health_check_port": "8124",
"target": {
"id": "i-0b9b6e5a2775bXXXX",
"port": 8124
},
"target_health": {
"state": "UNhealthy"
}
},
{
"health_check_port": "8124",
"target": {
"id": "i-0feb307f8bdf6XXXX",
"port": 8124
},
"target_health": {
"state": "healthy"
}
}
]
}
I want to run this task until both "target_health:states" are healthy.
I wasn't able to do it, I was able to put the output to the file and then run a shell script that checks if the string "state": "healthy" is accruing more than once.
But then I realized the file is actually static and I write to it only once and only then I run the script in a loop which doesn't make any sense.
Is there a way for creating this query until I get the proper result I want without writing it to a file?
The retries:, delay:, and until: keywords will interest you
- name: Gather information about the target group attached to a particular LB
vars:
ansible_python_interpreter: /usr/bin/python3.6
register: target_health
community.aws.elb_target_group_info:
region: "{{AWS_REGION}}"
target_group_arns: "{{TARGET_GROUP_ARN}}"
collect_targets_health: yes
retries: 12
delay: 5
until: >-
{{ (target_health.target_groups[0].targets_health_description|length)
== target_health.target_groups[0].targets_health_description
| selectattr("target_health.state", "eq", "healthy") | list | length }}
delegate_to: 127.0.0.1
Alternatively, one could use awscli's wait, if you'd prefer to let awscli stall for you (it's not very ansible-y, but it makes for a ton less playbook log output as ansible retries)
- command: >-
aws --region {{ AWS_REGION }} elbv2 wait
target-in-service --target-group-arn {{ TARGET_GROUP_ARN | quote }}

Build a re-usable list of paths from ansible copy module

Given the following ansible snippet:
- name: copy artifact
copy: src=target/{{ item.artifactId }}-{{ item.version }}.{{ item.type }} dest={{ item.dest }}
become: yes
with_items: "{{ gavdeploy_list }}"
register: artifact
TASK: [gavdeploy | debug msg="{{ artifact }}"] ********************************
ok: [mymachine] => {
"msg": "{'msg': 'All items completed', 'changed': False,
'results': [{'group': 'wildfly', 'uid': 11002, 'dest':
'/opt/wildfly/standalone/deployments/foolistener-1.0.104.war',
'checksum': '4c110c4833cf37535b37325ef01a4212ed4a14c4', 'changed': False,
'owner': 'wildfly', 'item': {'endpoint': 'http://localhost:8080/foo-
listener-bot/health', 'group': 'wildfly', 'dest':
'/opt/wildfly/standalone/deployments', 'version': u'1.0.104', 'owner':
'wildfly', 'type': 'war', 'groupId': 'company.foo.listener',
'artifactId': 'foolistener'}, 'state': 'file', 'gid': 11002, 'secontext':
'system_u:object_r:usr_t:s0', 'mode': '0644', 'invocation':
{'module_name': u'copy', 'module_complex_args': {}, 'module_args':
u'src=target/foolistener-1.0.104.war
dest=/opt/wildfly/standalone/deployments owner=wildfly group=wildfly
mode=644'}, 'path': '/opt/wildfly/standalone/deployments/foolistener-
1.0.104.war', 'size': 38665931},
I can see in the -vv output that we have output
'path': '/opt/wildfly/standalone/deployments/foolistener-
1.0.104.war'
'changed': False
I would be grateful if someone could show me a way to extract path and changed fields, in such a way I can re-use this with jboss-cli to perform a hot deploy.
E.g. ( but I am in deep waters here )
- name: hot deploy
shell: /path/to/jboss-cli.sh --command "deploy {{ item.path }}"
with_items: "{{ artefact }}"
If I can get the basics of this working, I can probably figure out how to deal with changed: False or True myself.
I think this will solution your problem:
- name: hot deploy
shell: "/path/to/jboss-cli.sh --command deploy {{ item.item.path }}"
with_items: "{{ artefact.results }}"
Hope that help you.

Resources