Skip to content

Commit 7a30745

Browse files
authored
Add diff and change detection to systemd generation (#608)
Fix #339 Signed-off-by: Sagi Shnaidman <[email protected]>
1 parent cc34c2b commit 7a30745

File tree

5 files changed

+203
-28
lines changed

5 files changed

+203
-28
lines changed

plugins/module_utils/podman/common.py

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,34 +96,93 @@ def run_generate_systemd_command(module, module_params, name, version):
9696
return rc, systemd, err
9797

9898

99+
def compare_systemd_file_content(file_path, file_content):
100+
if not os.path.exists(file_path):
101+
# File does not exist, so all lines in file_content are different
102+
return '', file_content
103+
# Read the file
104+
with open(file_path, 'r') as unit_file:
105+
current_unit_file_content = unit_file.read()
106+
107+
# Function to remove comments from file content
108+
def remove_comments(content):
109+
return "\n".join([line for line in content.splitlines() if not line.startswith('#')])
110+
111+
# Remove comments from both file contents before comparison
112+
current_unit_file_content_nocmnt = remove_comments(current_unit_file_content)
113+
unit_content_nocmnt = remove_comments(file_content)
114+
if current_unit_file_content_nocmnt == unit_content_nocmnt:
115+
return None
116+
117+
# Get the different lines between the two contents
118+
diff_in_file = [line
119+
for line in unit_content_nocmnt.splitlines()
120+
if line not in current_unit_file_content_nocmnt.splitlines()]
121+
diff_in_string = [line
122+
for line in current_unit_file_content_nocmnt.splitlines()
123+
if line not in unit_content_nocmnt.splitlines()]
124+
125+
return diff_in_string, diff_in_file
126+
127+
99128
def generate_systemd(module, module_params, name, version):
100-
empty = {}
129+
result = {
130+
'changed': False,
131+
'systemd': {},
132+
'diff': {},
133+
}
101134
sysconf = module_params['generate_systemd']
102135
rc, systemd, err = run_generate_systemd_command(module, module_params, name, version)
103136
if rc != 0:
104137
module.log(
105138
"PODMAN-CONTAINER-DEBUG: Error generating systemd: %s" % err)
106-
return empty
139+
return result
107140
else:
108141
try:
109142
data = json.loads(systemd)
143+
result['systemd'] = data
110144
if sysconf.get('path'):
111145
full_path = os.path.expanduser(sysconf['path'])
112146
if not os.path.exists(full_path):
113147
os.makedirs(full_path)
148+
result['changed'] = True
114149
if not os.path.isdir(full_path):
115150
module.fail_json("Path %s is not a directory! "
116151
"Can not save systemd unit files there!"
117152
% full_path)
118153
for file_name, file_content in data.items():
119154
file_name += ".service"
155+
if not os.path.exists(os.path.join(full_path, file_name)):
156+
result['changed'] = True
157+
if result['diff'].get('before') is None:
158+
result['diff'] = {'before': {}, 'after': {}}
159+
result['diff']['before'].update({f'systemd_{file_name}.service': ''})
160+
result['diff']['after'].update({f'systemd_{file_name}.service': file_content})
161+
162+
else:
163+
diff_ = compare_systemd_file_content(os.path.join(full_path, file_name), file_content)
164+
if diff_:
165+
result['changed'] = True
166+
if result['diff'].get('before') is None:
167+
result['diff'] = {'before': {}, 'after': {}}
168+
result['diff']['before'].update({f'systemd_{file_name}.service': "\n".join(diff_[0])})
169+
result['diff']['after'].update({f'systemd_{file_name}.service': "\n".join(diff_[1])})
120170
with open(os.path.join(full_path, file_name), 'w') as f:
121171
f.write(file_content)
122-
return data
172+
diff_before = "\n".join(
173+
[f"{j} - {k}" for j, k in result['diff'].get('before', {}).items() if 'PIDFile' not in k]).strip()
174+
diff_after = "\n".join(
175+
[f"{j} - {k}" for j, k in result['diff'].get('after', {}).items() if 'PIDFile' not in k]).strip()
176+
if diff_before or diff_after:
177+
result['diff']['before'] = diff_before + "\n"
178+
result['diff']['after'] = diff_after + "\n"
179+
else:
180+
result['diff'] = {}
181+
return result
123182
except Exception as e:
124183
module.log(
125184
"PODMAN-CONTAINER-DEBUG: Error writing systemd: %s" % e)
126-
return empty
185+
return result
127186

128187

129188
def delete_systemd(module, module_params, name, version):

plugins/module_utils/podman/podman_container_lib.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,11 +1567,19 @@ def update_container_result(self, changed=True):
15671567
self.results.update({'diff': self.container.diff})
15681568
if self.module.params['debug'] or self.module_params['debug']:
15691569
self.results.update({'podman_version': self.container.version})
1570+
sysd = generate_systemd(self.module,
1571+
self.module_params,
1572+
self.name,
1573+
self.container.version)
1574+
self.results['changed'] = changed or sysd['changed']
15701575
self.results.update(
1571-
{'podman_systemd': generate_systemd(self.module,
1572-
self.module_params,
1573-
self.name,
1574-
self.container.version)})
1576+
{'podman_systemd': sysd['systemd']})
1577+
if sysd['diff']:
1578+
if 'diff' not in self.results:
1579+
self.results.update({'diff': sysd['diff']})
1580+
else:
1581+
self.results['diff']['before'] += sysd['diff']['before']
1582+
self.results['diff']['after'] += sysd['diff']['after']
15751583

15761584
def make_started(self):
15771585
"""Run actions if desired state is 'started'."""

plugins/modules/podman_generate_systemd.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@
219219
import os
220220
from ansible.module_utils.basic import AnsibleModule
221221
import json
222-
222+
from ansible_collections.containers.podman.plugins.module_utils.podman.common import compare_systemd_file_content
223223

224224
RESTART_POLICY_CHOICES = [
225225
'no-restart',
@@ -447,25 +447,7 @@ def generate_systemd(module):
447447
)
448448

449449
# See if we need to write the unit file, default yes
450-
need_to_write_file = True
451-
# If the unit file already exist, compare it with the
452-
# generated content
453-
if os.path.exists(unit_file_full_path):
454-
# Read the file
455-
with open(unit_file_full_path, 'r') as unit_file:
456-
current_unit_file_content = unit_file.read()
457-
# If current unit file content is the same as the
458-
# generated content
459-
# Remove comments from files, before comparing
460-
current_unit_file_content_nocmnt = "\n".join([
461-
line for line in current_unit_file_content.splitlines()
462-
if not line.startswith('#')])
463-
unit_content_nocmnt = "\n".join([
464-
line for line in unit_content.splitlines()
465-
if not line.startswith('#')])
466-
if current_unit_file_content_nocmnt == unit_content_nocmnt:
467-
# We don't need to write it
468-
need_to_write_file = False
450+
need_to_write_file = bool(compare_systemd_file_content(unit_file_full_path, unit_content))
469451

470452
# Write the file, if needed
471453
if need_to_write_file:
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Systemd generation
2+
- containers.podman.podman_container:
3+
executable: "{{ test_executable | default('podman') }}"
4+
name: idempotency
5+
state: absent
6+
7+
- containers.podman.podman_container:
8+
executable: "{{ test_executable | default('podman') }}"
9+
image: "{{ idem_image }}"
10+
name: idempotency
11+
state: started
12+
command: 1h
13+
generate_systemd:
14+
path: /tmp/
15+
restart_policy: always
16+
time: 120
17+
no_header: true
18+
names: true
19+
pod_prefix: whocares
20+
separator: zzzz
21+
container_prefix: contain
22+
register: system0
23+
24+
- name: Check if the result is changed
25+
assert:
26+
that:
27+
- system0 is changed
28+
29+
- containers.podman.podman_container:
30+
executable: "{{ test_executable | default('podman') }}"
31+
image: "{{ idem_image }}"
32+
name: idempotency
33+
state: started
34+
command: 1h
35+
generate_systemd:
36+
path: /tmp/
37+
restart_policy: always
38+
time: 120
39+
no_header: true
40+
names: true
41+
pod_prefix: whocares
42+
separator: zzzz
43+
container_prefix: contain
44+
register: system1
45+
46+
- name: Check if the result is not changed
47+
assert:
48+
that:
49+
- system1 is not changed
50+
51+
- name: Remove the systemd unit file
52+
ansible.builtin.file:
53+
path: /tmp/containzzzzidempotency.service
54+
state: absent
55+
56+
- containers.podman.podman_container:
57+
executable: "{{ test_executable | default('podman') }}"
58+
image: "{{ idem_image }}"
59+
name: idempotency
60+
state: started
61+
command: 1h
62+
generate_systemd:
63+
path: /tmp/
64+
restart_policy: always
65+
time: 120
66+
no_header: true
67+
names: true
68+
pod_prefix: whocares
69+
separator: zzzz
70+
container_prefix: contain
71+
register: system2
72+
73+
- name: Check if the result is changed
74+
assert:
75+
that:
76+
- system2 is changed
77+
78+
- containers.podman.podman_container:
79+
executable: "{{ test_executable | default('podman') }}"
80+
image: "{{ idem_image }}"
81+
name: idempotency
82+
state: started
83+
command: 1h
84+
generate_systemd:
85+
path: /tmp/
86+
restart_policy: always
87+
time: 120
88+
no_header: true
89+
names: true
90+
pod_prefix: whocares
91+
separator: zzzz
92+
container_prefix: contain
93+
register: system3
94+
95+
- name: Check if the result is not changed
96+
assert:
97+
that:
98+
- system3 is not changed
99+
100+
- name: Add string to change the systemd unit file
101+
ansible.builtin.shell: echo 'test=onetwo' >> /tmp/containzzzzidempotency.service
102+
103+
- containers.podman.podman_container:
104+
executable: "{{ test_executable | default('podman') }}"
105+
image: "{{ idem_image }}"
106+
name: idempotency
107+
state: started
108+
command: 1h
109+
generate_systemd:
110+
path: /tmp/
111+
restart_policy: always
112+
time: 120
113+
no_header: true
114+
names: true
115+
pod_prefix: whocares
116+
separator: zzzz
117+
container_prefix: contain
118+
register: system4
119+
120+
- name: Check if the result is changed
121+
assert:
122+
that:
123+
- system4 is changed

tests/integration/targets/podman_container_idempotency/tasks/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
- name: Test idempotency of containers in pods
2424
include_tasks: idem_pods.yml
2525

26+
- name: Test idempotency of systemd generation
27+
include_tasks: idem_systemd.yml
28+
2629
- name: Test idempotency of other settings
2730
include_tasks: idem_all.yml
2831

0 commit comments

Comments
 (0)