Accessing values of nested dictionaries - variables

I'm working some separate tasks for automating VM deployments through tower.
Basically I just need a quick run down on how to gather/use the various properties of a registered return from a task.
I've got this.
tasks:
- name: Gather disk info from virtual machine using name
vmware_guest_disk_info:
hostname: "{{ vcenter }}"
username: "{{ username }}"
password: "{{ esxipassword }}"
datacenter: "{{ datacenter }}"
name: "{{ fqdn }}"
register: disk_info
- debug:
var: disk_info
This spits out the information I want. But, for the life of me I can't figure out how to select a single property. can someone tell me how to do that (particularly for the backing_filename) property?
I mean in powershell it would just be disk_info.backing_filename or something like backing = $disk_info | select -expandproperty backing_filename. Just looking for something like the equivalent of that.
Snip of output
{
"disk_info": {
"guest_disk_info": {
"0": {
"key": 2000,
"label": "Hard disk 1",
"summary": "104,857,600 KB",
"backing_filename": "[datastorex] vmname/vmname.vmdk",

To be fair, this one is not as simple as it looks, because your dictionary has a key being a string 0, but, would you be doing disk_info.guest_disk_info.0.backing_filename you would try to access an element 0, so a list, and not a dictionary key '0'.
Here would be an example playbook solving your issue:
- hosts: all
gather_facts: yes
tasks:
- debug:
var: disk_info.guest_disk_info['0'].backing_filename
vars:
disk_info:
guest_disk_info:
'0':
key: 2000
label: Hard disk 1
summary: 104,857,600 KB
backing_filename: "[datastorex] vmname/vmname.vmdk"
That gives:
{
"disk_info.guest_disk_info['0'].backing_filename": "[datastorex] vmname/vmname.vmdk"
}
While this works also, you would see that the YAML is representing a totally different structure, also including a list, and not only multiple nested dictionaries:
- hosts: all
gather_facts: yes
tasks:
- debug:
var: disk_info.guest_disk_info.0.backing_filename
vars:
disk_info:
guest_disk_info:
- key: 2000
label: Hard disk 1
summary: 104,857,600 KB
backing_filename: "[datastorex] vmname/vmname.vmdk"
To give you an equivalent in JSON, since you seems to have issue understanding the YAML constructions, your output is
{
"disk_info": {
"guest_disk_info": {
"0": {
"backing_filename": "[datastorex] vmname/vmname.vmdk"
}
}
}
}
That would be accessible via disk_info.guest_disk_info['0'].backing_filename.
While
{
"disk_info": {
"guest_disk_info": [
{
"backing_filename": "[datastorex] vmname/vmname.vmdk"
}
]
}
}
Would be accessible via disk_info.guest_disk_info.0.backing_filename

Related

Ansible cisco ios, shutdown interfaces that are not connected

So here is my current playbook
---
- hosts: SWITCHES
gather_facts: no
tasks:
- name: Show Interface Status
ios_command:
commands:
- show int status
register: out
- debug: var=out.stdout_lines
I basically want to take this script, and then disable all the ports in the "notconnect" state, meaning all the ports with nothing connected to them. Is there a way I can add a "when" statement to this, so that when "show interface status" comes back, it looks at all the ports that are not connected and disables them by applying the "shutdown" command to each interface? I think a "when" statement is what I am needing to do, but not sure where to get started with it. Or is there a better way to accomplish this?
Is there a python script that could accomplish this as well?
You should use ios_facts to retrieve a dictionary containing all the interfaces. Then you can iterate over that dictionary to shutdown the interfaces that are not connected.
If you run your playbook using the -vvv switch, you will see the all the variables collected by ios_facts.
I believe in Ansible 2.9 and later, Ansible gathers the actual network device facts if you specify "gather_facts: yes". With Ansible 2.8 or older, you need to use the "ios_facts" module.
---
- hosts: SWITCHES
gather_facts: no
tasks:
- name: gather IOS facts
ios_facts:
- name: Shutdown notconnect interfaces
ios_config:
lines: shutdown
parents: "interface {{ item.key }}"
with_dict: "{{ ansible_net_interfaces }}"
when: item.value.operstatus == "down"
Here is an example from part of a collected "ansible_net_interfaces" variable:
{
"ansible_net_interfaces": {
"GigabitEthernet0/0": {
"bandwidth": 1000000,
"description": null,
"duplex": "Full",
"ipv4": [],
"lineprotocol": "down",
"macaddress": "10b3.d507.5880",
"mediatype": "RJ45",
"mtu": 1500,
"operstatus": "administratively down",
"type": "RP management port"
},
"GigabitEthernet1/0/1": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "10b3.d507.5881",
"mediatype": "10/100/1000BaseTX",
"mtu": 1500,
"operstatus": "down",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/10": {
"bandwidth": 1000000,
"description": "Telefon/PC",
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "null,
"mediatype": "10/100/1000BaseTX",
"mtu": 1500,
"operstatus": "down",
"type": "Gigabit Ethernet"
},
"GigabitEthernet1/0/11": {
"bandwidth": 1000000,
"description": null,
"duplex": null,
"ipv4": [],
"lineprotocol": null,
"macaddress": "10b3.d507.588b",
"mediatype": "10/100/1000BaseTX",
"mtu": 1500,
"operstatus": "down",
"type": "Gigabit Ethernet"
}
}
The value of the "ansible_net_interfaces" variable is a dictionary. Each key in that dictionary is the interface name, and the value is a new dictionary containing new key/value pairs. The "operstatus" key will have a value "down" when the interface is not connected.
Using "with_dict" in the "ios_config" task loops through all top-level key/value pairs in the dictionary, and you can use the variables in each key/value pair by referring to "{{ item.key }}" or "{{ item.value }}".
Using "when" in the "ios_config" task, you set a condition for when the task is to be executed. In this case we only want it to run when "operstatus" has a value of "down".
The "parents" parameter in the "ios_config" task specifies a new section where the configuration is to be entered, in this case the section is the interface configuration mode. The interface name is returned for each interface in the "ansible_net_interfaces" using the "{{ item.key }}" variable.
Refer to Ansibles documentation for these modules to get a better understanding of them:
https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_facts_module.html
https://docs.ansible.com/ansible/latest/collections/cisco/ios/ios_config_module.html

Saving a random choice string to a usable variable in ansible

Hoping someone can help me figure out what I feel should be a simple nested string problem. I have the following tasks to randomly choose a string, save it to a variable and print it:
tasks:
- name: Debug Section 1
debug:
msg: "{{ item }}"
with_random_choice:
- MY_CHOICE1
- MY_CHOICE2
register: choice
- name: Set result to a fact
set_fact:
THE_CHOICE: "{{ choice.results }}"
- name: Debug Section 3
debug:
msg: "{{ THE_CHOICE }}"
The results return with:
PLAY [Testing variable] **********************************************************************************************************************************************************************************************************************
TASK [Debug Section 1] ***********************************************************************************************************************************************************************************************************************ok: [localhost] => (item=MY_CHOICE1) => {
"msg": "MY_CHOICE1"
}
TASK [Set result to a fact] ******************************************************************************************************************************************************************************************************************ok: [localhost]
TASK [Debug Section 3] ***********************************************************************************************************************************************************************************************************************ok: [localhost] => {
"msg": [
{
"ansible_loop_var": "item",
"changed": false,
"failed": false,
"item": "MY_CHOICE1",
"msg": "MY_CHOICE1"
}
]
}
PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I would like the variable THE_CHOICE to just return the item, but I can't seem to get it to work. In the set_fact section I've tried the following:
THE_CHOICE: "{{ choice.results['item'] }}"
THE_CHOICE: "{{ choice.results.item }}"
THE_CHOICE: "{{ choice['results']['item'] }}"
All attempts result in something to the effect of this:
"The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'item'
Could anybody provide some insight into what I'm missing?
It would be simpler to use the random filter (since Ansible 1.6):
vars:
choices:
- MY_CHOICE1
- MY_CHOICE2
tasks:
- name: Set fact random
set_fact:
THE_CHOICE: "{{ choices | random }}"
About your original playbook, choice.results is a list, containing one result for each item of the loop. In your case, it only contains one result, because the with_random_choice loop only iterates once. So, in order to access your item, you must select the first result of the list with [0]:
- name: Set result to a fact
set_fact:
THE_CHOICE: "{{ choice.results[0].item }}"

Ansible if nested value doesn't exist in nested array

I'd like to make my Ansible EIP creation idempotent. In order to do that I only want the task to run when Tag "Name" value "tag_1" doesn't exist.
However I'm not sure how I could add this as a 'when' at the end of a task.
"eip_facts.addresses": [
{
"allocation_id": "eipalloc-blablah1",
"domain": "vpc",
"public_ip": "11.11.11.11",
"tags": {
"Name": "tag_1",
}
},
{
"allocation_id": "eipalloc-blablah2",
"domain": "vpc",
"public_ip": "22.22.22.22",
"tags": {
"Name": "tag_2",
}
},
{
"allocation_id": "eipalloc-blablah3",
"domain": "vpc",
"public_ip": "33.33.33.33",
"tags": {
"Name": "tag_3",
}
}
]
(Tags are added later) I'm looking for something like:
- name: create elastic ip
ec2_eip:
region: eu-west-1
in_vpc: yes
when: eip_facts.addresses[].tags.Name = "tag_1" is not defined
What is the correct method of achieving this? Bear in mind the value can not exist in that parameter in the entire array, not just a single iteration.
Ok, I found a semi-decent solution
- name: Get list of EIP Name Tags
set_fact:
eip_facts_Name_tag: "{{ eip_facts.addresses | map(attribute='tags.Name') | list }}"
Which extracts the Name tag and puts them into an array
ok: [localhost] => {
"msg": [
"tag_1",
"tag_2",
"tag_3"
]
}
and then...
- debug:
msg: "Hello"
when: '"tag_1" in "{{ eip_facts_Name_tag }}"'
This will work, beware though, this doesn't do an exact string search. So if you did a search for just 'tag' that'd count as a hit too.

Using variables from one yml file in another playbook

I am new to ansible and am trying to use variables from a vars.yml file in a playbook.yml file.
vars.yml
---
- firstvar:
id: 1
name: One
- secondvar:
id: 2
name: two
playbook.yml
---
- hosts: localhost
tasks:
- name: Import vars
include_vars:
file: ./vars.yml
name: vardata
- name: Use FirstVar
iso_vlan:
vlan_id: "{{ vardata.firstvar.id }}"
name: "{{ vardata.firstvar.name }}"
state: present
- name: Use Secondvar
iso_vlan:
vlan_id: "{{ vardata.secondvar.id }}"
name: "{{ vardata.secondvar.name }}"
state: present
So you can see here I am treating the imported variable data, which is stored in vardata, as object and trying to call each of them in other tasks. I am pretty sure these imported vars at the first task are only available in that very task. How can I use that in other tasks? It would output as variables undefined for each tasks. Any input is appreciated.
Your vars.yml file isn't formatted correctly.
Try this:
---
firstvar:
id: 1
name: One
secondvar:
id: 2
name: two
I used this to test it:
---
- hosts: localhost
tasks:
- name: Import vars
include_vars:
file: ./vars.yml
name: vardata
- name: debug
debug:
msg: "{{ vardata.firstvar.name }}"
- name: more debug
debug:
msg: "{{ vardata.secondvar.id }}"
On top of the error you made when declaring the variables (syntax is very important), you can also define include_vars: ./vars.yml such that you can just call {{ firstvar.name }}, {{ firstvar.id }} immediately. Much more leaner/shorter.

Create an arbitrary number of DigitalOcean droplets with Ansible

I want to create an arbitrary number of droplets when I call a playbook with ansible.
For example:
I need to create 10 droplets running some python code.
$ ansible-playbook install_pyapp_commission_new.yml --extra-vars "number_of_droplets_to_create=10"
I tried using with_sequence: count = X but you can't apply it to roles, or inside tasks (as far as I know). My playbook looks something like this:
- name: Digital Ocean Provisioning
hosts: 127.0.0.1
gather_facts: false
connection: local
roles:
- { role: do_provision, do_droplet_number: "{{ number_of_droplets_to_create | default(01) }}" }
- name: Setting up application
gather_facts: true
user: root
hosts: do_instances
roles:
- { role: application, wait_time: 60 }
So I pass the input number of droplets to do_provision as do_droplet_number because atm I create one per run (this way I can run 10 in parallel from bash, each with a different number, thus achieving my goal, but it's a dirty solution).
I wanted to do something like this:
- name: Digital Ocean Provisioning
hosts: 127.0.0.1
gather_facts: false
connection: local
roles:
- { role: do_provision, do_droplet_number: "{{ item }}" }
with_sequence: count={{ number_of_droplets_to_create }}
But this is not valid.
This should work using loop instead of with_sequence.
It shifts the loop into the role, because playbooks can't include the 'when'.
The 'when' is needed to prevent a droplet from being created when do_droplet_number is 0.
playbook
- name: list hosts
hosts: all
gather_facts: false
vars:
thiscount: "{{ mycount | default('0') }}"
roles:
- { role: do-provision, do_droplet_number: "{{ thiscount }}" }
roles/do-provision/task/main.yml
- name: display number
debug:
msg: "mycount {{ item }}"
loop: "{{ range(0, do_droplet_number|int) |list }}"
when: thiscount > 0