Ansible: group values on same line with common key - ansible

I'm struggling to group some common values on a same line, with ansible.
I have the vg name and disks on each line, as follows:
ok: [localhost] => {
"msg": [
"vg01 /dev/xvdk",
"vg01 /dev/xvdj",
"vg02 /dev/xvdi",
"vg02 /dev/xvdh",
"vg03 /dev/xvdg",
"vg03 /dev/xvdf"
]
}
Now, I want the vg with all it's disks, on one same line, one per vg, as below:
"vg01 /dev/xvdk, /dev/xvdj",
"vg02 /dev/xvdi, /dev/xvdh",
"vg03 /dev/xvdg, /dev/xvdf",
Still not able to achieve this; Can someone help, please?
Thank you.

Create a list of dictionaries. For example, given the data is stored in the variable disks
- set_fact:
l1: "{{ l1|default([]) + [{'dsk': item.split(' ')|first,
'dev': item.split(' ')|last}] }}"
loop: "{{ disks }}"
gives
l1:
- dev: /dev/xvdk
dsk: vg01
- dev: /dev/xvdj
dsk: vg01
- dev: /dev/xvdi
dsk: vg02
- dev: /dev/xvdh
dsk: vg02
- dev: /dev/xvdg
dsk: vg03
- dev: /dev/xvdf
dsk: vg03
Use groupby filter and format the lines in the loop, for example
- debug:
msg: "{{ item.0 }} {{ item.1|flatten|map(attribute='dev')|join(', ') }}"
loop: "{{ l1|groupby('dsk') }}"
gives
msg: vg01 /dev/xvdk, /dev/xvdj
msg: vg02 /dev/xvdi, /dev/xvdh
msg: vg03 /dev/xvdg, /dev/xvdf
A more robust option, using regex_replace/from_yaml, might be able to handle some malformed data where simple split/first/last would fail e.g.
- set_fact:
l1: "{{ l1|default([]) + [item.0|combine(item.1)] }}"
with_together:
- "{{ disks|map('regex_replace', '^(.*?) (.*)$', 'dsk: \\1')|
map('from_yaml')|list }}"
- "{{ disks|map('regex_replace', '^(.*?) (.*)$', 'dev: \\2')|
map('from_yaml')|list }}"

Related

Ansible - loop over multiple items in stdout_lines

I am performing a grep with multiple items.
---
- hosts: my_host
gather_facts: false
vars:
my_list:
- whatever
- something
tasks:
- name: grep for item in search path
shell: "grep -rIL {{ item }} /tmp"
register: the_grep
loop: "{{ my_list }}"
- debug:
msg: "{{ item.stdout_lines }}"
loop: "{{ the_grep.results }}"
Depending on the result, multiple files could match.
msg:
- /tmp/something.conf
- /tmp/folder/file.txt
Q: How would I configure Ansible to loop over the items in stdout_lines?
The use case I'm solving is to delete .ini sections based on the item, but in this case, Ansible doesn't loop over the stdout_lines.
- name: remove stanza from ini file
ini_file:
path: "{{ item.stdout_lines }}"
section: "{{ item.item }}"
mode: '0600'
state: absent
loop: "{{ the_grep.results }}"
when: item.stdout_lines | length > 0
It seems that this doesn't work, but configuring item.stdout_lines[0] gives the partially expected result, since Ansible will use only the first item in that list. But ofc, not the 2nd and so on.
Perhaps there's a prettier answer, but solved it by using with_nested and creating a json_query:
- name: remove stanza from ini file
ini_file:
path: "{{ item.0 }}"
section: "{{ item.1.item }}"
mode: '0600'
state: absent
with_nested:
- "{{ the_grep | json_query('results[].stdout_lines[]') }}"
- "{{ the_grep.results }}"

Ansible: exclude item(s) from lookup result

The following Ansible play:
- name: Fetch domains
vars:
domain_lines: "{{ lookup('file', domain_file).splitlines() }}"
debug:
msg: "Domains: {{ domain_lines }}"
reads and splits the lines from the following "domain_file":
test1.domain.tld
# test2.domain.tld
test3.domain.tld
test4.domain.tld
This works nicely. But now I'd like to skip/remove the lines starting with a hashtag. What's the best way to achieve this?
Use reject. The play below
- set_fact:
domain_selected: "{{ domain_lines|reject('regex', '^#(.*)$')|list }}"
- debug:
var: domain_selected
gives
"domain_selected": [
"test1.domain.tld",
"test3.domain.tld",
"test4.domain.tld"
]

Ansible: Sequence range using loop variable

I need to create a certain number of systemd unit files based on name and included variable with number of files to create, like:
app-name#1.service
app-name#2.service
app-name#3.service
script-name#1.service
script-name#2.service
script-name#3.service
script-name#4.service
It works with a nested loop using range function, but I not understand how to use loop variable (item.1.workers) into range() parameters.
- hosts: localhost
vars:
apps:
- name: app-name
workers: 3
- name: script-name
workers: 5
connection: local
tasks:
- name: test
debug:
msg: "{{ item.1.name }}#{{ item.0 }}.service"
loop: "{{ range(1, 3) | product(apps) | list }}"
Let's create the lists of workers in the first task and loop with subelements in the second task
- set_fact:
apps1: "{{ apps1|default([]) +
[{'name': item.name,
'workers': range(1, item.workers + 1)|list}] }}"
loop: "{{ apps }}"
- debug:
msg: "{{ item.0.name }}#{{ item.1 }}.service"
loop: "{{ apps1|subelements('workers') }}"
gives
msg: app-name#1.service
msg: app-name#2.service
msg: app-name#3.service
msg: script-name#1.service
msg: script-name#2.service
msg: script-name#3.service
msg: script-name#4.service
msg: script-name#5.service

Ansible - create a list of unused disks

I am trying to create a list of unused disks from gather facts
-
key | search ("sd")
with_dict: "{{ ansible_devices }}"
But its only displaying one disk, like if the server have two unused disks sdb and sdc , its only displaying sdb. How can I modify my code to include all unused disks.
the way you have it, set_fact gets equal to the "current" item from the iteration, probably this is why you see only 1 disk in the final result. You need to set the disks as a list of elements and append to that list the current item.key. you can use this syntax:
set_fact:
disks: "{{ disks|default([]) + ['/dev/{{item.key}}'] }}"
to understand how many results you have in the loops of the with_dict clause, you can try a debug task:
- name: Print disk result
debug:
msg: "/dev/{{item.key}}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | search ("sd")
with_dict: "{{ ansible_devices }}"
(indentation may need fixes, i just copied from your code)
hope it helps
thanks for the reply, I used the mentioned code for printing the disk result, it was displaying multiple disks, but when I used set_fact, and the msg to display the variable, its showing like this:-
"disks": [
"/dev/sdc",
"/dev/{{item.key}}"
]
- name: Print disk result
set_fact:
disks: "{{ disks|default([]) + ['/dev/{{item.key}}'] }}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | search ("sd")
with_dict: "{{ ansible_devices }}"
- debug: var=disks
If anyone is ever looking for the answer, this works for me:
- name: Print disk result
set_fact:
disks: "{{ disks|default([]) + ['/dev/' + item.key] }}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | regex_search ("sd")
with_dict: "{{ ansible_devices }}"
- name: debug
debug:
msg: "{{disks}}"
It returns:
ok: [localhost] => {
"msg": [
"/dev/sdb"
]
}

Adding field to dict items

Consider the following play. What I am trying to do is add a field, tmp_path which is basically the key and revision appended together to each element in the scripts dict.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
scripts:
a.pl:
revision: 123
b.pl:
revision: 456
tasks:
- with_dict: "{{ scripts }}"
debug:
msg: "{{ item.key }}_{{ item.value.revision }}"
# - with_items: "{{ scripts }}"
# set_fact: {{item.value.tmp_path}}="{{item.key}}_{{item.value.revision}}"
# - with_items: "{{ scripts }}"
# debug:
# msg: "{{ item.value.tmp_path }}"
...
Obviously the commented code doesn't work, any idea how I can get this working? Is it possible to alter the scripts dict directly, or should I somehow be creating a new dict to reference instead?
By the way welcome to correct the terminology for what I am trying to do.
OK, I think I got a solution (below), at least to let me move forwards with this. Disadvantages are it has removed the structure of my dict and also seems a bit redundant having to redefine all the fields and use a new variable, If anyone can provide a better solution I will accept that instead.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
scripts:
a.pl:
revision: 123
b.pl:
revision: 456
tasks:
- with_dict: "{{ scripts }}"
debug:
msg: "{{ item.key }}_{{ item.value.revision }}"
- with_dict: "{{ scripts }}"
set_fact:
new_scripts: "{{ (new_scripts | default([])) + [ {'name': item.key, 'revision': item.value.revision, 'tmp_path': item.key ~ '_' ~ item.value.revision}] }}"
# - debug:
# var: x
# - with_dict: "{{ scripts }}"
- with_items: "{{ new_scripts }}"
debug:
msg: "{{ item.tmp_path }}"
...
BTW credit to the following question which pointed me in the right direction:
Using Ansible set_fact to create a dictionary from register results

Resources