Iterate through an array of values of yaml data - ansible

I have the output of a command:
---
data:
versions:
- alt-php53
- ea-php56
- ea-php72
metadata:
command: php_get_installed_versions
reason: OK
result: 1
version: 1
And I need to iterate through the values of the versions key.
I have this to assign the output to a variable:
- name: Get PHP versions
shell: "/usr/sbin/whmapi1 php_get_installed_versions"
register: eaphp_versions
But I do not know how exactly to iterate through those values. I tried various methods with dict2items, from_yaml, from_yaml_all using loop, I tried sub_elements but none worked.
The closest I got is the below:
- name: 'Apply stuff'
shell: "echo the item is {{ item }} >> report.txt"
loop: "{{ eaphp_versions.stdout | from_yaml_all | list }}"
But judging from the output it iterates through the whole output as one item:
the item is {udata: {uversions: [ualt-php53, uea-php56, uea-php72]}, umetadata: {ureason: uOK, uversion: 1, ucommand: uphp_get_installed_versions, uresult: 1}}
Please help.
Thanks in advance.

Assume the data structure at top are the contents from eaphp_versions, then you can:
- any-your-module:
any-module-params: use of {{ item }}
with_items: "{{ eaphp_versions.data.versions }}"
The {{ item }} is one of the 3 versions.

I found a solution to this myself. Here it is below how I did it.
- name: Set fact
set_fact:
versions: "{{ versions|default({})|combine(item) }}"
loop: "{{ eaphp_versions.stdout | from_yaml_all|list }}"
- name: 'Set PHP memory_limit to 512M (all PHP versions)'
#40
shell: "/usr/sbin/whmapi1 php_ini_set_directives directive-1=memory_limit%3A512M version={{ item }}"
with_items: "{{ versions.data.versions }}"

Related

Run a task first time we execute the script but not after that

Background:
We are providing an Ansible utility for the admins to add or remove comments in motd file. We want to restrict any direct edits to motd file. Since there can be previous comments we want to retain them. This means that we parse the file only once and capture existing comments. After which the admins have to use the tool to add/delete comments. Any comments directly added to the file will be discarded.
Requirement:
I have this block which needs to run only once. Not once per execution but once only for many executions. In other words, it should run the first time we execute the script but not after that.
Approach:
To accomplish this, I defined a flag variable and initialized it to 0 like this common_motd_qsc_flag: 0 in defaults/mail.yml. Once I executed a particular task I am trying to update the variable to 1 like this common_motd_qsc_flag: 1. Within the task, I am making sure that the task is executed only when the flag variable is 0 in using the when condition.
Problem:
Every time the script executes it is still running the task that shouldn't be run. I understand why this is happening. It is because during the start of the script it is reading common_motd_qsc_flag: 0 in defaults/main.yml.
Question:
Is there a way to update common_motd_qsc_flag: 1 in defaults/main.yml without using lineinfile module? Any alternative approaches are also appreciated if this an ugly way to handle this requirement.
tasks/main.yml:
- name: Parse all existing comments from /etc/motd
shell: tail --lines=+10 "{{ common_motd_qsc_motd_file }}"
register: existing_comments
when:
- motd_file.stat.exists == True
- common_motd_qsc_flag == 0 # defaults
- name: Update flag variable
set_fact:
common_motd_qsc_flag: 1
when: common_motd_qsc_flag == 0
- name: Add existing comments to the array
set_fact:
common_motd_qsc_comments_array: "{{ common_motd_qsc_comments_array | union([t_existing_entry]) }}"
loop: "{{ existing_comments.stdout_lines }}"
when:
- not t_existing_entry is search('Note:')
- not t_existing_entry is search('APPTYPE:')
- not t_existing_entry is search('Comments:')
- t_existing_entry not in common_motd_qsc_comments_array
vars:
t_existing_entry: "{{ item | trim }}"
defaults/main.yml:
common_motd_qsc_flag: 0
I was able to fix this using local facts as per your advice. Thanks much for the pointer. Here is the working code:
- name: Parse all existing comments from /etc/motd
shell: tail --lines=+10 "{{ common_motd_qsc_motd_file }}"
register: existing_comments
when:
- t_common_motd_qsc_check_qsc_file.stat.exists == True
- ansible_local['snps'] is defined
- ansible_local['snps']['cache'] is defined
- ansible_local['snps']['cache']['common_motd_qsc_flag'] is not defined
changed_when: false
- name: Add existing comments to the array
set_fact:
common_motd_qsc_comments_array: "{{ common_motd_qsc_comments_array | union([t_existing_entry]) }}"
loop: "{{ existing_comments.stdout_lines }}"
when:
- ansible_local['snps'] is defined
- ansible_local['snps']['cache'] is defined
- ansible_local['snps']['cache']['common_motd_qsc_flag'] is not defined
- not t_existing_entry is search('Note:')
- not t_existing_entry is search('APPTYPE:')
- not t_existing_entry is search('Comments:')
- t_existing_entry not in common_motd_qsc_comments_array
vars:
t_existing_entry: "{{ item | trim }}"
- name: Set common_motd_qsc_flag to facts file
ini_file:
dest: "/etc/ansible/facts.d/snps.fact"
section: 'cache' # [header]
option: 'common_motd_qsc_flag' # key
value: "1" # value
- name: Add a new comment if it does not exist
set_fact:
common_motd_qsc_comments_array: "{{ common_motd_qsc_comments_array | union([t_new_entry]) }}"
loop: "{{ common_motd_qsc_add_comment }}"
when:
- t_new_entry not in common_motd_qsc_comments_array
- t_new_entry|length > 0
vars:
t_new_entry: "{{ item | trim }}"
- name: Delete an existing comment
set_fact:
common_motd_qsc_comments_array: "{{ common_motd_qsc_comments_array | difference([t_new_entry]) }}"
loop: "{{ common_motd_qsc_delete_comment }}"
when:
- t_new_entry in common_motd_qsc_comments_array
- t_new_entry|length > 0
vars:
t_new_entry: "{{ item | trim }}"
- name: Save comments to snps.fact file
ini_file:
dest: "/etc/ansible/facts.d/snps.fact"
section: 'motd' # [header]
option: 'common_motd_qsc_comment_array' # key
value: "{{ common_motd_qsc_comments_array }}" # value

Format output to list

I'm working on an ansible playbook. I'm checking for new package patch versions of software, these versions are part of a list.
My code looks like this atm, credits for building the list goes to #Zeitounator
- name: get list of all supported version packages
shell: |
set -o pipefail
repoquery --cache --showduplicates --qf "%{VERSION}" --enablerepo xyz abc \
| grep -E -- "13." \
| sort --unique --version-sort
changed_when: false
register: versions
- name: get the major versions
set_fact:
major_versions: >-
{{
versions.stdout_lines
| map('regex_replace', '^(\d*\.\d*)\.\d*$', '\g<1>')
| unique
| sort
}}
- name: Create a consolidated list per major version
set_fact:
consolidated_versions: >-
{{
consolidated_versions | default([])
+
[{'major_version': item, 'patch_versions': versions.stdout_lines | select('contains', item) | list }]
}}
loop: "{{ major_versions }}"
My first task was to check the patch levels only through their corresponding major versions.
For example:
Check version 13.0.[1-10] only in folder 13.0, 13.1.[1-5] only in folder 13.1 etc.
This works as expected.
What I want to do now is to download only the latest package version of a major version, so I only need a list with the patch version numbers, like this:
13.0.10
13.1.5
13.2.2
I tried it with another set_fact
Like this:
- name: get latest patchversion of supported version
set_fact:
patch_version: "{{ consolidated_versions | json_query('[*].patch_versions[-1]') }}"
- name: output the last versions
debug:
var: patch_version
This gives me an output like this:
ok: [localhost] => {
"patch_version": [
"13.0.10",
"13.1.5",
"13.2.2"
]
}
This is indeed the data I need.
I need to use these 3 elements in a loop to download the packages like this:
- name: Download the files
get_url:
url: https://packages.xyz.com/abc/def/packages/el/{{ ansible_distribution_major_version }}/abc-def-{{ item }}-ee.0.el{{ ansible_distribution_major_version }}.x86_64.rpm/download.rpm
dest: /var/www/html/abc/{{ date }}/abc-def-{{ item }}-ee.0.el{{ ansible_distribution_major_version }}.x86_64.rpm
loop: "{{ patch_version }}"
Problem is now that ansible does not handle the fact as a list, so it replaces {{ patch_version }}with this: ['13.0.10', '13.1.5', '13.2.2']
Of course, this won't work.
How do I transform this output into a loopable list?
I already tried to make it a list, but then I got the whole output as a string as one element. How do I split this into a list like this:
- 13.0.10
- 13.1.5
- 13.2.2
?
Thanks in advance, I'm so confused.
I couldn't reproduce your problem because fact is handled as list in my ansible version (ansible 2.9.6, python 3.7.3).
Nevertheless, if fact is a string try to pass it through from_json filter:
loop: "{{ patch_version | from_json }}"
FWIW, you can check variable type piping it into type_debug:
- debug:
msg: "{{ patch_version | type_debug }}"
Fixed it by myself, not sure what the bug was, maybe just my mess ;).
For testing purposes I tried some things with with_nested and with loop.
So my code looked like this for a while:
- name: Download the files
get_url:
url: https://packages.xyz.com/abc/def/packages/el/{{ ansible_distribution_major_version }}/abc-def-{{ item }}-ee.0.el{{ ansible_distribution_major_version }}.x86_64.rpm/download.rpm
dest: /var/www/html/abc/{{ date }}/abc-def-{{ item }}-ee.0.el{{ ansible_distribution_major_version }}.x86_64.rpm
loop:
- "{{ patch_version }}"
# - commented out loop
# - another commented out loop
At this case the list seems to be read as a string.
So ansible replaces the variable with the all values as a string into my code.
After I changed the code to:
- name: Download the files
get_url:
url: https://packages.xyz.com/abc/def/packages/el/{{ ansible_distribution_major_version }}/abc-def-{{ item }}-ee.0.el{{ ansible_distribution_major_version }}.x86_64.rpm/download.rpm
dest: /var/www/html/abc/{{ date }}/abc-def-{{ item }}-ee.0.el{{ ansible_distribution_major_version }}.x86_64.rpm
loop: "{{ patch_version }}"
It worked as expected. Thanks for the hints, maybe my brain's a little bit confused after the whole day working on that playbook ;)

How to pass multiple lines as value to Ansible

I'm trying to pass multiple lines as a value to my Ansible file But it's only just passing the first line.
cat slot_number.txt
slot4
slot2
slot1
slot3
My ansible file as below
update_bios.yml
tasks:
- name: Powering off slot number
command: "/usr/local/bin/power-util {{slot_number}} off"
- name: Starting to update BIOS
command: "/usr/bin/fw-util {{slot_number}} --update --bios"
ansible-playbook -l myhosts update_bios.yml -e "slot_number=$(cat slot_number.txt)"
I want to run my command like below:
/usr/local/bin/power-util slot1 off
/usr/local/bin/power-util slot2 off
/usr/local/bin/power-util slot3 off
Edit after understanding the question details:
Map the slot numbers to a host:
cat slot_numbers.yml
slot_numbers:
server1:
-slot4
-slot2
server2:
-slot1
-slot3
After that distinguish between hosts in the playbook as follows,
tasks:
- name: Powering off slot number
command: "/usr/local/bin/power-util {{ item }} off"
loop: "{{ slot_numbers[inventory_hostname] }}"
- name: Starting to update BIOS
command: "/usr/bin/fw-util {{ item }} --update --bios"
loop: "{{ slot_numbers[inventory_hostname] }}"
In this approach we are doing the mapping between hosts and slots in the slot_numbers variable file. Alternatively, you can define the variables as host variables also.
Previous answer:
Ansible understands yaml and json.
Also, playbook needs to handle what happens with the data available to it via variables. So, if you want to loop through a list of slot numbers you have to tell your playbook to do exactly that.
Define your slot number file as follows:
In this file, you are creating a list of slot numbers and assigning it to the key 'slot_numbers'
cat slot_numbers.yml
slot_numbers:
-slot4
-slot2
-slot1
-slot3
Modify your update_bios.yml to loop through the slot numbers you are passing,
tasks:
- name: Powering off slot number
command: "/usr/local/bin/power-util {{ item }} off"
loop: "{{ slot_numbers }}"
- name: Starting to update BIOS
command: "/usr/bin/fw-util {{ item }} --update --bios"
loop: "{{ slot_numbers }}"
then use:
ansible-playbook -l myhosts update_bios.yml -e "#slot_numbers.yml"
Detailed documentation of how to use variables and playbooks can be found here
Something like this should work.
vars:
slot_numbers: "{{ lookup('file', './slot_number.txt').splitlines() }}"
tasks:
- name: Powering off slot number
command: "/usr/local/bin/power-util {{ item }} off"
loop: "{{ slot_numbers }}"
- name: Starting to update BIOS
command: "/usr/bin/fw-util {{ item }} --update --bios"
loop: "{{ slot_numbers }}"

Is there any method to map multiple attributes in ansible

I have an output from yum list module. The thing is I want to display the output without those number (epoch attribute). The problem is I couldn't find any solutions of mapping 2 attributes (name and version). All the solutions I found are connected to only 1 attribute ( envra) in my case.
- name: check packages
become: true
yum:
list: installed
register: output
- name: add lines to files
lineinfile:
dest: "./file.txt"
line: "{{ inventory_hostname }} {{ item }}"
with_items:
- "{{ output.results | map(attribute='envra') |list }}"
delegate_to: localhost
This is the output without any mapping. As you can see there are multiple attributes. I would like to display only name and version of the package.
10.112.65.15 {u'envra': u'0:GeoIP-1.5.0-14.el7.x86_64', u'name': u'GeoIP', u'repo': u'installed', u'epoch': u'0', u'version': u'1.5.0', u'release': u'14.el7', u'yumstate': u'installed', u'arch': u'x86_64'}
The closest to expected values is envra attribute, but still has those epoch number inside...
10.112.65.15 0:GeoIP-1.5.0-14.el7.x86_64
As I mentioned at the begging I would like to get output of something like that
10.112.65.15 GeoIP 1.5.0
or at least without epoch attribute.
I've also change approach and tried this method
- name: add lines to files
lineinfile:
dest: "./file.txt"
line: "{{ inventory_hostname }} {{ item }} "
with_items:
- "{{ output | json_query(my_query) | list }}"
delegate_to: localhost
vars:
my_query: "results[].[name, version]"
but received result was with '[]' and u' which I'd like to delete but don't exactly know how.
10.112.65.15 [u'GeoIP', u'1.5.0']
Why do you extract the attribute or use json query ? Simply use the hash and print out the needed fields. The following should work out of the box.
- name: add lines to files
lineinfile:
dest: "./file.txt"
line: "{{ inventory_hostname }} {{ item.name }} {{ item.version }}"
with_items:
- "{{ output.results }}"
delegate_to: localhost

Ansible Registers - Dynamic naming

I am trying to use a register in Ansible playbook to store my output. Below is the code which i am using.
I have tried below code
- name: Check if Service Exists
stat: path=/etc/init.d/{{ item }}
register: {{ item }}_service_status
with_items:
- XXX
- YYY
- ZZZ
I need different outputs to be stored in different register variables based on the items as mentioned in the code. It is failing and not able to proceed. Any help would be appreciated.
Updated answer
I think you need to put quotes around it:
register: "{{ item }}_service_status"
Or you can use set_fact (1, 2, 3, 4)
register all the output to a single static variable output and then use a loop to iteratively build a new variable service_status (a list) by looping over each item in the static variable output
- name: Check if Service Exists
stat: path=/etc/init.d/{{ item }}
register: output
with_items:
- XXX
- YYY
- ZZZ
- name: Setting fact using output of loop
set_fact:
service_status:
- rc: "{{ item.rc }}"
stdout: "{{ item.stdout }}"
id: "{{ item.id }}"
with_items:
- "{{ output }}"
- debug:
msg: "ID and stdout: {{ item.id }} - {{ item.stdout }}"
with_items:
- "{{ service_status }}"
Initial Answer
IIUC, this link from the Ansible docs shows how to use register inside a loop (see another example in this SO post).
A couple of points
it may be more convenient to assign the list (XXX, YYY, ZZZ) to a separate variable (eg. 1, 2)
I don't know if this is part of the problem, but with_items is no longer the recommended approach to loop over a variable: instead use loop - see here for an example
vars:
items:
- XXX
- YYY
- ZZZ
- name: Check if Service Exists
stat: path=/etc/init.d/{{ item }}
register: service_status
loop: "{{ items|flatten(levels=1) }}"
- name: Show the return code and stdout
debug:
msg: "Cmd {{ item.cmd }}, return code {{ item.rc }}, stdout {{ item.stdout }}"
when: item.rc != 0
with_items: "{{ service_status.results }}"

Resources