From 924d9433927f3ca0ab5995c8b058650303bf93ba Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Wed, 31 Jan 2024 17:44:00 -0700 Subject: [PATCH] fix: ensure user linger is enabled and disabled correctly Cause: The role was not always enabling user lingering before creating resources, and not always canceling lingering after removing resources. Consequence: The role would give errors if attempting to create a secret or other resource requiring lingering, or would leave lingering enabled after removing resources. Fix: Centralize linger handling and keep track of users which may need linger canceling. Ensure linger is canceled for all users if all of that user's resources are removed and linger is no longer needed. Result: Resources for rootless users are always created properly. Lingering is always canceled when no longer needed. Signed-off-by: Rich Megginson --- tasks/cancel_linger.yml | 51 +++++++++++++++++++++++ tasks/cleanup_kube_spec.yml | 18 ++------ tasks/cleanup_quadlet_spec.yml | 32 +++----------- tasks/create_update_kube_spec.yml | 9 ++-- tasks/create_update_quadlet_spec.yml | 9 ++-- tasks/handle_secret.yml | 5 +++ tasks/main.yml | 10 +++++ tasks/manage_linger.yml | 27 ++++++++++++ tests/tests_basic.yml | 3 +- tests/tests_quadlet_basic.yml | 62 +++++++++++----------------- 10 files changed, 137 insertions(+), 89 deletions(-) create mode 100644 tasks/cancel_linger.yml create mode 100644 tasks/manage_linger.yml diff --git a/tasks/cancel_linger.yml b/tasks/cancel_linger.yml new file mode 100644 index 00000000..21d71ddf --- /dev/null +++ b/tasks/cancel_linger.yml @@ -0,0 +1,51 @@ +--- +# Input: +# * __podman_linger_user - username +- name: Get user information + getent: + database: passwd + key: "{{ __podman_linger_user }}" + fail_key: true + when: "'getent_passwd' not in ansible_facts or + __podman_linger_user not in ansible_facts['getent_passwd']" + +- name: Set cancel linger vars + set_fact: + __podman_xdg_runtime_dir: >- + /run/user/{{ ansible_facts["getent_passwd"][__podman_linger_user][1] }} + +- name: Gather facts for containers + containers.podman.podman_container_info: + environment: + XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}" + become: true + become_user: "{{ __podman_linger_user }}" + register: __podman_container_info + +- name: Gather facts for networks + command: podman network ls -q + register: __podman_networks + changed_when: false + environment: + XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}" + become: true + become_user: "{{ __podman_linger_user }}" + +- name: Gather secrets + command: podman secret ls -n -q + register: __podman_linger_secrets + changed_when: false + environment: + XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}" + become: true + become_user: "{{ __podman_linger_user }}" + +- name: Cancel linger if no more resources are in use + command: loginctl disable-linger {{ __podman_linger_user }} + when: + - __podman_container_info.containers | length == 0 + - __podman_networks.stdout_lines | reject("match", "^podman$") | + reject("match", "^podman-default-kube-network$") | + list | length == 0 + - __podman_linger_secrets.stdout == "" + changed_when: true diff --git a/tasks/cleanup_kube_spec.yml b/tasks/cleanup_kube_spec.yml index 17e4705e..3d3bec70 100644 --- a/tasks/cleanup_kube_spec.yml +++ b/tasks/cleanup_kube_spec.yml @@ -39,17 +39,7 @@ when: __podman_removed is changed # noqa no-handler changed_when: true -- name: Gather facts for all containers - containers.podman.podman_container_info: - environment: - XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}" - become: "{{ __podman_rootless | ternary(true, omit) }}" - become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}" - register: __podman_container_info - -- name: Cancel linger if no more containers are running - command: loginctl disable-linger {{ __podman_user }} - when: - - __podman_rootless | bool - - __podman_container_info.containers | length == 0 - changed_when: true +- name: Manage linger + include_tasks: manage_linger.yml + vars: + __podman_item_state: absent diff --git a/tasks/cleanup_quadlet_spec.yml b/tasks/cleanup_quadlet_spec.yml index 4cbcae10..604438b8 100644 --- a/tasks/cleanup_quadlet_spec.yml +++ b/tasks/cleanup_quadlet_spec.yml @@ -25,6 +25,11 @@ state: absent register: __podman_file_removed +- 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 block: @@ -53,30 +58,3 @@ XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}" become: "{{ __podman_rootless | ternary(true, omit) }}" become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}" - - - name: Gather facts for all containers - containers.podman.podman_container_info: - environment: - XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}" - become: "{{ __podman_rootless | ternary(true, omit) }}" - become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}" - register: __podman_container_info - no_log: true - - - name: Gather facts for networks - command: podman network ls -q - register: __podman_networks - changed_when: false - environment: - XDG_RUNTIME_DIR: "{{ __podman_xdg_runtime_dir }}" - become: "{{ __podman_rootless | ternary(true, omit) }}" - become_user: "{{ __podman_rootless | ternary(__podman_user, omit) }}" - - - name: Cancel linger if no more resources are in use - command: loginctl disable-linger {{ __podman_user }} - when: - - __podman_rootless | bool - - __podman_container_info.containers | length == 0 - - __podman_networks.stdout_lines | reject('match', '^podman$') | - list | length == 0 - changed_when: true diff --git a/tasks/create_update_kube_spec.yml b/tasks/create_update_kube_spec.yml index fca64981..93054202 100644 --- a/tasks/create_update_kube_spec.yml +++ b/tasks/create_update_kube_spec.yml @@ -1,9 +1,8 @@ --- -- name: Enable lingering if needed - command: loginctl enable-linger {{ __podman_user }} - when: __podman_rootless | bool - args: - creates: /var/lib/systemd/linger/{{ __podman_user }} +- name: Manage linger + include_tasks: manage_linger.yml + vars: + __podman_item_state: present - name: Get the host mount volumes set_fact: diff --git a/tasks/create_update_quadlet_spec.yml b/tasks/create_update_quadlet_spec.yml index f76f232c..6cb4b444 100644 --- a/tasks/create_update_quadlet_spec.yml +++ b/tasks/create_update_quadlet_spec.yml @@ -1,9 +1,8 @@ --- -- name: Enable lingering if needed - command: loginctl enable-linger {{ __podman_user }} - when: __podman_rootless | bool - args: - creates: /var/lib/systemd/linger/{{ __podman_user }} +- name: Manage linger + include_tasks: manage_linger.yml + vars: + __podman_item_state: present - name: Create host directories file: "{{ __defaults | combine(podman_host_directories[__hostitem]) diff --git a/tasks/handle_secret.yml b/tasks/handle_secret.yml index ecbe604f..14aa9a11 100644 --- a/tasks/handle_secret.yml +++ b/tasks/handle_secret.yml @@ -20,6 +20,11 @@ __podman_xdg_runtime_dir: >- /run/user/{{ ansible_facts["getent_passwd"][__podman_user][1] }} +- name: Manage linger + include_tasks: manage_linger.yml + vars: + __podman_item_state: "{{ __podman_secret.state | d('present') }}" + - name: Manage each secret containers.podman.podman_secret: data: "{{ __podman_secret.data | string diff --git a/tasks/main.yml b/tasks/main.yml index a34068f7..1b9ca4ab 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -106,6 +106,10 @@ selinux_ports: "{{ podman_selinux_ports }}" when: podman_selinux_ports | length > 0 +- name: Keep track of users that need to cancel linger + set_fact: + __podman_cancel_user_linger: [] + - name: Handle secrets include_tasks: handle_secret.yml loop: "{{ podman_secrets }}" @@ -124,3 +128,9 @@ loop: "{{ podman_quadlet_specs }}" loop_control: loop_var: __podman_quadlet_spec_item + +- name: Cancel linger + include_tasks: cancel_linger.yml + loop: "{{ __podman_cancel_user_linger }}" + loop_control: + loop_var: __podman_linger_user diff --git a/tasks/manage_linger.yml b/tasks/manage_linger.yml new file mode 100644 index 00000000..1690f288 --- /dev/null +++ b/tasks/manage_linger.yml @@ -0,0 +1,27 @@ +--- +# Input: +# * __podman_rootless - true or false +# * __podman_user - name of user +# * __podman_item_state - present or absent +# Globals: __podman_cancel_user_linger +- name: Enable linger if needed + when: + - __podman_rootless | bool + - __podman_item_state | d('present') != 'absent' + block: + - name: Enable linger if needed + command: loginctl enable-linger {{ __podman_user }} + when: __podman_rootless | bool + args: + creates: /var/lib/systemd/linger/{{ __podman_user }} + + - name: Mark user as not yet needing to cancel linger + set_fact: + __podman_cancel_user_linger: "{{ __podman_cancel_user_linger | difference([__podman_user]) }}" + +- name: Mark user for possible linger cancel + set_fact: + __podman_cancel_user_linger: "{{ __podman_cancel_user_linger | union([__podman_user]) }}" + when: + - __podman_rootless | bool + - __podman_item_state | d('present') == 'absent' diff --git a/tests/tests_basic.yml b/tests/tests_basic.yml index acc01c08..f2cf7101 100644 --- a/tests/tests_basic.yml +++ b/tests/tests_basic.yml @@ -234,7 +234,8 @@ spec: containers: - name: bogus - image: this_is_a_bogus_image + image: >- + quay.io/linux-system-roles/this_is_a_bogus_image:latest rescue: - name: Verify image not pulled diff --git a/tests/tests_quadlet_basic.yml b/tests/tests_quadlet_basic.yml index 94c1b66c..025497fa 100644 --- a/tests/tests_quadlet_basic.yml +++ b/tests/tests_quadlet_basic.yml @@ -7,6 +7,7 @@ vars: podman_use_copr: false # disable copr for CI testing podman_fail_if_too_old: false + __json_secret_data: '{"test": "json"}' __secret_password_env: "{{ lookup('env', 'SYSTEM_ROLES_PODMAN_PASSWORD') }}" __podman_secrets: - name: mysql_container_root_password @@ -14,6 +15,9 @@ skip_existing: true data: "{{ (__secret_password_env | length > 0) | ternary(__secret_password_env, mysql_container_root_password) }}" + - name: json_secret + state: present + data: "{{ __json_secret_data | string }}" __podman_quadlet_specs: - name: quadlet-basic type: network @@ -34,8 +38,9 @@ Volume: quadlet-basic-mysql.volume:/var/lib/mysql Network: quadlet-basic.network # Once 4.5 is released change this line to use the quadlet Secret key - PodmanArgs: "--secret=mysql_container_root_password,type=env,\ - target=MYSQL_ROOT_PASSWORD" + PodmanArgs: >- + --secret=mysql_container_root_password,type=env,target=MYSQL_ROOT_PASSWORD + --secret=json_secret,type=mount,target=/tmp/test.json Environment: - FOO=/bin/busybox-extras - BAZ=test @@ -120,6 +125,12 @@ - quadlet-basic.network - quadlet-basic-mysql.volume + - name: Ensure linger + stat: + path: /var/lib/systemd/linger/user_quadlet_basic + register: __stat + failed_when: not __stat.stat.exists + # must clean up networks last - cannot remove a network # in use by a container - name: Cleanup user @@ -135,41 +146,18 @@ (__podman_quadlet_specs | selectattr('type', 'match', '^network$') | list)) | map('combine', __absent) | list }}" - - name: Set secret var for root testing - set_fact: - __root_podman_secrets: "{{ __podman_secrets + __json_secret }}" - __root_json_data: '{"test": "json"}' - vars: - __json_secret: - - name: json_secret - state: present - data: '{"test": "json"}' - no_log: true - - - name: Set container vars for root testing - set_fact: - __root_podman_quadlet_specs: "{{ __podman_quadlet_specs + - __json_container }}" - vars: - __json_container: - - name: json_container - type: container - Install: - WantedBy: default.target - Container: - Image: "{{ mysql_image }}" - ContainerName: json_container - # Once 4.5 is released change this line to use the quadlet Secret - PodmanArgs: "--secret=mysql_container_root_password,type=env,\ - target=MYSQL_ROOT_PASSWORD --secret=json_secret,type=mount,\ - target=/tmp/test.json" + - name: Ensure no linger + stat: + path: /var/lib/systemd/linger/user_quadlet_basic + register: __stat + failed_when: __stat.stat.exists - name: Run the role - root include_role: name: linux-system-roles.podman vars: - podman_secrets: "{{ __root_podman_secrets }}" - podman_quadlet_specs: "{{ __root_podman_quadlet_specs }}" + podman_secrets: "{{ __podman_secrets }}" + podman_quadlet_specs: "{{ __podman_quadlet_specs }}" - name: Check files command: cat {{ __dir }}/{{ item }} @@ -182,9 +170,9 @@ - quadlet-basic-mysql.volume - name: Check JSON - command: podman exec json_container cat /tmp/test.json + command: podman exec quadlet-basic-mysql cat /tmp/test.json register: __result - failed_when: __result.stdout != __root_json_data + failed_when: __result.stdout != __json_secret_data changed_when: false - name: Cleanup system - root @@ -192,10 +180,10 @@ name: linux-system-roles.podman vars: __absent: {"state":"absent"} - podman_secrets: "{{ __root_podman_secrets | map('combine', __absent) | + podman_secrets: "{{ __podman_secrets | map('combine', __absent) | list }}" - podman_quadlet_specs: "{{ ((__root_podman_quadlet_specs | + podman_quadlet_specs: "{{ ((__podman_quadlet_specs | rejectattr('type', 'match', '^network$') | list) + - (__root_podman_quadlet_specs | + (__podman_quadlet_specs | selectattr('type', 'match', '^network$') | list)) | map('combine', __absent) | list }}"