Ansible-newbie here.
I am struggling with understanding how to check if a value exists in an array returned from a previous task.
Im trying to search if a volume exists in any array of volumes returned from a search:
- name: Get volume pools data
community.libvirt.virt_pool:
command: facts
changed_when: false
- debug: msg="{{ ansible_libvirt_pools }}.stdout_lines"
this returns output like this:
ok: [server] => {
"msg": "{'default': {'status': 'running', 'size_total': '75125227520', 'size_used': '14622126080', 'size_available': '60503101440', 'autostart': 'yes', 'persistent': 'yes', 'state': 'active', 'path': '/var/lib/libvirt/images', 'type': 'dir', 'uuid': '5f6e9ba1-6a50-4313-9aa6-0981448aff0a', 'volume_count': 5, 'volumes': ['centos-8-stream.raw', 'ubuntu-20.04-server-cloudimg-amd64.img', 'ubuntu-server-20.04.raw', 'vyos-1.4-rolling-202201080317-amd64.iso', 'CentOS-Stream-GenericCloud-8-20201019.1.x86_64.qcow2']}, 'infra0_pool_0': {'status': 'running', 'size_total': '11998523817984', 'size_used': '294205259776', 'size_available': '11704318558208', 'autostart': 'yes', 'persistent': 'yes', 'state': 'active', 'path': '/dev/almalinux_images1', 'type': 'logical', 'uuid': '62882128-003d-472c-ac89-d0118cc992c6', 'volume_count': 6, 'volumes': ['admin0.admin_base_vol', 'test01.test_base_vol', 'test00.test_base_vol', 'root', 'vyos0', 'swap'], 'format': 'lvm2'}}.stdout_lines"
}
I need help understanding how to search a specific volumes array to see if a value already exists, and to ignore:
- name: Create VM volume
command: |
virsh vol-create-as {{ pool_name }} {{ vm_volume }} {{ vm_size }}
when {{ vm_volume}} not in ansible_libvirt_pools.{{ pool_name }}.volumes
Would appreciate any help on how to do this correctly.
Im getting warnings on Jinja templating for the 'when' statement, and I don't think its working as expected.
ansible_libvirt_pools:
default:
autostart: 'yes'
path: /var/lib/libvirt/images
persistent: 'yes'
size_available: '60503101440'
size_total: '75125227520'
size_used: '14622126080'
state: active
status: running
type: dir
uuid: 5f6e9ba1-6a50-4313-9aa6-0981448aff0a
volume_count: 5
volumes:
- centos-8-stream.raw
- ubuntu-20.04-server-cloudimg-amd64.img
- ubuntu-server-20.04.raw
- vyos-1.4-rolling-202201080317-amd64.iso
- CentOS-Stream-GenericCloud-8-20201019.1.x86_64.qcow2
infra0_pool_0:
autostart: 'yes'
format: lvm2
path: /dev/almalinux_images1
persistent: 'yes'
size_available: '11704318558208'
size_total: '11998523817984'
size_used: '294205259776'
state: active
status: running
type: logical
uuid: 62882128-003d-472c-ac89-d0118cc992c6
volume_count: 6
volumes:
- admin0.admin_base_vol
- test01.test_base_vol
- test00.test_base_vol
- root
- vyos0
- swap
Q: "How to search a specific volumes array to see if a value already exists."
A: For example, given the list of volumes that should exist
vm_volumes: [swap, export]
the task below
- debug:
msg: "Missing volumes in {{ item.key }}: {{ _missing }}"
loop: "{{ ansible_libvirt_pools|dict2items }}"
vars:
_missing: "{{ vm_volumes|difference(item.value.volumes) }}"
gives (abridged)
msg: 'Missing volumes in default: [''swap'', ''export'']'
msg: 'Missing volumes in infra0_pool_0: [''export'']'
If you want to find existing volumes the task below
- debug:
msg: "Present volumes in {{ item.key }}: {{ _present }}"
loop: "{{ ansible_libvirt_pools|dict2items }}"
vars:
_present: "{{ vm_volumes|intersect(item.value.volumes) }}"
gives (abridged)
msg: 'Present volumes in default: []'
msg: 'Present volumes in infra0_pool_0: [''swap'']'
Related
I'm trying to setup an role for rolling out users. I have a list of users with their variables and I would like to only roll out authorized_key when the variable pubkey is set. Here is my code:
provisioning_user:
- name: ansible
state: present
pubkeys:
- 'ssh-rsa peter-key-1 peter#key1'
- 'ssh-rsa peter-key-3 peter#key3'
root: true
# removes directorys associated with the user
remove: true
create_home: true
comment: Deploy user for ansible
# If set to true when used with home: , attempt to move the user’s old home
# directory to the specified directory if it isn’t there already and the
# old home exists.
#non_unique: false
#uid: 11
#group:
groups: admin, developer
# If false, user will only be added to the groups specified in groups,
# removing them from all other groups.
append: yes
password: '!'
#ssh_public_keyfiles: ['ansible.pub', 'patrick.pub']
#key: ssh-ed25519 AAAAC3NzetfqeafaC1lZDI1NTE5AAAAIHu28wqv0r4aqoK1obosoLCBP0vqZj8MIlkvpAbXv0LL
#key_options:
#key_comment:
key_exclusive: true
key_manage_dir: true
- name: testuser2
state: present
# removes directorys associated with the user
#remove: false
create_home: yes
comment: Deploy user for ansible
As you see, the second user has no attribute pubkeys. Here is my Ansible code:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ '\n'.join(provisioning_user|map(attribute='pubkeys')|flatten) }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
loop: "{{ provisioning_user }}"
when: item.pubkeys is defined
Thats what Ansible says:
fatal: [cloud.xxx.xxx]: FAILED! =>
msg: |-
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'pubkeys'
The error appears to be in '/home/xxx/gitlab.com/xxx/ansible/roles/provisioning/tasks/keys.yaml': line 2, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
---
- name: test key
^ here
Can you please help me getting the when condition work? I only want this task to run if a user has pubkeys defined, when not just skipping it.
This doesn't make any sense:
key: "{{ '\n'.join(provisioning_user|map(attribute='pubkeys')|flatten) }}"
You're looping over the contents of provisioning_user in this task; provisioning_user doesn't have a key pubkeys, rather, each individual item in that list may have a pubkeys value. So you'd want something like:
key: "{{ '\n'.join(item.pubkeys) }}"
Making the complete task look like:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ '\n'.join(item.pubkeys) }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
loop: "{{ provisioning_user }}"
when: item.pubkeys is defined
Running the above in a test environment produces:
TASK [test key] *****************************************************************************************
changed: [node0] => (item={'name': 'ansible', 'state': 'present', 'pubkeys': ['ssh-rsa peter-key-1 peter#key1', 'ssh-rsa peter-key-3 peter#key3'], 'root': True, 'remove': True, 'create_home': True, 'comment': 'Deploy user for ansible', 'groups': 'admin, developer', 'append': True, 'password': '!', 'key_exclusive': True, 'key_manage_dir': True})
skipping: [node0] => (item={'name': 'testuser2', 'state': 'present', 'create_home': True, 'comment': 'Deploy user for ansible'})
...which is I think what you want.
I have vars defined like this:
x_named_zones:
- name: 'example1.com'
type: 'dynamic'
- name: 'example2.org'
type: 'dynamic'
- name: 'example3.tld'
type: 'master'
and I would like to iterate over them with a task:
- name: loop over the list
debug:
msg: 'name: {{ item.name }} type: {{ item.type }}'
loop: '{{ x_named_zones }}'
and this works. But how can I loop over the list where the type: dynamic only? So the expected result of the loop task would be:
example1.com and example2.org
Select the items you want
- name: loop over the list
debug:
msg: "name: {{ item.name }} type: {{ item.type }}"
loop: "{{ x_named_zones|selectattr('type', 'eq', 'dynamic') }}"
Notes
The iteration will fail if the atribute type is missing in any of the items. For example, iteration of the data below
x_named_zones:
- name: 'example1.com'
type: 'dynamic'
- name: 'example2.org'
type: 'dynamic'
- name: 'example3.tld'
type: 'master'
- name: 'example4.tld'
misc: 'master backup'
will fail
TASK [loop over the list] *************************************************************
fatal: [localhost]: FAILED! =>
msg: '''dict object'' has no attribute ''type'''
In this case, you can use the filter json_query. This filter ignores missing attributes. For example,
- name: loop over the list
debug:
msg: "name: {{ item.name }} type: {{ item.type }}"
loop: "{{ x_named_zones|json_query('[?type == `dynamic`]') }}"
gives (abridged)
msg: 'name: example1.com type: dynamic'
msg: 'name: example2.org type: dynamic'
Update the items of the list with default type if you are not able, or don't want to use json_query. For example,
default_type: 'static'
x_named_zones_update: "{{ [{'type': default_type}]|
product(x_named_zones)|
map('combine')|
list }}"
gives
x_named_zones_update:
- name: example1.com
type: dynamic
- name: example2.org
type: dynamic
- name: example3.tld
type: master
- misc: master backup
name: example4.tld
type: static
I'm trying to get the pending installed windows update kb using ansible.
- name: Check for missing updates.
win_updates:
state: searched
category_names: "{{ win_updates_categories }}"
register: update_count
ignore_errors: yes
- debug: msg="{{ update_count.updates.kb }}"
but runs in error, could anyone help me, thank you !
Here is the output for register updatte
- debug:
var: update_count
"update_count": {
"changed": false,
"failed": false,
"filtered_updates": {},
"found_update_count": 1,
"installed_update_count": 0,
"reboot_required": false,
"updates": {
"67eab6a6-099b-42c5-86ce-63681f58ebd2": {
"categories": [
"Security Updates",
"Windows Server 2016"
],
"id": "67eab6a6-099b-42c5-86ce-63681f58ebd2",
"installed": false,
"kb": [
"4593226"
],
}
}
}
}
Here is the error if i only want show kb info
- debug:
var: update_count.updates.kb
"update_count.updates.kb": "VARIABLE IS NOT DEFINED!"
Please see below, I used the {{ item.value.id }} to get the ID value (last task) and saving them in an array if they are more than one, same way you should be able to retrieve the KBs. hopefully this explains
install critical updates only - reboot if nece
- name: install category critical updates only
win_updates:
category_names: ['CriticalUpdates']
server_selection: windows_update
state: installed
ignore_errors: yes
register: CriticalUpdateResults
- name: Postgres Column Values assigned
set_fact: UpdateCategory="{{ CriticalUpdateResults }}"
- name: CriticalUpdates Category assigned
set_fact: PatchCategory="CriticalUpdates"
- name: UpdatesArray assigned space
set_fact:
UpdatesArray: ""
- name: UpdatesArray assigned None if no update count
set_fact:
UpdatesArray: "None"
when: UpdateCategory.found_update_count == 0
- name: Updates Array assigned
set_fact:
UpdatesArray: "{{ UpdatesArray }} {{ **item.value.id** }}"
with_items:
- "{{ UpdateCategory.updates | dict2items }}"
when: UpdateCategory.found_update_count != 0
So I'm working on some audit points using Ansible for many of the servers we support. In most cases, I have had to use the shell modules to get the data I want and then write some files based on pass/fail cases. In a lot of situations, this has been the easier way to work with the output data. First, I realize this isn't necessarily Ansible's forte. I guess at some point it was pitched to the company that it could do this pretty easily, and I would agree - it's easier in many ways than just writing a custom python/BASH script to do the same. So - I do realize I'm bending the concept of Ansible a bit here for reporting, rather than configuration/state management. However; I like the tool and want to show the company we can get a lot of value from it.
While I could do this section easily using the shell module, I would like to learn Ansible a bit better. So thought I would post this question.
I'm using the Yum module to just get a repolist on the target hosts. But I've been running into confusion on just how to extract the list values nested in the output dictionary. I have done some checking on the types and as far as I can tell - the 'results' variable is a dictionary, with the output in in a list. What I want to do, is get the key/values from the list and then perform some other tasks based on that output. But for the life of me - I can't figure out how to do this!
Ideally - I would like to either use some 'when' module statements based on the output (When the repo ID is.. do this.. for example) or at least be able to store them in a variable to work with the data. So from this output, I just want to get the repoid and if it's enabled. How can I get these values from the nested list?
Simple Playbook:
---
- hosts: localhost
become: yes
tasks:
- name: Section 1.1 - Check Yum Repos
yum:
list: repos
register: section1_1
- name: Debug
debug:
var: section1_1
Here is my output from the debug task in this playbook:
TASK [Debug] ****************************************************************************************************************************************************
ok: [localhost] => {
"section1_1": {
"changed": false,
"failed": false,
"results": [
{
"repoid": "ansible",
"state": "enabled"
},
{
"repoid": "epel",
"state": "enabled"
},
{
"repoid": "ol7_UEKR6",
"state": "enabled"
},
{
"repoid": "ol7_latest",
"state": "enabled"
}
]
}
}
I suspect this might be easy for someone out there. I've been trying this and that's for quite a while now and finally got to the point where I thought I would just ask :)
As the output of registered in section1_1 is a list of dictionaries. We can loop through each item, to get the dictionary keys.
Example:
- name: Get the first repo's repoid and state
debug:
msg: "Repo ID: {{ results[0]['repoid'] }}, is {{ results[0]['state'] }}"
# This will show -- Repo ID: ansible, is enabled
Similarly we can access other elements with their number.
Or we can loop through each element of array:
- name: loop through array and conditionally do something
debug:
msg: "Repo ID is {{ item.repoid }}, so I am going to write a playbook."
when: item.repoid == 'ansible'
loop: "{{ results }}"
Q: "Get the key/values from the list."
A: There are more options. Given the data below
section1_1:
changed: false
failed: false
results:
- repoid: ansible
state: enabled
- repoid: epel
state: enabled
- repoid: ol7_UEKR6
state: enabled
- repoid: ol7_latest
state: enabled
- repoid: test
state: disabled
1a) Get the keys and values, and create a dictionary
_keys1: "{{ section1_1.results|map(attribute='repoid')|list }}"
_vals1: "{{ section1_1.results|map(attribute='state')|list }}"
repos1: "{{ dict(_keys1|zip(_vals1)) }}"
gives
_keys1: [ansible, epel, ol7_UEKR6, ol7_latest, test]
_vals1: [enabled, enabled, enabled, enabled, disabled]
repos1:
ansible: enabled
epel: enabled
ol7_UEKR6: enabled
ol7_latest: enabled
test: disabled
1b) The filter items2dict gives the same result
repos2: "{{ section1_1.results|
items2dict(key_name='repoid', value_name='state') }}"
1c) The filter json_query gives also the same result
repos3: "{{ dict(section1_1.results|
json_query('[].[repoid, state]')) }}"
Iterate the dictionary
- debug:
msg: "Repo {{ item.key }} is {{ item.value }}"
loop: "{{ repos1|dict2items }}"
gives (abridged)
msg: Repo ansible is enabled
msg: Repo epel is enabled
msg: Repo ol7_UEKR6 is enabled
msg: Repo ol7_latest is enabled
msg: Repo test is disabled
The next option is the conversion of the values to boolean
_vals4: "{{ section1_1.results|
json_query('[].state.contains(#, `enabled`)') }}"
repos4: "{{ dict(_keys1|zip(_vals4)) }}"
gives
_vals4: [true, true, true, true, false]
repos4:
ansible: true
epel: true
ol7_UEKR6: true
ol7_latest: true
test: false
Iterate the dictionary
- debug:
msg: "Repo {{ item.key }} is enabled: {{ item.value }}"
loop: "{{ repos4|dict2items }}"
gives (abridged)
msg: 'Repo ansible is enabled: True'
msg: 'Repo epel is enabled: True'
msg: 'Repo ol7_UEKR6 is enabled: True'
msg: 'Repo ol7_latest is enabled: True'
msg: 'Repo test is enabled: False'
3a) The list of the enabled repos can be easily selected
- debug:
msg: "Repo {{ item.key }} is enabled"
loop: "{{ repos4|dict2items|selectattr('value') }}"
gives (abridged)
msg: Repo ansible is enabled
msg: Repo epel is enabled
msg: Repo ol7_UEKR6 is enabled
msg: Repo ol7_latest is enabled
3b), or rejected
- debug:
msg: "Repo {{ item.key }} is disabled"
loop: "{{ repos4|dict2items|rejectattr('value') }}"
gives (abridged)
msg: Repo test is disabled
Example of a complete playbook for testing
- hosts: localhost
vars:
section1_1:
changed: false
failed: false
results:
- {repoid: ansible, state: enabled}
- {repoid: epel, state: enabled}
- {repoid: ol7_UEKR6, state: enabled}
- {repoid: ol7_latest, state: enabled}
- {repoid: test, state: disabled}
_keys1: "{{ section1_1.results|map(attribute='repoid')|list }}"
_vals1: "{{ section1_1.results|map(attribute='state')|list }}"
repos1: "{{ dict(_keys1|zip(_vals1)) }}"
repos2: "{{ section1_1.results|
items2dict(key_name='repoid', value_name='state') }}"
repos3: "{{ dict(section1_1.results|
json_query('[].[repoid, state]')) }}"
_vals4: "{{ section1_1.results|
json_query('[].state.contains(#, `enabled`)') }}"
repos4: "{{ dict(_keys1|zip(_vals4)) }}"
tasks:
- debug:
var: section1_1
- debug:
var: _keys1|to_yaml
- debug:
var: _vals1|to_yaml
- debug:
var: repos1
- debug:
var: repos2
- debug:
var: repos3
- debug:
msg: "Repo {{ item.key }} is {{ item.value }}"
loop: "{{ repos1|dict2items }}"
- debug:
var: _vals4|to_yaml
- debug:
var: repos4
- debug:
msg: "Repo {{ item.key }} is enabled: {{ item.value }}"
loop: "{{ repos4|dict2items }}"
- debug:
msg: "Repo {{ item.key }} is enabled"
loop: "{{ repos4|dict2items|selectattr('value') }}"
- debug:
msg: "Repo {{ item.key }} is disabled"
loop: "{{ repos4|dict2items|rejectattr('value') }}"
I'm creating some ec2 instances from a specific image, then trying to get a list of disks attached to these instances.
The problem is when I try to loop over the registered variable from the create instance task, I got an error
I have tried the solution from this post but with no luck
ansible get aws ebs volume id which already exist
- name: create instance
ec2:
region: us-east-1
key_name: xxxxxxx
group: xxxxxx
instance_type: "{{ instance_type }}"
image: "{{ instance_ami }}"
wait: yes
wait_timeout: 500
instance_tags:
Name: "{{ item.name }}"
vpc_subnet_id: "{{ item.subnet }}"
register: ec2
loop: "{{ nodes }}"
- name: show attached volumes Ids
debug:
msg: "{{ item.block_device_mapping | map(attribute='volume_id') }}"
loop: "{{ ec2.results[0].instances }}"
while printing only msg: "{{ item.block_device_mapping }}" I get:
"msg": {
"/dev/sda1": {
"delete_on_termination": true,
"status": "attached",
"volume_id": "vol-xxxxxxx"
},
"/dev/xvdb": {
"delete_on_termination": false,
"status": "attached",
"volume_id": "vol-xxxxxx"
},
"/dev/xvdc": {
"delete_on_termination": false,
"status": "attached",
"volume_id": "vol-xxxxxx"
}
}
but when I use
msg: "{{ item.block_device_mapping | map(attribute='volume_id') }}"
I get this error:
"msg": "[AnsibleUndefined, AnsibleUndefined, AnsibleUndefined]"
The task below
- debug:
msg: "{{ item }}: {{ block_device_mapping[item].volume_id }}"
loop: "{{ block_device_mapping.keys() }}"
gives the {device: volume_id} tuples (grep msg):
"msg": "/dev/xvdb: vol-xxxxxx"
"msg": "/dev/xvdc: vol-xxxxxx"
"msg": "/dev/sda1: vol-xxxxxxx"
To iterate instances use json_query. The task below
- debug:
msg: "{{ item.block_device_mapping|json_query('*.volume_id') }}"
loop: "{{ ec2.results[0].instances }}"
gives:
"msg": [
"vol-xxxxxx",
"vol-xxxxxx",
"vol-xxxxxxx"
]
and the task below with zip
- debug:
msg: "{{ item.block_device_mapping.keys()|zip(
item.block_device_mapping|json_query('*.volume_id'))|list }}"
loop: "{{ ec2.results[0].instances }}"
gives the list of lists:
"msg": [
[
"/dev/xvdb",
"vol-xxxxxx"
],
[
"/dev/xvdc",
"vol-xxxxxx"
],
[
"/dev/sda1",
"vol-xxxxxxx"
]
]
and the task below with dict
- debug:
msg: "{{ dict (item.block_device_mapping.keys()|zip(
item.block_device_mapping|json_query('*.volume_id'))) }}"
loop: "{{ ec2.results[0].instances }}"
gives the tuples
"msg": {
"/dev/sda1": "vol-xxxxxxx",
"/dev/xvdb": "vol-xxxxxx",
"/dev/xvdc": "vol-xxxxxx"
}
The mistake:
So the main mistake you made was thinking of item.block_device_mapping as if it was the map you wanted to work with instead of a map within a map. That is, the keys that you have to first find would, according to the msg that you printed /dev/sda, /dev/xvdb and /dev/xvdc.
So first you'd have to make an array with the keys of the parent map. In the question you can see the necessary code to make Jinja get you the necessary strings:
# The necessary filter to get that array should be something along these lines
item['block_device_mapping'] | list() | join(', ')
You should register that to then loop over,giving you the keys you need to access those elements' attributes.