Ansible/Linux: set a group's member list - ansible

In Ansible/RedHat 7.6, I am looking for a way to clear out a group's member list, and set it to a new list.
Given a user list u1, u2, u3, u4, I want to transform the following entry in /etc/group:
mygroup:x:1234:u1,oldu17,oldu19,u3
to
mygroup:x:1234:u1,u2,u3,u4
I do not have access to the old list of group members. Also note that some members may stay in the group (in the above example, u1 and u3 are both in the old and the new list of users)
Background: We have a number of users who get access to certain applications for six months at a time, all starting at the same time (think a semester-long class in a university). Every six months, I have to clear out that group and add the new batch of users.
Of course I can think of a couple ways to accomplish this, but they all seem very hackish.
I could use lineinfile to edit /etc/group directly.
I could delete the group and re-create it (but that wouldn't even be idempotent).
I could somehow parse out the list of old group members, use jinja2 to build a list of all the users to be removed, and all the users to be added.
What I'm looking for is something less crude, and hopefully less complex.

Something like this:
---
- hosts: sample-host
gather_facts: no
vars:
managed_group: docker
valid_users:
- deploy
- telegraf
tasks:
- getent:
database: group
# 'getent_group' fact is registered by this module
- command: "gpasswd -d {{ item | quote }} {{ managed_group | quote }}"
become: yes
loop: "{{ actual_users | difference(valid_users) }}"
vars:
actual_users: "{{ getent_group[managed_group][2].split(',') }}"

Related

How to remove every part, except the end from elements in a list in ansible?

I have a problem to edit remove every part, except the end, of every elements in a list.
The list looks like this at the beginning:
['/dev/sda1', '/dev/sdb1', '/dev/sdc1', '/mnt/xxx/yyy']
Now i want to edit it, so i only have the last part of every element left.
So the list should be look like this at the end:
['/sda1', '/sdb1', '/sdc1', '/yyy']
I wanted to loop through this list and edit every element but i don't know how i can regex this.
Does anybody has an idea?
Thanks for everybody who look at this!
The task below does the job
- debug:
msg: "{{ my_list|map('regex_replace', my_regex, my_replace)|list }}"
vars:
my_list: ['/dev/sda1', '/dev/sdb1', '/dev/sdc1', '/mnt/xxx/yyy']
my_regex: '^.*/(.*)$'
my_replace: '/\1'
gives
msg:
- /sda1
- /sdb1
- /sdc1
- /yyy

Ansible split returning multiple indexes

I'm trying to use split with Ansible to return 2 different indexes, in the example below (which doesn't work) let's say I want to set my_split to 'ad':
my_string: "a-b-c-d"
my_split: "{{ my_string.split('-')[0,3]|join() }}"
All documentation I can find only shows examples returning 1 index and I can't find any references to what I'm trying to achieve
Q: Set my_split to 'ad'
A: The tasks
- set_fact:
my_split: "{{ [0,3]|map('extract',my_string.split('-'))|join() }}"
- debug:
var: my_split
give
"my_split": "ad"
The problem is the selection of the first and fourth elements of the sequence. The expression below
my_string.split('-')[0,3]
fails
The error was: list object has no element (0, 3)
Instead, it's possible to use map and extract. See Extracting values from containers.

The right way to extract a list of attributes from a list of maps in Ansible

I'm looking for a non-ugly way to extract a list of particular values from a list of maps in Ansible. I can find some ways to do it, for example here: here, but everything I have seen is very ugly, and it feels like there should be a way where it is clearer what is being done to someone reading it in future. I could write a filter, but it feels like this should be unnecessary since this must come up relatively regularly.
I have a data structure like so in Ansible:
interfaces:
- name: eth0
subnet: 192.168.2
netmask: 255.255.255.0
static_dhcp_hosts:
- {name: "hosta", mac: "00:01:02:03:04:05", ip: "192.168.2.20"}
- name: eth1
subnet: 192.168.5
netmask: 255.255.255.0
static_dhcp_hosts:
- {name: "hostb", mac: "00:02:03:04:05:06", ip: "192.168.5.20"}
- {name: "hostc", mac: "00:03:04:05:06:07", ip: "192.168.5.21"}
I'd like to get a space separated list of the interface names, so
eth0 eth1
Obviously this is just example data, the actual top level list has 10 elements for one host. I know that I can use the join filter to get from a list of interfaces to the string I want and how to do that.
Can anyone suggest a nice way to make the list, that is readable to future maintainers (code/configuration should be self-documenting as far as possible (and no further))?
I am looking to do something like
{% for interface in interfaces %}{{ interface.name }} {% endfor %}
or
" ".join([ interface['name'] for interface in interfaces ])
in Python.
but, AFAIK, you can't, or it is considered bad practice to, use jinja2 loops like this in a role's task/main.yml, and, as I said, it feels like it shouldn't be necessary to use a custom filter for this.
(This role isn't just configuring a DHCP server, so please don't just suggest a pre-existing role that does that, that wouldn't solve my issue).
Any non-ugly way to do this would be much appreciated, as would confirmation from people that there is no non-ugly way.
I am using Ansible 2.3, but I'm interested in answers still even if they only work in later versions.
Edit:
The following:
"{{ internal_interfaces | items2dict(key_name='name',value_name='name') | list | join(\" \") }}"
works, and is the least ugly I can think of. It makes a dictionary from the list, with both the key and values being from the name attribute of the dictionaries in the list, and then casts this dictionary to a list, which just gives a list of keys. I'd still like something less obtuse and ugly if anyione can think of anything, or for any Ansible gurus to reply if they think there is nothing nicer.
Map and join is what you require:
- set_fact:
interface_names: "{{ interfaces | map(attribute='name') | join(' ') }}"
OK. I am being stupid. There is a nice way to do this:
"{{ interfaces | map(attribute='name') | join(\" \") }}"
The output from map is a generator which generates a list of the interface names, and join turns this into the string I want. Perfect.

Changing list value in Ansible

I have inventory with a very complicated structure. For my specific installation I want to override only some values. For example, I have structure:
---
System:
atr1: 47
config:
- nodes:
- logger:
id: 'all'
svr: 'IEW'
- Database:
constr: 'login/pass#db'
atr2: 'some value'
I want to override severity of the logger, i.e. add statistic information
svr: 'IEWS'. I want to provide an override within --extra-vars parameter.
In ansible.cfg -> hash_behaviour = merge
I don't want to use something like - svr: "{{ svr_custom | default('IEW') }}", because there are too many parameters, and thus it will be difficult to write the entire inventory in such way.
I read about combine filter, but I can't use it, when I had to override only one item in hash.
How can I achieve my goal?
The way you found is the simplest one. It's verbose to write but very easy to debug and to fix.
If you REALLY want to shrink this job, you can write your own lookup plugin. (https://docs.ansible.com/ansible/2.5/plugins/lookup.html).
From my experience, I really want to say that direct and dumb approach (write verbose) is much better for overall maintainability. A next person will see a dumb dump (pun intended) which is easy to fix, not a some obscure python snippet.
To make life easier you may want to store this configuration as a separate file (with all jinja pieces) and use lookup (st right from docs):
# Since 2.4, you can pass in variables during evaluation
- debug: msg="{{ lookup('template', './some_template.j2', template_vars=dict(x=42)) }} is evaluated with x=42"
Moreover, you can use Jinja's |from_yaml (or from_json) to convert loaded and processed template into data structure.
I read about combine filter, but I can't use it, when I had to override only one item in hash.
Why is that? Wouldn't new_svr defined in --extra-vars achieve what you want?
- set_fact:
System: "{{ System | combine({'config':[{'nodes':[{'logger':{'svr':new_svr }}]}]}, recursive=True) }}"

How do I concatenate all items from listB onto all items from listA?

I'm trying to figure out how to create an Ansible list that is the result of appending every string from listB onto every string in listA, effectively multiplying two lists of strings together.
In python I'd do this:
["-".join((x, y)) for x in listA for y in listB]
In other languages I'd nest one for loop inside another.
I can't figure out an analogue to this in Ansible.
My reason for doing this is to allow a role to automatically determine the site a host is in. All of my hosts are in at least one group named for the physical location and the type of site (development, staging, production). So for example, the New York production site's group would be "nyc-prod". I need my playbook to be able to pick out the site name from the complete list of groups the host is in.
Given a list of all possible sites, I can intersect that list with the list of groups a host is in, and the resulting single-element list would contain the current hosts's site.
I could brute force this by hand-writing a list of all possible site-type combinations into group_vars/all or vars/main.yml in a role, but with a large number of sites this multiplies out to a massive list that would have to be maintained. So my approach has been to try to programmatically construct the list from the much shorter list of physical sites and the list of types.
If I could embed arbitrary python in a jinja template I could do something like this in tasks/main.yml:
# sites contains a list of all physical locations ['nyc', 'sfo', ...]
- name: get groups
debug:
var: group_names
register: groups
- name: find my site group
set_fact:
my_site: "{% site for site in {{groups}} if site in ['-'.join((x, y)) for x in {{sites}} for y in ['dev', 'stage', 'prod']] }%"
- name: print the group
debug:
msg: "My site is {{ my_site }}"
That obviously doesn't work, however.
Has anyone solved this problem before?
Note: I'm using Ansible 2.2.1.0
Could be achieved in several ways probably.
Here's a fix for the Jinja2 template (you should use Jinja2 syntax not Python inside):
vars:
list1:
- list1element1
- list1element2
list2:
- list2element1
- list2element2
tasks:
- set_fact:
list3: "{% for prefix in list1 %}{% for postfix in list2 %}{{ prefix }}-{{ postfix }} {% endfor %}{% endfor %}"
- debug:
var: list3.split(' ')

Resources