Skip to content

Commit ea7290c

Browse files
committed
feat: add support for user units
Feature: The role can manage user units in addition to system units. Each item in each input list can be a string or a `dict` consisting of the item (file, template, or unit), a user name, and a state (for files and templates). The role will not create users and will give an error if a non-existent user is specified. Reason: The role should allow management of user units. Result: The role can manage user units. Signed-off-by: Rich Megginson <rmeggins@redhat.com>
1 parent a2039b3 commit ea7290c

File tree

6 files changed

+159
-4
lines changed

6 files changed

+159
-4
lines changed

README.md

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ ansible-galaxy collection install -vv -r meta/collection-requirements.yml
2424

2525
List of variables consumed by the role follows, note that none of them is mandatory.
2626

27+
Each of the variables can either be a list of strings, or a list of `dicts`.
28+
29+
The list of strings form assumes that the items to be managed are system units
30+
owned by `root`, and for files, assumes that the files should be `present`.
31+
32+
The list of `dict` form looks like this:
33+
34+
```yaml
35+
systemd_unit_files:
36+
- item: some.service
37+
user: my_user
38+
state: [present|absent]
39+
```
40+
41+
Use the `dict` form to manage user units, and to remove unit files. If using
42+
user units, the role will manage lingering for those users.
43+
2744
### systemd_unit_files
2845

2946
List of systemd unit file names that should be deployed to managed nodes.
@@ -79,7 +96,7 @@ List of unit files that shall be unmasked via systemd.
7996

8097
This variable is used to handle reboots required by transactional updates. If a transactional update requires a reboot, the role will proceed with the reboot if systemd_transactional_update_reboot_ok is set to true. If set to false, the role will notify the user that a reboot is required, allowing for custom handling of the reboot requirement. If this variable is not set, the role will fail to ensure the reboot requirement is not overlooked.
8198

82-
Example of setting the variables:
99+
Example of setting the variables for the simple list of strings format:
83100

84101
```yaml
85102
systemd_unit_files:
@@ -96,12 +113,49 @@ systemd_enabled_units:
96113
- bar.service
97114
```
98115

116+
Example of setting the variables for the list of `dict` format:
117+
118+
```yaml
119+
systemd_unit_files:
120+
- item: foo.service
121+
user: root
122+
state: present
123+
- item: bar.service
124+
user: my_user
125+
state: absent
126+
systemd_dropins:
127+
- item: cups.service.conf.j2
128+
user: root
129+
state: present
130+
- item: avahi-daemon.service.conf.j2
131+
user: my_user
132+
state: absent
133+
systemd_started_units:
134+
- item: foo.service
135+
user: root
136+
- item: bar.service
137+
user: my_user
138+
systemd_enabled_units:
139+
- item: foo.service
140+
user: root
141+
- item: bar.service
142+
user: my_user
143+
```
144+
99145
## Variables Exported by the Role
100146

101147
### `systemd_units`
102148

103-
Variable shall contain a list of dictionaries where each entry describes state of one systemd unit
104-
present on the managed host.
149+
The variable is a `dict`. Each key is the name of a systemd unit. Each value
150+
is a dict with fields that describe the state of that systemd unit present on
151+
the managed host for the system scope.
152+
153+
### `systemd_units_user`
154+
155+
Variable shall contain a dict. Each key is the name of a user given in one of
156+
the lists passed to the role, and `root` (even if `root` is not given). Each
157+
value is a dict of systemd units for that user, or system units for `root`, in
158+
the format of `systemd_units` above.
105159

106160
## Example Playbook
107161

@@ -112,7 +166,10 @@ present on the managed host.
112166
systemd_unit_file_templates:
113167
- foo.service.j2
114168
systemd_started_units:
115-
- foo.service
169+
- item: foo.service
170+
user: root
171+
- item: bar.service
172+
user: my_user
116173
systemd_enabled_units:
117174
- foo.service
118175
roles:
@@ -130,3 +187,4 @@ MIT
130187
## Author
131188

132189
Michal Sekletar <msekleta@redhat.com>
190+
Rich Megginson <rmeggins@redhat.com>

tasks/cancel_linger.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-License-Identifier: MIT
2+
# Inputs:
3+
# __systemd_user_info
4+
---
5+
- name: Check if linger for users can be cancelled
6+
vars:
7+
__systemd_linger_users: "{{ __systemd_user_info | dict2items |
8+
rejectattr('key', 'match', '^root$') | list }}"
9+
when: __systemd_linger_users | length > 0
10+
block:
11+
- name: Cancel linger for given user
12+
include_tasks: cancel_linger_for_user.yml
13+
vars:
14+
__systemd_linger_data: "{{ __systemd_linger_user_data.value }}"
15+
__systemd_linger_user: "{{ __systemd_linger_user_data.key }}"
16+
loop: "{{ __systemd_linger_users }}"
17+
loop_control:
18+
loop_var: __systemd_linger_user_data

tasks/cancel_linger_for_user.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# SPDX-License-Identifier: MIT
2+
# Inputs:
3+
# __systemd_linger_user_data
4+
---
5+
- name: Get unit files for user
6+
find:
7+
path: "{{ __systemd_linger_data['units_dir'] }}"
8+
register: __systemd_find
9+
10+
- name: Cancel linger if no files
11+
command: loginctl disable-linger {{ __systemd_linger_user | quote }}
12+
changed_when: true
13+
register: __systemd_cancel_linger
14+
when: __systemd_find.matched == 0
15+
args:
16+
removes: /var/lib/systemd/linger/{{ __systemd_linger_user }}
17+
18+
- name: Wait for user session to exit closing state # noqa no-handler
19+
command: loginctl show-user --value -p State {{ __systemd_linger_user | quote }}
20+
register: __systemd_user_state
21+
changed_when: false
22+
until: __systemd_user_state.stdout != "closing"
23+
when: __systemd_cancel_linger is changed
24+
ignore_errors: true
25+
26+
# see https://github.com/systemd/systemd/issues/26744#issuecomment-2261509208
27+
- name: Handle user stuck in closing state
28+
vars:
29+
__pat: "Failed to get user: User ID .* is not logged in or lingering"
30+
when:
31+
- __systemd_cancel_linger is changed
32+
- __systemd_user_state is failed
33+
- not __systemd_user_state.stderr is match(__pat)
34+
block:
35+
- name: Stop logind
36+
service:
37+
name: systemd-logind
38+
state: stopped
39+
40+
- name: Wait for user session to exit closing state
41+
command: loginctl show-user --value -p State {{ __systemd_linger_user | quote }}
42+
changed_when: false
43+
register: __systemd_user_state
44+
until: __systemd_user_state.stderr is match(__pat) or
45+
__systemd_user_state.stdout != "closing"
46+
failed_when:
47+
- not __systemd_user_state.stderr is match(__pat)
48+
- __systemd_user_state.stdout == "closing"
49+
50+
- name: Restart logind
51+
service:
52+
name: systemd-logind
53+
state: started

tasks/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,6 @@
133133
set_fact:
134134
systemd_units: "{{ ansible_facts['systemd_units']
135135
if 'systemd_units' in ansible_facts else {} }}"
136+
137+
- name: Cancel linger for users if necessary
138+
include_tasks: cancel_linger.yml

tasks/manage_unit_files.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,19 @@
7979
owner: "{{ item.user }}" # only needed for daemon_reload which happens later
8080
loop: "{{ __systemd_absent }}"
8181
register: __systemd_unit_files_absent_result
82+
83+
- name: Find files in dropins directory
84+
find:
85+
path: "{{ __path }}"
86+
loop: "{{ __systemd_absent }}"
87+
when: __systemd_list_name == "systemd_dropins"
88+
register: __systemd_find
89+
90+
- name: Remove dropin directory if no more files
91+
file:
92+
path: "{{ item['invocation']['module_args']['path'] }}"
93+
state: absent
94+
loop: "{{ __systemd_find.results | d([]) }}"
95+
when:
96+
- __systemd_list_name == "systemd_dropins"
97+
- item.matched == 0

tests/tests_user_units.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@
223223
{{ '.'.join(__dest.split('.')[:-1]) }}.d/\
224224
99-override.conf"
225225

226+
- name: Verify no lingering
227+
stat:
228+
path: /var/lib/systemd/linger/{{ item.name }}
229+
register: __stat
230+
failed_when: __stat.stat.exists
231+
loop: "{{ __users }}"
232+
226233
rescue:
227234
- name: Get journald information
228235
command: journalctl -ex

0 commit comments

Comments
 (0)