Skip to content

Commit

Permalink
fix: proper cleanup for networks; ensure cleanup of resources
Browse files Browse the repository at this point in the history
Cause: The code was not managing network systemd quadlet units.

Consequence: Network systemd quadlet units were not being stopped and
disabled.  Subsequent runs would fail due to the network units not
being cleaned up properly.

Fix: The role manages network systemd quadlet units, including stopping
and removing.

Result: Systemd quadlet network units are properly cleaned up.

In addition - improve the removal of all types of quadlet resources,
and include code which can be used to test and debug quadlet resource
removal.
  • Loading branch information
richm committed Jul 22, 2024
1 parent 75585a0 commit a85908e
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 58 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ registries will validate TLS certs or not. The default `null` means to use
whatever is the default used by the `containers.podman.podman_image` module. You
can override this on a per-spec basis using `validate_certs`.

### podman_prune_images

Boolean - default is `false` - by default, the role will not prune unused images
when removing quadlets and other resources. Set this to `true` to tell the role
to remove unused images when cleaning up.

## Variables Exported by the Role

### podman_version
Expand Down
4 changes: 4 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,7 @@ podman_registry_certificates: []
# by the containers.podman.podman_image module
# You can override this on a per-spec basis using validate_certs
podman_validate_certs: null

# Prune images when removing quadlets/kube specs -
# this will remove all unused/unreferenced images
podman_prune_images: false
2 changes: 1 addition & 1 deletion tasks/cancel_linger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
when: __podman_xdg_stat.stat.exists

- name: Cancel linger if no more resources are in use
command: loginctl disable-linger {{ __podman_linger_user }}
command: loginctl disable-linger {{ __podman_linger_user | quote }}
when:
- __podman_xdg_stat.stat.exists
- __podman_container_info.containers | length == 0
Expand Down
188 changes: 139 additions & 49 deletions tasks/cleanup_quadlet_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,80 +33,170 @@
- name: See if quadlet file exists
stat:
path: "{{ __podman_quadlet_file }}"
register: __podman_network_stat
when: __podman_quadlet_type == "network"
register: __podman_quadlet_stat

- name: Get network quadlet network name
when:
- __podman_quadlet_type == "network"
- __podman_network_stat.stat.exists
block:
- name: Create tempdir
tempfile:
prefix: podman_
suffix: _lsr.ini
state: directory
register: __podman_network_tmpdir
delegate_to: localhost

- name: Fetch the network quadlet
fetch:
dest: "{{ __podman_network_tmpdir.path }}/network.ini"
src: "{{ __podman_quadlet_file }}"
flat: true

- name: Get the network name
set_fact:
__podman_network_name: "{{
lookup('ini', 'NetworkName section=Network file=' ~
__podman_network_tmpdir.path ~ '/network.ini') }}"
always:
- name: Remove tempdir
file:
path: "{{ __podman_network_tmpdir.path }}"
state: absent
delegate_to: localhost
- name: Parse quadlet file
include_tasks: parse_quadlet_file.yml
when: __podman_quadlet_stat.stat.exists

- name: Remove quadlet file
file:
path: "{{ __podman_quadlet_file }}"
state: absent
register: __podman_file_removed

- name: Refresh systemd # noqa no-handler
systemd:
daemon_reload: true
scope: "{{ __podman_systemd_scope }}"
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"
when: __podman_file_removed is changed # noqa no-handler

- name: Remove managed resource
command: >-
podman {{ 'rm' if __podman_quadlet_type == 'container'
else 'network rm' if __podman_quadlet_type == 'network'
else 'volume rm' if __podman_quadlet_type == 'volume' }}
{{ __podman_quadlet_resource_name | quote }}
register: __podman_rm
failed_when:
- __podman_rm is failed
- not __podman_rm.stderr is search(__str)
changed_when: __podman_rm.rc == 0
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"
vars:
__str: " found: no such "
__type_to_name: # map quadlet type to quadlet property name
container:
section: Container
name: ContainerName
network:
section: Network
name: NetworkName
volume:
section: Volume
name: VolumeName
__section: "{{ __type_to_name[__podman_quadlet_type]['section'] }}"
__name: "{{ __type_to_name[__podman_quadlet_type]['name'] }}"
__podman_quadlet_resource_name: "{{
__podman_quadlet_parsed[__section][__name]
if __section in __podman_quadlet_parsed
and __name in __podman_quadlet_parsed[__section]
else 'systemd-' ~ __podman_quadlet_name }}"
when:
- __podman_file_removed is changed # noqa no-handler
- __podman_quadlet_type in __type_to_name
- not __podman_rootless or __podman_xdg_stat.stat.exists
- __podman_service_name | length > 0
no_log: true

- name: Remove volumes
command: podman volume rm {{ item | quote }}
loop: "{{ __volume_names }}"
when:
- __podman_file_removed is changed # noqa no-handler
- not __podman_rootless or __podman_xdg_stat.stat.exists
- __podman_service_name | length == 0
- __podman_quadlet_file.endswith(".yml") or
__podman_quadlet_file.endswith(".yaml")
changed_when: true
vars:
__volumes: "{{ __podman_quadlet_parsed |
selectattr('apiVersion', 'defined') | selectattr('spec', 'defined') |
map(attribute='spec') | selectattr('volumes', 'defined') |
map(attribute='volumes') | flatten }}"
__config_maps: "{{ __volumes | selectattr('configMap', 'defined') |
map(attribute='configMap') | selectattr('name', 'defined') |
map(attribute='name') | list }}"
__secrets: "{{ __volumes | selectattr('secret', 'defined') |
map(attribute='secret') | selectattr('secretName', 'defined') |
map(attribute='secretName') | list }}"
__pvcs: "{{ __volumes | selectattr('persistentVolumeClaim', 'defined') |
map(attribute='persistentVolumeClaim') | selectattr('claimName', 'defined') |
map(attribute='claimName') | list }}"
__volume_names: "{{ __config_maps + __secrets + __pvcs }}"
no_log: true

- name: Clear parsed podman variable
set_fact:
__podman_quadlet_parsed: null

- name: Prune images no longer in use
command: podman image prune --all -f
when:
- podman_prune_images | bool
- not __podman_rootless or __podman_xdg_stat.stat.exists
changed_when: true
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"

- name: Manage linger
include_tasks: manage_linger.yml
vars:
__podman_item_state: absent

- name: Cleanup container resources
when: __podman_file_removed is changed # noqa no-handler
- name: Collect information for testing/debugging
when:
- __podman_test_debug | d(false)
- not __podman_rootless or __podman_xdg_stat.stat.exists
block:
- name: Reload systemctl # noqa no-handler
systemd:
daemon_reload: true
scope: "{{ __podman_systemd_scope }}"
- name: For testing and debugging - images
command: podman images -n
register: __podman_test_debug_images
changed_when: false
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"

- name: Prune images no longer in use
command: podman image prune -f
- name: For testing and debugging - volumes
command: podman volume ls -n
register: __podman_test_debug_volumes
changed_when: false
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"

- name: For testing and debugging - containers
command: podman ps --noheading
register: __podman_test_debug_containers
changed_when: false
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
changed_when: true
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"

- name: For testing and debugging - networks
command: podman network ls -n -q
register: __podman_test_debug_networks
changed_when: false
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"

- name: Remove network
command: podman network rm {{ __name | quote }}
changed_when: true
when: __podman_quadlet_type == "network"
- name: For testing and debugging - secrets
command: podman secret ls -n -q
register: __podman_test_debug_secrets
changed_when: false
no_log: true
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"

- name: For testing and debugging - services
service_facts:
become: "{{ __podman_rootless | ternary(true, omit) }}"
become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}"
vars:
__name: "{{ __podman_network_name if
__podman_network_name | d('') | length > 0
else 'systemd-' ~ __podman_quadlet_name }}"
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"
2 changes: 2 additions & 0 deletions tasks/handle_quadlet_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@
if __podman_quadlet_type in ['container', 'kube']
else __podman_quadlet_name ~ '-volume.service'
if __podman_quadlet_type in ['volume']
else __podman_quadlet_name ~ '-network.service'
if __podman_quadlet_type in ['network']
else none }}"

- name: Set per-container variables part 4
Expand Down
2 changes: 1 addition & 1 deletion tasks/manage_linger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
- __podman_item_state | d('present') != 'absent'
block:
- name: Enable linger if needed
command: loginctl enable-linger {{ __podman_user }}
command: loginctl enable-linger {{ __podman_user | quote }}
when: __podman_rootless | bool
args:
creates: /var/lib/systemd/linger/{{ __podman_user }}
Expand Down
57 changes: 57 additions & 0 deletions tasks/parse_quadlet_file.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
# Input:
# * __podman_quadlet_file - path to quadlet file to parse
# Output:
# * __podman_quadlet_parsed - dict
- name: Slurp quadlet file
slurp:
path: "{{ __podman_quadlet_file }}"
register: __podman_quadlet_raw
no_log: true

- name: Parse quadlet file
set_fact:
__podman_quadlet_parsed: |-
{% set rv = {} %}
{% set section = ["DEFAULT"] %}
{% for line in __val %}
{% if line.startswith("[") %}
{% set val = line.replace("[", "").replace("]", "") %}
{% set _ = section.__setitem__(0, val) %}
{% else %}
{% set ary = line.split("=", 1) %}
{% set key = ary[0] %}
{% set val = ary[1] %}
{% if key in rv.get(section[0], {}) %}
{% set curval = rv[section[0]][key] %}
{% if curval is string %}
{% set newary = [curval, val] %}
{% set _ = rv[section[0]].__setitem__(key, newary) %}
{% else %}
{% set _ = rv[section[0]][key].append(val) %}
{% endif %}
{% else %}
{% set _ = rv.setdefault(section[0], {}).__setitem__(key, val) %}
{% endif %}
{% endif %}
{% endfor %}
{{ rv }}
vars:
__val: "{{ (__podman_quadlet_raw.content | b64decode).split('\n') |
select | reject('match', '#') | list }}"
when: __podman_service_name | length > 0
no_log: true

- name: Parse quadlet yaml file
set_fact:
__podman_quadlet_parsed: "{{ __podman_quadlet_raw.content | b64decode |
from_yaml_all }}"
when:
- __podman_service_name | length == 0
- __podman_quadlet_file.endswith(".yml") or
__podman_quadlet_file.endswith(".yaml")
no_log: true

- name: Reset raw variable
set_fact:
__podman_quadlet_raw: null
2 changes: 1 addition & 1 deletion tests/files/quadlet-basic.network
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Subnet=192.168.29.0/24
Gateway=192.168.29.1
Label=app=wordpress
NetworkName=quadlet-basic
NetworkName=quadlet-basic-name
12 changes: 12 additions & 0 deletions tests/tasks/cleanup_registry.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
# SPDX-License-Identifier: MIT
---
- name: Get volume for cleanup
command: >-
podman inspect podman_registry --format
'{% raw %}{{range .}}{{range .Mounts}}{{if eq .Type
"volume"}}{{.Name}}{{end}}{{end}}{{end}}{% endraw %}'
changed_when: false
register: __registry_volume

- name: Destroy registry container
command: podman rm -f podman_registry
changed_when: true

- name: Destroy volume
command: podman volume rm {{ __registry_volume.stdout | quote }}
changed_when: true

- name: Cleanup paths
file:
path: "{{ item }}"
Expand Down
2 changes: 1 addition & 1 deletion tests/templates/quadlet-demo-mysql.container.j2
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Volume=/tmp/quadlet_demo:/var/lib/quadlet_demo:Z
Network=quadlet-demo.network
{% if podman_version is version("4.5", ">=") %}
Secret=mysql-root-password-container,type=env,target=MYSQL_ROOT_PASSWORD
HealthCmd=/usr/bin/true
HealthCmd=/bin/true
HealthOnFailure=kill
{% else %}
PodmanArgs=--secret=mysql-root-password-container,type=env,target=MYSQL_ROOT_PASSWORD
Expand Down
Loading

0 comments on commit a85908e

Please sign in to comment.