Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for Pod quadlets #190

Merged
merged 1 commit into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The role requires podman version 4.4 or later for quadlet support and secret
support.
The role requires podman version 4.5 or later for support for using healthchecks
(only supported when using quadlet Container types).
The role requires podman version 5.0 or later for support for using
[Pod quadlet types](https://github.com/containers/podman/releases/tag/v5.0.0).

### Collection requirements

Expand Down Expand Up @@ -196,6 +198,13 @@ quadlet container unit spec on the managed node.
*NOTE*: When removing quadlets, you must remove networks *last*. You cannot
remove a network that is in use by a container.

*NOTE*: When specifying a `Pod` for a `Container` to use, you must add a `.pod`
to the podname e.g. `Pod=my-name.pod`. The pod must already exist and be
running, so specify any pods first in `podman_quadlet_specs` before the
containers that use them.
See [podman quadlet doc](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html#pod)
for more information.

### podman_secrets

This is a list of secret specs in almost the same format as used by
Expand Down
16 changes: 15 additions & 1 deletion tasks/cleanup_quadlet_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
command: >-
podman {{ 'rm' if __podman_quadlet_type == 'container'
else 'network rm' if __podman_quadlet_type == 'network'
else 'volume rm' if __podman_quadlet_type == 'volume' }}
else 'volume rm' if __podman_quadlet_type == 'volume'
else 'pod rm' if __podman_quadlet_type == 'pod' }}
{{ __podman_quadlet_resource_name | quote }}
register: __podman_rm
failed_when:
Expand All @@ -82,6 +83,9 @@
volume:
section: Volume
name: VolumeName
pod:
section: Pod
name: PodName
__section: "{{ __type_to_name[__podman_quadlet_type]['section'] }}"
__name: "{{ __type_to_name[__podman_quadlet_type]['name'] }}"
__podman_quadlet_resource_name: "{{
Expand Down Expand Up @@ -194,6 +198,16 @@
environment:
XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}"

- name: For testing and debugging - pods
command: podman pod ls -n -q
register: __podman_test_debug_pods
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) }}"
Expand Down
16 changes: 12 additions & 4 deletions tasks/handle_quadlet_spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@
if __podman_quadlet_type in ['volume']
else __podman_quadlet_name ~ '-network.service'
if __podman_quadlet_type in ['network']
else __podman_quadlet_name ~ '-pod.service'
if __podman_quadlet_type in ['pod']
else none }}"

- name: Set per-container variables part 4
Expand Down Expand Up @@ -164,6 +166,14 @@
__volumes_from_kube_spec
if podman_create_host_directories else [] }}"
vars:
__volumes_from_container: "{{
__podman_quadlet_spec.get('Container', {}).get('Volume', []) |
map('regex_search', '^([^:]+):.+$') | reject('search', '[.]volume$') |
select | list }}"
__volumes_from_pod: "{{
__podman_quadlet_spec.get('Pod', {}).get('Volume', []) |
map('regex_search', '^([^:]+):.+$') | reject('search', '[.]volume$') |
select | list }}"
__file: "{{ __podman_quadlet_spec_item['file']
if 'file' in __podman_quadlet_spec_item
else none }}"
Expand All @@ -184,11 +194,9 @@
regex_findall('(?m)^Volume=\"?([^:]+):.+$') |
reject('search', '[.]volume$') | list
if __podman_quadlet_str
and __podman_quadlet_type == 'container'
and __podman_quadlet_type in ['container', 'pod']
and podman_create_host_directories
else __podman_quadlet_spec.get('Container', {}).get('Volume', []) |
map('regex_search', '^([^:]+):.+$') | reject('search', '[.]volume$') |
select | list
else __volumes_from_container + __volumes_from_pod
if podman_create_host_directories else [] }}"
__host_paths: "{{ __podman_kube | selectattr('spec', 'defined') |
map(attribute='spec') | selectattr('volumes', 'defined') |
Expand Down
41 changes: 41 additions & 0 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,47 @@
- podman_version is version("4.4", "<")
- not podman_fail_if_too_old | d(true)

- name: Podman package version must be 5.0 or later for Pod quadlets
fail:
msg: >
podman package version {{ podman_version }} is too old -
must be 5.0 or later to use Pod quadlets
when:
- __has_type_pod or __has_pod_file_ext or __has_pod_file_src_ext or
__has_pod_template_src_ext or __has_pod_template_src_ext_j2
- podman_version is version("5.0", "<")
- podman_fail_if_too_old | d(true)
vars:
__has_type_pod: "{{ podman_quadlet_specs | selectattr('type', 'defined') |
selectattr('type', 'match', '^pod$') | list | length > 0 }}"
__has_pod_file_ext: "{{ podman_quadlet_specs | selectattr('file', 'defined') |
selectattr('file', 'search', '.pod$') | list | length > 0 }}"
__has_pod_file_src_ext: "{{ podman_quadlet_specs | selectattr('file_src', 'defined') |
selectattr('file_src', 'search', '.pod$') | list | length > 0 }}"
__has_pod_template_src_ext: "{{ podman_quadlet_specs | selectattr('template_src', 'defined') |
selectattr('template_src', 'search', '.pod$') | list | length > 0 }}"
__has_pod_template_src_ext_j2: "{{ podman_quadlet_specs | selectattr('template_src', 'defined') |
selectattr('template_src', 'search', '.pod.j2$') | list | length > 0 }}"

- name: Podman package version must be 5.0 or later for Pod quadlets
meta: end_host
when:
- __has_type_pod or __has_pod_file_ext or __has_pod_file_src_ext or
__has_pod_template_src_ext or __has_pod_template_src_ext_j2
- podman_version is version("5.0", "<")
- not podman_fail_if_too_old | d(true)
vars:
__has_type_pod: "{{ podman_quadlet_specs | selectattr('type', 'defined') |
selectattr('type', 'match', '^pod$') | list | length > 0 }}"
__has_pod_file_ext: "{{ podman_quadlet_specs | selectattr('file', 'defined') |
selectattr('file', 'search', '.pod$') | list | length > 0 }}"
__has_pod_file_src_ext: "{{ podman_quadlet_specs | selectattr('file_src', 'defined') |
selectattr('file_src', 'search', '.pod$') | list | length > 0 }}"
__has_pod_template_src_ext: "{{ podman_quadlet_specs | selectattr('template_src', 'defined') |
selectattr('template_src', 'search', '.pod$') | list | length > 0 }}"
__has_pod_template_src_ext_j2: "{{ podman_quadlet_specs | selectattr('template_src', 'defined') |
selectattr('template_src', 'search', '.pod.j2$') | list | length > 0 }}"

- name: Check user and group information
include_tasks: handle_user_group.yml
vars:
Expand Down
2 changes: 2 additions & 0 deletions templates/systemd.j2
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@
{% for key, value in data.items() %}
{{ render_option(key, value) -}}
{% endfor %}
{% else %}
[{{ __podman_quadlet_type | capitalize }}]
{% endfor %}
207 changes: 207 additions & 0 deletions tests/tests_quadlet_pod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# SPDX-License-Identifier: MIT
---
- name: Ensure that the role can manage quadlet pods
hosts: all
gather_facts: true # for machine_id
vars_files:
- vars/test_vars.yml
vars:
podman_use_copr: false # disable copr for CI testing
podman_fail_if_too_old: false
__podman_quadlet_specs:
- name: quadlet-pod-pod
type: pod
Pod:
PodName: quadlet-pod
- name: quadlet-pod-container
type: container
Install:
WantedBy: default.target
Container:
Image: "{{ test_image }}"
ContainerName: quadlet-pod-container
Pod: quadlet-pod-pod.pod
Exec: /bin/busybox-extras httpd -f -p 80
tasks:
- name: Run test
block:
- name: Run the role - root
include_role:
name: linux-system-roles.podman
vars:
podman_quadlet_specs: "{{ __podman_quadlet_specs }}"

- name: Check files
command: cat {{ __dir }}/{{ item }}
changed_when: false
vars:
__dir: /etc/containers/systemd
loop:
- quadlet-pod-container.container
- quadlet-pod-pod.pod

- name: Check pod
command: podman pod inspect quadlet-pod --format {{ __fmt | quote }}
register: __check_pod
changed_when: false
failed_when: not "quadlet-pod-container" in __check_pod.stdout_lines
vars:
__fmt: "{% raw %}{{range .Containers}}{{.Name}}\n{{end}}{% endraw %}"

- name: Create user for testing
user:
name: user_quadlet_pod
uid: 2223

- name: Run the role - user
include_role:
name: linux-system-roles.podman
vars:
podman_run_as_user: user_quadlet_pod
podman_quadlet_specs: "{{ __podman_quadlet_specs }}"
podman_pull_retry: true

- name: Check files
command: cat {{ __dir }}/{{ item }}
changed_when: false
vars:
__dir: /home/user_quadlet_pod/.config/containers/systemd
loop:
- quadlet-pod-container.container
- quadlet-pod-pod.pod

- name: Check pod
command: podman pod inspect quadlet-pod --format {{ __fmt | quote }}
register: __check_pod
changed_when: false
failed_when: not "quadlet-pod-container" in __check_pod.stdout_lines
vars:
__fmt: "{% raw %}{{range .Containers}}{{.Name}}\n{{end}}{% endraw %}"
become: true
become_user: user_quadlet_pod
environment:
XDG_RUNTIME_DIR: /run/user/2223

- name: Ensure linger
stat:
path: /var/lib/systemd/linger/user_quadlet_pod
register: __stat
failed_when: not __stat.stat.exists

# must clean up in the reverse order of creating - and
# ensure networks are removed last
- name: Cleanup user
include_role:
name: linux-system-roles.podman
vars:
podman_prune_images: true
__podman_test_debug: true
podman_run_as_user: user_quadlet_pod
__absent: {"state":"absent"}
podman_quadlet_specs: "{{ __podman_quadlet_specs | reverse |
map('combine', __absent) | list }}"

- name: Ensure no resources
assert:
that:
- __podman_test_debug_images.stdout == ""
- __podman_test_debug_networks.stdout_lines |
reject("match", "^podman$") |
reject("match", "^podman-default-kube-network$") |
list | length == 0
- __podman_test_debug_volumes.stdout == ""
- __podman_test_debug_containers.stdout == ""
- __podman_test_debug_secrets.stdout == ""
- __podman_test_debug_pods.stdout == ""
- ansible_facts["services"] | dict2items |
rejectattr("value.status", "match", "not-found") |
selectattr("key", "match", "quadlet-demo") |
list | length == 0

- name: Ensure no linger
stat:
path: /var/lib/systemd/linger/user_quadlet_pod
register: __stat
failed_when: __stat.stat.exists

rescue:
- name: Debug3
shell: |
set -x
set -o pipefail
exec 1>&2
#podman volume rm --all
#podman network prune -f
podman volume ls
podman network ls
podman secret ls
podman container ls
podman pod ls
podman images
systemctl list-units | grep quadlet
systemctl list-unit-files | grep quadlet
ls -alrtF /etc/containers/systemd
/usr/libexec/podman/quadlet -dryrun -v -no-kmsg-log
changed_when: false

- name: Check AVCs
command: grep type=AVC /var/log/audit/audit.log
changed_when: false
failed_when: false

- name: Dump journal
command: journalctl -ex
changed_when: false
failed_when: true

always:
- name: Cleanup
when: false
block:
- name: Cleanup user
include_role:
name: linux-system-roles.podman
vars:
podman_prune_images: true
podman_run_as_user: user_quadlet_pod
__absent: {"state":"absent"}
podman_quadlet_specs: "{{ __podman_quadlet_specs | reverse |
map('combine', __absent) | list }}"

- name: Remove test user
user:
name: user_quadlet_pod
state: absent

- name: Cleanup system - root
include_role:
name: linux-system-roles.podman
vars:
podman_prune_images: true
__podman_test_debug: true
__absent: {"state":"absent"}
podman_quadlet_specs: "{{ __podman_quadlet_specs | reverse |
map('combine', __absent) | list }}"

- name: Ensure no resources
assert:
that:
- __podman_test_debug_images.stdout == ""
- __podman_test_debug_networks.stdout_lines |
reject("match", "^podman$") |
reject("match", "^podman-default-kube-network$") |
list | length == 0
- __podman_test_debug_volumes.stdout == ""
- __podman_test_debug_containers.stdout == ""
- __podman_test_debug_secrets.stdout == ""
- __podman_test_debug_pods.stdout == ""
- ansible_facts["services"] | dict2items |
rejectattr("value.status", "match", "not-found") |
selectattr("key", "match", "quadlet-demo") |
list | length == 0

rescue:
- name: Dump journal
command: journalctl -ex
changed_when: false
failed_when: true
Loading