Searching Ansible debug messages for a string - python-2.7

I'm trying to find all the up interfaces on a switch, by looking at the results of some output from nxos_facts:
- name: get nxos facts via nxapi
nxos_facts:
provider: "{{ provider['nxapi'] }}"
gather_subset:
- "interfaces"
register: nxfacts_nxapi
- debug:
msg: "{{ nxfacts_nxapi.ansible_facts.ansible_net_interfaces | to_nice_json}} "
And I can successfully print out the debug to show the structure of the dictionary:
"ansible_net_interfaces": {
"Ethernet1/1": {
"bandwidth": 1000000,
"duplex": "full",
"ipv4": {
"address": "10.0.1.2",
"masklen": 24
},
"macaddress": "0800.276d.ee15",
"mtu": "1500",
"speed": "1000 Mb/s",
"state": "up",
"type": "100/1000/10000 Ethernet"
},
"Ethernet1/10": {
"bandwidth": 10000000,
"duplex": "auto",
"macaddress": "0800.276c.eecc",
"mode": "access",
"mtu": "1500",
"speed": "auto-speed",
"state": "down",
"type": "100/1000/10000 Ethernet"
},
But I'm struggling with the syntax to dereference the dictionary to only print when the "state" is "up"?
I'm running with the following version:
ansible 2.3.1.0
Any help is much appreciated.

You can iterate over the dictionary of interfaces and print only those elements for which the condition is true. Example:
- name: mytask
debug:
msg: "{{ item }}"
when: "item.value.state == 'up'"
with_dict: "{{ nxfacts_nxapi.ansible_facts.ansible_net_interfaces }}"

Related

Ansible - remove empty elements from the list

I have an ansible output from which I want to remove all empty (none) elements for a specific attribute. Here is the example list:
"resources": [
{
"id": "9c40900909",
"name": "some_name1"
},
{
"id": "pc4b09090"
},
{
"id": "8lknkkn45"
},
{
"id": "9df40900909",
"name": "some_name2"
}
]
Here is how I reduced the list to be just "resources" with attribute "name":
- set_fact:
resources_names: "{{ output.resources | map(attribute='name') }}"
Problem with that is that I get the elements that does not have any value for the name. These are "AnsibleUndefined" in the following output:
- debug:
msg: resources_names list is "{{ resources_names }}"
ok: [localhost] => {
"msg": "resources_names list is \"['some_name1', AnsibleUndefined, AnsibleUndefined, 'some_name2']\""
}
I tried to remove it with reject and regexp but that's not working.
- set_fact:
list2: "{{ resources_names | reject('match', '^$') | list }}"
Same with this one:
- set_fact:
resources_names: "{{ output.resources | map(attribute='name') | rejectattr('name', 'none') }}"
Any idea?
Thanks.
In a nutshell:
---
- hosts: localhost
gather_facts: false
vars:
resources: [
{
"id": "9c40900909",
"name": "some_name1"
},
{
"id": "pc4b09090"
},
{
"id": "8lknkkn45"
},
{
"id": "9df40900909",
"name": "some_name2"
}
]
tasks:
- debug:
msg: "{{ resources | selectattr('name', 'defined') | map(attribute='name') }}"
which gives:
$ ansible-playbook /tmp/play.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
PLAY [localhost] *******************************************************************************************************************************************************************************
TASK [debug] ***********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"some_name1",
"some_name2"
]
}
PLAY RECAP *************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Note for your next question: please make sure you double check your examples and paste valid json/yaml data in your question. Thanks
Use the filter json_query. This filter ignores missing attributes
resources_names: "{{ output.resources|json_query('[].name') }}"
gives
resources_names:
- some_name1
- some_name2

join list of dictionaries

I have a list of macaddresses and network adapters that I'd like to combine into single list.
vars:
network_adapters:
- name: "Network adapter one"
- name: "Network adapter two"
macaddresses:
- "00:50:56:9c:8c:b1"
- "00:50:56:9c:8c:b2"
It can be done with below method using additional variable but I'm hoping for oneliner.
- name: "Set fact"
set_fact:
network_interfaces: >-
{{
network_interfaces | default([])
+ [item[0] | combine({'macaddress':item[1]})]
}}
loop: "{{ network_adapters | zip(macaddresses) }}"
outputs:
{
"macaddress": "00:50:56:9c:8c:b1",
"name": "Network adapter one"
},
{
"macaddress": "00:50:56:9c:8c:b2",
"name": "Network adapter two"
}
Here are some of my tryouts, maybe someone finds it useful.
add hash to the macaddresses list
"{{ macaddresses | json_query('[].{\"macaddress\": #}') }}"
{
"macaddress": "00:50:56:9c:8c:b1"
},
{
"macaddress": "00:50:56:9c:8c:b2"
}
combine into a list of hashes
"{{ macaddresses | json_query('[].{\"macaddress\": #}') | zip(network_adapters) }}"
[
{
"macaddress": "00:50:56:9c:8c:b1"
},
{
"name": "Network adapter one"
}
],
[
{
"macaddress": "00:50:56:9c:8c:b2"
},
{
"name": "Network adapter two"
}
]
combine into a dictionary
"{{ macaddresses | json_query('[].{\"macaddress\": #}') | map('combine', network_adapters) }}"
{
"macaddress": "00:50:56:9c:8c:b1",
"name": "Network adapter two"
},
{
"macaddress": "00:50:56:9c:8c:b2",
"name": "Network adapter two"
}
I'm specially interested in the last one. This outputs dictionary with the last network_adapters item both times.
Is it a bug?
The task does the job
- debug:
msg: "{{ dict(network_adapters|map(attribute='name')|zip(macaddresses))|
dict2items(key_name='name', value_name='macaddress') }}"
gives
msg:
- macaddress: 00:50:56:9c:8c:b1
name: Network adapter one
- macaddress: 00:50:56:9c:8c:b2
name: Network adapter two
Q: "More complex data. Keep other attributes, like 'ip'?"
network_adapters:
- name: "Network adapter one"
ip: "192.168.0.1"
- name: "Network adapter two"
ip: "192.168.0.2"
A: No "one-liner", just the loop (you've already found out), e.g.
- set_fact:
na2: "{{ na2|default([]) + [item.0|combine({'macaddress': item.1})] }}"
with_together:
- "{{ network_adapters }}"
- "{{ macaddresses }}"
gives
na2:
- ip: 192.168.0.1
macaddress: 00:50:56:9c:8c:b1
name: Network adapter one
- ip: 192.168.0.2
macaddress: 00:50:56:9c:8c:b2
name: Network adapter two

Ansible extract value using regex

I have a file similar to below.
SITE "A" (id:1,bindings:http/*:80:,net.tcp/808:*,net.pipe/*,state:Stopped)
SITE "B" (id:2,bindings:https/*:344:,net.tcp/325:*,net.pipe/*,state:Started)
SITE "C" (id:3,bindings:http/:80:xyz.domain.com,http/*:80:xyz,http/*:80:seiging,http/*:80:seiging.domain.com,state:Started)
SITE "D" (id:4,bindings:http/:901:,http/*:80:actdaov,state:Started)
SITE "E" (id:5,bindings:https/:443:idu.domain.com,http/*:80:idu.domain.com,state:Started)
I want to extract the below content.
"A,id:1,",
"B,id:2,",
"C,id:3,http/:80:seiging.domain.com",
"D,id:4,",
"E,id:5,http/:80:idu.domain.com",
The third field - If domain.com is present I need to extract that, if not it should be blank. If more than one domain.com exists then I need to take the last one.
I tried as below:
set_fact: inv_op="{{ item | regex_replace('SITE \\\"(.*)\\\" \((.*)\,bindings(.*)\,state.*', '\\1,\\2,\\3') }}"
Output as below:
"A,id:1,:http/:80:,net.tcp/808:,net.pipe/",
"B,id:2,:https/:344:,net.tcp/325:,net.pipe/",
"C,id:3,:http/:80:xyz.domain.com,http/:80:xyz,http/:80:seiging,http/:80:seiging.domain.com",
"D,id:4,:http/:901:,http/:80:actdaov",
"E,id:5,:https/:443:idu.domain.com,http/*:80:idu.domain.com",
Please advice
It's possible to create a dictionary first. For example the tasks
- set_fact:
my_lines: "{{ lookup('file', 'data.csv').splitlines() }}"
- set_fact:
my_lists: "{{ my_lists|default([]) + [item.split(' ')] }}"
loop: "{{ my_lines }}"
- set_fact:
my_dict: "{{ my_dict|default({})|
combine({item.1.1: {'id': item.2.split(',')[0].split(':')[1],
'state': item.2.split(',')[-1].split(':')[1][:-1],
'bindings': item.2.split(',')[1:-1]|
map('regex_replace', 'bindings:', '')|list}})
}}"
loop: "{{ my_lists }}"
give
"my_dict": {
"A": {
"bindings": [
"http/*:80:",
"net.tcp/808:*",
"net.pipe/*"
],
"id": "1",
"state": "Stopped"
},
"B": {
"bindings": [
"https/*:344:",
"net.tcp/325:*",
"net.pipe/*"
],
"id": "2",
"state": "Started"
},
"C": {
"bindings": [
"http/:80:xyz.domain.com",
"http/*:80:xyz",
"http/*:80:seiging",
"http/*:80:seiging.domain.com"
],
"id": "3",
"state": "Started"
},
"D": {
"bindings": [
"http/:901:",
"http/*:80:actdaov"
],
"id": "4",
"state": "Started"
},
"E": {
"bindings": [
"https/:443:idu.domain.com",
"http/*:80:idu.domain.com"
],
"id": "5",
"state": "Started"
}
}
Then print the data. For example the task
"The third field - If domain.com is present I need to extract that, if not it should be blank. If more than one domain.com exists then I need to take the last one."
- debug:
msg: |
{{ item.key }},id:{{ item.value.id }},
{%- if item.value.bindings|select('search', 'domain\.com')|list %}
{{ item.value.bindings|select('search', 'domain\.com')|reverse|list|first }},
{% endif %}
loop: "{{ my_dict|dict2items }}"
gives
"msg": "A,id:1,\n"
"msg": "C,id:3,http/*:80:seiging.domain.com,\n"
"msg": "B,id:2,\n"
"msg": "E,id:5,http/*:80:idu.domain.com,\n"
"msg": "D,id:4,\n"

Ansible and AWS Subnets

I am relatively new to working with Ansible Core / Tower and I am at a complete loss what is causing the following issues. I have spent the past two days reading everything I could find on the topic and I am still stuck, looking for help.
Here is what I currently have setup (among other Ansible playbooks, roles, and tasks to create brand new VPC).
Below are the tasks that I am using to create a set of new subnets, one per availability zone, and get the results back from what is created. These tasks all works perfectly as verified through the AWS Console.
### Create the Internet-facing DMZ subnets ###
- name: Create Subnet(s) in VPC - DMZ
ec2_vpc_subnet:
state: present
vpc_id: "{{ new_vpc_info['vpcs'][0]['id'] }}"
region: "{{ vpc_region }}"
az: "{{ item.az }}"
cidr: "{{ item.subnet }}"
resource_tags:
Name: "{{ item.name }}"
Role: "{{ role_tag }}"
Team: "{{ team_tag }}"
Product Area: "{{ product_area_tag }}"
Portfolio: "{{ portfolio_tag }}"
with_items: "{{ dmz_subnet_az }}"
- name: Get Sunbet Info - DMZ
ec2_vpc_subnet_facts:
region: "{{ vpc_region }}"
filters:
"tag:Name": "{{ item.name }}"
with_items: "{{ dmz_subnet_az }}"
register: new_dmz_subnets
- debug:
var=new_dmz_subnets
The output of the "debug" command is provided below, truncated to remove the rest of the subnets and redacted so I do not get yelled at, which matches up to what is in the AWS Console.
{
"changed": false,
"_ansible_verbose_always": true,
"new_dmz_subnets": {
"msg": "All items completed",
"changed": false,
"results": [
{
"_ansible_parsed": true,
"subnets": [
{
"tags": {
"Product Area": "Engineering Tools",
"Portfolio": "Shared Platform and Operations",
"Role": "splunk-proof-of-concept",
"Name": "DMZ_Subnet_A",
"Team": "Engineering Tools"
},
"subnet_id": "subnet-XXXX",
"assign_ipv6_address_on_creation": false,
"default_for_az": false,
"state": "available",
"ipv6_cidr_block_association_set": [],
"availability_zone": "us-east-1a",
"vpc_id": "vpc-XXXX",
"cidr_block": "x.x.x.x/24",
"available_ip_address_count": 251,
"id": "subnet-XXXX",
"map_public_ip_on_launch": false
}
],
"changed": false,
"_ansible_item_label": {
"subnet": "x.x.x.x/24",
"az": "us-east-1a",
"name": "DMZ_Subnet_A"
},
"item": {
"subnet": "x.x.x.x/24",
"az": "us-east-1a",
"name": "DMZ_Subnet_A"
},
"_ansible_item_result": true,
"failed": false,
"invocation": {
"module_args": {
"profile": null,
"aws_secret_key": null,
"aws_access_key": null,
"security_token": null,
"region": "us-east-1",
"filters": {
"tag:Name": "DMZ_Subnet_A"
},
"ec2_url": null,
"subnet_ids": [],
"validate_certs": true
}
},
"_ansible_ignore_errors": null,
"_ansible_no_log": false
},
{
"_ansible_parsed": true,
"subnets": [
{
"tags": {
"Product Area": "Engineering Tools",
"Portfolio": "Shared Platform and Operations",
"Role": "splunk-proof-of-concept",
"Name": "DMZ_Subnet_B",
"Team": "Engineering Tools"
},
"subnet_id": "subnet-XXXX",
"assign_ipv6_address_on_creation": false,
"default_for_az": false,
"state": "available",
"ipv6_cidr_block_association_set": [],
"availability_zone": "us-east-1b",
"vpc_id": "vpc-XXXX",
"cidr_block": "x.x.x.x/24",
"available_ip_address_count": 251,
"id": "subnet-XXXX",
"map_public_ip_on_launch": false
}
],
"changed": false,
"_ansible_item_label": {
"subnet": "x.x.x.x/24",
"az": "us-east-1b",
"name": "DMZ_Subnet_B"
},
"item": {
"subnet": "x.x.x.x/24",
"az": "us-east-1b",
"name": "DMZ_Subnet_B"
},
"_ansible_item_result": true,
"failed": false,
"invocation": {
"module_args": {
"profile": null,
"aws_secret_key": null,
"aws_access_key": null,
"security_token": null,
"region": "us-east-1",
"filters": {
"tag:Name": "DMZ_Subnet_B"
},
"ec2_url": null,
"subnet_ids": [],
"validate_certs": true
}
},
"_ansible_ignore_errors": null,
"_ansible_no_log": false
},
......
}
]
},
"_ansible_no_log": false
}
Now onto the tasks that I am having issues getting working, below is my most recent attempt, which may be completely in left field due to me trying everything I found to get it working. I am attempting to get a list of the "subnet_id" from the registered "new_dmz_subnets" variable, then concatenating it with a "name" that is set in a vars file, and finally using that information to create a NAT Gateway within each of the subnets.
### Create the NAT Gateway in VPC ###
- name: Set DMZ Subnet facts
set_fact:
subnet_id_items:
subnet_id: '{{ item.subnets | map(attribute="subnet_id") | list }}'
with_items: "{{ new_dmz_subnets }}"
register: subnet_id_list
- name: Set Name and DMZ Subnet loop facts
set_fact:
name_subnet_items:
name: "{{ nat_gateway.name }}"
subnet_id: "{{ item.subnet_id }}"
loop: "{{ subnet_id_list }}"
register: name_subnet_list
- debug:
var=name_subnet_list
- name: Create NAT Gateway, allocate new EIP, in VPC
ec2_vpc_nat_gateway:
state: present
subnet_id: "{{ item.subnet_id }}"
region: "{{ vpc_region }}"
wait: yes
if_exist_do_not_create: true
tags:
Name: "{{ item.name }}"
Role: "{{ role_tag }}"
Team: "{{ team_tag }}"
Product Area: "{{ product_area_tag }}"
Portfolio: "{{ portfolio_tag }}"
with_items: "{{ name_subnet_list }}"
register: new_nat_gateway
- debug:
var=new_nat_gateway
When I ran this setup, I got the following fatal error message, which is pretty much the same across every variation I have attempted.
12:55:15
fatal: [localhost]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'subnets'\n\nThe error appears to have been in '/var/lib/awx/projects/_6__erik_andresen_git/ansible/splunk_poc_playbook/roles/create_networking_role/tasks/create_gateways_task.yml': line 21, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n### Starting working on this Task ###\n- name: Set DMZ Subnet facts\n ^ here\n"
}
Please let me know if I can provide any additional details and thanks in advance for the help!!!
-- Erik
I came across a setup that actually works! It may not be the best way to do it and I am still open to suggestions, but it at least works.
Here is code of the "ec2_vpc_subnet" module and collecting the Subnet IDs for later use in the playbook.
### Create the Internet-facing DMZ subnets ###
- name: Create Subnet(s) in VPC - DMZ
ec2_vpc_subnet:
state: present
vpc_id: "{{ vpc_id }}"
region: "{{ vpc_region }}"
az: "{{ item.az }}"
cidr: "{{ item.subnet }}"
resource_tags:
Name: "{{ item.name }}"
Role: "{{ role_tag }}"
Team: "{{ team_tag }}"
Product Area: "{{ product_area_tag }}"
Portfolio: "{{ portfolio_tag }}"
Created By: "{{ created_by }}"
with_items: "{{ dmz_subnet_az }}"
register: new_dmz_subnets
- name: Set facts for Subnet - DMZ
set_fact:
subnet_dmz_id: "{{ subnet_dmz_id | default({}) | combine({ item.subnet.tags.Name: item.subnet.id }) }}"
loop: "{{ new_dmz_subnets.results }}"
- debug:
var=subnet_dmz_id
And here is the use of the Subnet IDs in the "ec2_vpc_nat_gateway" module to create a NAT Gateway within each Availability Zone.
### Create the NAT Gateway in VPC ###
- name: Create NAT Gateway, allocate new EIP, in VPC
ec2_vpc_nat_gateway:
state: present
# NAT Gateways being deployed in DMZ subnets
subnet_id: "{{ subnet_dmz_id[item.subnet_name] }}"
region: "{{ vpc_region }}"
wait: yes
if_exist_do_not_create: true
# Tags not supported in the "ec2_vpc_nat_gateway" module
# https://github.com/ansible/ansible/issues/44339
#tags:
# Name: "{{ item.name }}"
# Role: "{{ role_tag }}"
# Team: "{{ team_tag }}"
# Product Area: "{{ product_area_tag }}"
# Portfolio: "{{ portfolio_tag }}"
# Created By: "{{ created_by }}"
with_items: "{{ nat_gateway }}"
register: new_nat_gateway
- debug:
var=new_nat_gateway

AWS AMI Cleanup w/Ansible iterate through results array

I have a previous task that creates weekly backups, labeling them with the server name followed by a date/time tag. The goal of this job is to go in behind it and clean up the old AMI backups, leaving only the last 3. The ec2_ami_find task works fine, but it could also return empty results for some servers and I'd like the deregister task to handle that.
The error I'm getting is pretty generic:
fatal: [127.0.0.1]: FAILED! => {
"failed": true,
"msg": "The conditional check 'item.ec2_ami_find.exists' failed. The error was: error while evaluating conditional
(item.ec2_ami_find.exists): 'dict object' has no attribute
'ec2_ami_find'\n\nThe error appears to have been in
'/root/ansible/ec2-backups-purge/roles/first_acct/tasks/main.yml': line 25,
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:
Deregister old backups\n ^ here\n"
The playbook task reads as follows:
---
- name: Find old backups
tags: always
ec2_ami_find:
owner: self
aws_access_key: "{{ access_key }}"
aws_secret_key: "{{ secret_key }}"
region: "{{ aws_region }}"
ami_tags:
Name: "{{ item }}-weekly-*"
sort: name
sort_order: descending
sort_start: 3
with_items:
- server-01
- server-02
- server-win-01
- downloads
register: stale_amis
- name: Deregister old backups
tags: always
ec2_ami:
aws_access_key: "{{ access_key }}"
aws_secret_key: "{{ secret_key }}"
region: "{{ aws_region }}"
image_id: "{{ item.ami_id }}"
delete_snapshot: True
state: absent
with_items:
- "{{ stale_amis.results }}"
Snippet of one of the results returns:
"results": [
{
"ami_id": "ami-zzzzzzz",
"architecture": "x86_64",
"block_device_mapping": {
"/dev/xvda": {
"delete_on_termination": true,
"encrypted": false,
"size": 200,
"snapshot_id": "snap-xxxxxxxxxxxxx",
"volume_type": "gp2"
}
},
"creationDate": "2017-08-01T15:26:11.000Z",
"description": "Weekly backup via Ansible",
"hypervisor": "xen",
"is_public": false,
"location": "111111111111/server-01.example.com-20170801152611Z",
"name": "server-01.example.com-20170801152611Z",
"owner_id": "111111111111",
"platform": null,
"root_device_name": "/dev/xvda",
"root_device_type": "ebs",
"state": "available",
"tags": {
"Name": "server-01-weekly-20170801152611Z",
"Type": "weekly"
},
"virtualization_type": "hvm"
},
I doubt your attempt:
with_items:
- "{{ stale_amis.results }}"
because ec2_ami_find put results into own results field. So the first AMI for first server will be stale_amis.results[0].results[0].ami_id
I advice to reduce original stale_amis to required list and loop over it. For example you can use json_query filter:
- ec2_ami:
aws_access_key: "{{ access_key }}"
aws_secret_key: "{{ secret_key }}"
region: "{{ aws_region }}"
image_id: "{{ item }}"
delete_snapshot: True
state: absent
with_items: "{{ stale_amis | json_query('results[].results[].ami_id') }}"