Compare commits

..

19 Commits

Author SHA1 Message Date
uumas
79f1be5cbe service: move secrets definition from set_fact to vars 2024-11-19 22:27:29 +02:00
uumas
98edd6051a synapse: rename synapse_public_base_domain to synapse_external_domain 2024-11-19 21:31:14 +02:00
uumas
b74b49d6e9 Add synapse role 2024-11-19 20:14:05 +02:00
uumas
d7c806bf19 service: Support setting volume user and group 2024-11-19 20:11:38 +02:00
uumas
1b62d4df72 service: Add support for template mounts
Template mounts are templated from jinja2 templates to a service name
-specific directory under /srv and bind mounted inside the container.
2024-11-19 20:10:33 +02:00
uumas
b17932816b service: Use C collate and ctype for postgresql 2024-11-19 20:04:47 +02:00
uumas
563bfa5a32 service: use include_vars instead of set_fact for database name 2024-11-19 20:03:56 +02:00
uumas
ab4b6b7825 service: Support setting container secrets 2024-11-19 20:00:27 +02:00
uumas
64606707d4 service: Support setting container user 2024-11-19 19:57:41 +02:00
uumas
cb3d5f177d container: Add no_log to sensitive task 2024-11-19 19:53:35 +02:00
uumas
61a8e67205 Add volume role and support setting volume owner
Add volume role
container: Use volume role, including support for user and group
2024-11-19 19:50:43 +02:00
uumas
61aa99bcd1 container: shell quote environment variables 2024-11-19 19:35:53 +02:00
uumas
2287077f42 network: switch to using the containers.podman.podman_network module 2024-11-19 19:34:32 +02:00
uumas
007514feb5 container: switch to using the containers.podman.podman_container module 2024-11-19 19:33:10 +02:00
uumas
28dc77a907 container: Ensure mount sources and destinations are unique 2024-11-19 19:12:08 +02:00
uumas
356560f268 container: Clarify argspec mount readonly description 2024-11-19 19:09:15 +02:00
uumas
93cc64ba98 podman: Exlicitely install aardvark-dns
It's needed for dns to work inside containers
2024-11-19 19:06:07 +02:00
uumas
5648887a64 service: Make database password accessible in template mounts 2024-11-13 20:54:10 +02:00
uumas
e7dab881f2 example: Add database 2024-11-13 19:15:24 +02:00
32 changed files with 555 additions and 91 deletions

View File

@@ -49,10 +49,20 @@ argument_specs:
type: str
required: true
readonly:
description: If true, volume will be mounted as read only inside the container
description: If true, mount will be read only inside the container
type: bool
required: false
default: false
user:
description: Volume owner uid. Only applicable if mount type is volume.
type: str
required: false
default: ""
group:
description: Volume owner gid. Only applicable if mount type is volume.
type: str
required: false
default: ""
container_publish_ports:
description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)"

View File

@@ -1,4 +1,7 @@
---
- name: Validate inputs
ansible.builtin.import_tasks: validation.yaml
- name: Create networks for container {{ container_name }}
ansible.builtin.include_role:
name: network
@@ -8,16 +11,37 @@
loop_control:
loop_var: network
- name: Create volumes for container {{ container_name }}
ansible.builtin.include_role:
name: volume
vars:
volume_name: "{{ volume.source }}"
volume_uid: "{{ volume.user | default('') }}"
volume_gid: "{{ volume.group | default('') }}"
loop: "{{ _container_volumes }}"
loop_control:
loop_var: volume
- name: Create secrets for container {{ container_name }}
containers.podman.podman_secret:
name: "{{ item.name }}"
data: "{{ item.value | default(lookup('community.general.random_string', special=false, length=128)) }}"
skip_existing: "{{ item.value is not defined }}"
no_log: true
loop: "{{ container_secrets }}"
- name: Create container service {{ container_name }}
ansible.builtin.template:
src: container.j2
dest: "/etc/containers/systemd/{{ container_name }}.container"
mode: "0600"
containers.podman.podman_container:
image: "{{ container_image }}"
name: "{{ container_name }}"
command: "{{ container_command or omit }}"
user: "{{ container_user or omit }}"
mount: "{{ _container_mounts | map('items') | map('map', 'join', '=') | map('join', ',') }}"
network: "{{ container_networks | map('regex_replace', '$', '.network') }}"
publish: "{{ container_publish_ports }}"
secrets: "{{ container_secrets | map(attribute='name') }}"
env: "{{ container_env.keys() | zip(container_env.values() | map('quote')) | community.general.dict }}"
state: quadlet
quadlet_file_mode: "0600"
quadlet_options: "{{ _container_quadlet_options }}"
notify: "Restart container service {{ container_name }}"

View File

@@ -0,0 +1,8 @@
---
- name: Assert mount sources and destinations are unique
ansible.builtin.assert:
that:
- _container_mount_destinations | unique | length == _container_mount_destinations | length
- _container_mount_sources | unique | length == _container_mount_sources | length
fail_msg: "Container mount sources and destinations must be unique. Failed with mounts: {{ container_mounts }}"
quiet: true

View File

@@ -1,49 +0,0 @@
# {{ ansible_managed }}
[Unit]
Description=Container {{ container_name }}
{% for requirement in container_requires %}
Requires={{ requirement }}
After={{ requirement }}
{% endfor %}
{% for want in container_wants %}
Requires={{ want }}
Before={{ want }}
{% endfor %}
[Container]
Image={{ container_image }}
ContainerName={{ container_name }}
{% if container_command | length > 0 %}
Exec="{{ container_command | join('" "') }}"
{% endif %}
{% if container_user | length > 0 %}
User={{ container_user }}
{% endif %}
{% for mount in container_mounts %}
Mount={% for key, value in mount.items() %}{{ key }}={{ value }}{% if not loop.last %},{% endif %}{% endfor %}
{% endfor %}
{% for network in container_networks %}
Network={{ network }}.network
{% endfor %}
{% for port in container_publish_ports %}
PublishPort={{ port }}
{% endfor %}
{% for secret in container_secrets %}
Secret={{ secret.name }}
{% endfor %}
{% for key, value in container_env.items() %}
Environment={{ key }}={{ value }}
{% endfor %}
{% if container_auto_update %}
AutoUpdate=registry
{% endif %}
{% if container_auto_start %}
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
{% endif %}

View File

@@ -0,0 +1,45 @@
---
_container_volumes: "{{ container_mounts | selectattr('type', '==', 'volume') }}"
_container_mount_sources: "{{ container_mounts | map(attribute='source') }}"
_container_mount_destinations: "{{ container_mounts | map(attribute='destination') }}"
_container_volume_mount_sources: >-
{{
_container_volumes
| map(attribute='source')
| map('regex_replace', '$', '.volume')
| map('community.general.dict_kv', 'source')
}}
_container_mounts: >-
{{
container_mounts | selectattr('type', '!=', 'volume') +
container_mounts | selectattr('type', '==', 'volume')
| community.general.remove_keys(['user', 'group'])
| zip(_container_volume_mount_sources) | map('combine')
}}
_container_quadlet_unit_options: |
[Unit]
Description=Container {{ container_name }}
StartLimitIntervalSec=30
StartLimitBurst=3
{% for requirement in container_requires %}
Requires={{ requirement }}
After={{ requirement }}
{% endfor %}
{% for want in container_wants %}
Wants={{ want }}
{% endfor %}
_container_quadlet_auto_start_options: |
[Service]
Restart=always
[Install]
WantedBy=multi-user.target
_container_quadlet_options_incl_empty:
- "{{ 'AutoUpdate=registry' if container_auto_update else '' }}"
- "{{ _container_quadlet_unit_options }}"
- "{{ _container_quadlet_auto_start_options if container_auto_start else '' }}"
_container_quadlet_options: "{{ _container_quadlet_options_incl_empty | select('!=', '') }}"

View File

@@ -18,6 +18,8 @@
readonly: true
service_container_http_port: 8080
service_domains: "{{ example_domains }}"
service_database_type: postgres
service_postgres_tag: 16-alpine
service_container_publish_ports:
- "127.0.0.1:8080:8080"
- "0.0.0.0:4443:8043"

View File

@@ -1,7 +1,7 @@
---
- name: "Create container network service {{ network_name }}"
ansible.builtin.template:
src: network.j2
dest: "/etc/containers/systemd/{{ network_name }}.network"
mode: "0644"
containers.podman.podman_network:
name: "{{ network_name }}"
state: quadlet
quadlet_file_mode: "0644"
notify: Reload systemd daemon

View File

@@ -1,7 +0,0 @@
# {{ ansible_managed }}
[Unit]
Description=Container network {{ network_name }}
[Network]
NetworkName={{ network_name }}

View File

@@ -12,5 +12,7 @@
- name: Install podman
ansible.builtin.apt:
name: podman
name:
- podman
- aardvark-dns
tags: podman

View File

@@ -1,8 +1,10 @@
---
service_domains: []
service_container_user: ""
service_container_publish_ports: []
service_container_mounts: []
service_container_secrets: []
service_container_env: {}
service_database_type: none

View File

@@ -25,6 +25,11 @@ argument_specs:
description: "The image to run in the service container(s), in FQIN format (registry/imagename:tag)."
type: str
required: true
service_container_user:
description: The UID to run as inside the container
type: str
required: false
default: ""
service_container_publish_ports:
description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)"
type: list
@@ -45,11 +50,13 @@ argument_specs:
choices:
- volume
- bind
- template
source:
description:
- Mount source.
- If mount type is volume, name of the volume.
- If mount type is bind, host path to bind mount inside the container.
- If mount type is template, the name of the template file, must end in .j2
type: str
required: true
destination:
@@ -57,10 +64,38 @@ argument_specs:
type: str
required: true
readonly:
description: If true, volume will be mounted as read only inside the container
description:
- If true, volume will be mounted as read only inside the container.
- Defaults to false for volume and bind, true for template
type: bool
required: false
default: false
user:
description: Volume owner uid. Only applicable if mount type is volume.
type: str
required: false
default: ""
group:
description: Volume owner gid. Only applicable if mount type is volume.
type: str
required: false
default: ""
service_container_secrets:
description: A list of secrets available to the service container in /run/secrets/<service name>-<secret name>
type: list
required: false
default: []
elements: dict
options:
name:
description: Name of the secret
type: str
required: true
value:
description:
- Value of the secret. Defaults to a 128-character random string containing alphanumeric characters.
- If the value is not explicitly set, it will not be changed if the secret already exists.
type: str
required: false
service_container_env:
description: A dict of environment variables for the service container(s)
type: dict
@@ -123,11 +158,13 @@ argument_specs:
choices:
- volume
- bind
- template
source:
description:
- Mount source.
- If mount type is volume, name of the volume.
- If mount type is bind, host path to bind mount inside the container.
- If mount type is template, the name of the template file, must end in .j2
type: str
required: true
destination:
@@ -135,10 +172,11 @@ argument_specs:
type: str
required: true
readonly:
description: If true, volume will be mounted as read only inside the container
description:
- If true, volume will be mounted as read only inside the container
- Defaults to false for volume and bind, true for template
type: bool
required: false
default: false
publish_ports:
description: "A list of published ports in docker format (<host listen address>:<host port>:<container port>)"
type: list

View File

@@ -1,7 +1,7 @@
---
- name: Set database name
ansible.builtin.set_fact:
_service_database_name: "{{ service_name }}-{{ service_database_type }}"
- name: Include database variables
ansible.builtin.include_vars:
file: database.yaml
- name: Database container for {{ service_name }}
ansible.builtin.include_role:
@@ -20,6 +20,7 @@
container_env:
POSTGRES_USER: "{{ service_name | replace('-', '_') }}"
POSTGRES_PASSWORD_FILE: "/run/secrets/{{ _service_database_name }}"
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
container_auto_update: "{{ service_auto_update }}"
- name: Get database secret info

View File

@@ -7,24 +7,26 @@
_service_container_mounts: []
_service_container_requires: "{{ service_requires }}"
- name: Mounts for {{ service_name }}
ansible.builtin.include_tasks: mounts.yaml
when: service_container_mounts | length > 0
- name: Databse for {{ service_name }}
ansible.builtin.include_tasks: database.yaml
when: "service_database_type != 'none'"
- name: Mounts for {{ service_name }}
ansible.builtin.include_tasks: mounts.yaml
when: service_container_mounts | length > 0
- name: Main container for {{ service_name }}
ansible.builtin.include_role:
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ service_name }}"
container_image: "{{ service_container_image }}"
container_user: "{{ service_container_user }}"
container_mounts: "{{ _service_container_mounts }}"
container_publish_ports: "{{ service_container_publish_ports }}"
container_networks:
- "{{ service_name }}"
container_secrets: "{{ _service_container_secrets }}"
container_env: "{{ service_container_env }}"
container_requires: "{{ _service_container_requires }}"
container_wants: "{{ [service_name + '-socat.socket'] if service_domains | length > 0 else [] }}"

View File

@@ -0,0 +1,32 @@
---
- name: Set container named mounts
ansible.builtin.set_fact:
_service_container_mounts: >
{{ _service_container_mounts +
[mount | combine({'source': service_name + '-' + mount.source})] }}
when: mount.type == 'volume'
- name: Set container named mounts
ansible.builtin.set_fact:
_service_container_mounts: "{{ _service_container_mounts + [mount] }}"
when: mount.type == 'bind'
- name: Template mounts
when: mount.type == 'template'
block:
- name: Set template host path
ansible.builtin.set_fact:
_service_template_host_path: "{{ _service_host_directory }}/mounts/{{ (mount.source | split('.'))[0:-1] | join('.') }}" # Strip .j2 extension
- name: Template files for template mounts
ansible.builtin.template:
src: "{{ mount.source }}"
dest: "{{ _service_template_host_path }}"
mode: "0644"
notify: "Restart container service {{ service_name }}"
- name: Set container template mounts
ansible.builtin.set_fact:
_service_container_mounts: >
{{ _service_container_mounts +
[{'readonly': true} | combine(mount) | combine({'type': 'bind', 'source': _service_template_host_path})] }}

View File

@@ -1,18 +1,21 @@
---
- name: Set container named mounts
ansible.builtin.set_fact:
_service_container_mounts: >
{{ _service_container_mounts +
[mount | combine({'source': service_name + '-' + mount.source})] }}
when: mount.type == 'volume'
loop: "{{ service_container_mounts }}"
loop_control:
loop_var: mount
- name: Create template mount directories under /srv
when: _service_template_mounts | length > 0
block:
- name: Create directory {{ _service_host_directory }}
ansible.builtin.file:
path: "{{ _service_host_directory }}"
state: directory
mode: "0755"
- name: Set container named mounts
ansible.builtin.set_fact:
_service_container_mounts: "{{ _service_container_mounts + [mount] }}"
when: mount.type == 'bind'
- name: Create directory {{ _service_host_directory + '/mounts' }}
ansible.builtin.file:
path: "{{ _service_host_directory }}/mounts"
state: directory
mode: "0700"
- name: Set mount definitions for {{ service_name }}
ansible.builtin.include_tasks: mount.yaml
loop: "{{ service_container_mounts }}"
loop_control:
loop_var: mount

View File

@@ -1,5 +1,16 @@
---
- name: Fail if service_container_user is not string
ansible.builtin.fail:
msg: "service_container_user must be a string, not int."
when: service_container_user is not string
- name: Fail if service_database_type is postgres but service_postgres_tag is not set
ansible.builtin.fail:
msg: "service_postgres_tag needs to be set when database type is postgres"
when: "service_database_type == 'postgres' and service_postgres_tag is not defined"
- name: Fail if template mount source doesn't end in .j2
ansible.builtin.fail:
msg: "Template mount source file name needs to end in .j2. The file {{ item.source }} of {{ service_name }} doesn't."
when: "item.source | split('.') | last != 'j2'"
loop: "{{ _service_template_mounts }}"

View File

@@ -0,0 +1,2 @@
---
_service_database_name: "{{ service_name }}-{{ service_database_type }}"

View File

@@ -0,0 +1,14 @@
---
_service_template_mounts: "{{ service_container_mounts | selectattr('type', '==', 'template') | list }}"
_service_host_directory: "/srv/{{ service_name }}"
_service_container_secrets: >
{{
service_container_secrets
| zip(service_container_secrets
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('community.general.dict_kv', 'name')
)
| map('combine')
}}

2
roles/synapse/README.md Normal file
View File

@@ -0,0 +1,2 @@
Sets up a matrix synapse podman container.
See https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html for info on configuration options where descriptions are not provided in this documentation.

View File

@@ -0,0 +1,18 @@
---
synapse_postgres_tag: 16-alpine
synapse_trusted_key_servers:
- matrix.org
synapse_room_complexity_limit: 0
synapse_room_complexity_error: ""
synapse_turn_uris: []
synapse_max_upload_size: 50M
synapse_allow_public_rooms_over_federation: false
synapse_auto_accept_invites:
enabled: false
synapse_auto_join_rooms: []
synapse_smtp_server: ""

View File

@@ -0,0 +1,87 @@
---
argument_specs:
main:
description:
- Sets up a matrix synapse podman container.
- >-
See https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html for info on configuration options where descriptions are
not provided in this documentation.
options:
synapse_server_name:
description: Matrix server name. This can not be changed without a full reset and database wipe. This will be visible to users.
type: str
required: true
synapse_external_domain:
description:
- The public-facing domain that clients use to access synapse, without https://. e.g. matrix.domain.tld
- This is used to set the public_baseurl option for synapse (with https:// and trailing / added)
type: str
required: true
synapse_signing_key:
description: The homeserver signing key
type: str
required: true
synapse_trusted_key_servers:
type: list
required: false
default:
- matrix.org
elements: str
synapse_remote_room_complexity_limit:
description: Sets limit_remote_rooms.complexity value
type: float
required: false
default: 0
synapse_room_complexity_error:
description: Sets limit_remote_rooms.copmlexity_error value. Required if synapse_remote_room_complexity_level is set.
type: str
default: ""
synapse_turn_uris:
type: list
required: false
default: []
elements: str
synapse_turn_shared_secret:
description: Required if synapse_turn_uris is not empty
type: str
synapse_email_smtp_server:
description: email.smtp_host, set this to enable sending emails
type: str
required: false
default: ""
synapse_email_smtp_user:
description: Required if synapse_email_smtp_server is set
type: str
synapse_email_smtp_password:
description: Required if synapse_email_smtp_server is set
type: str
synapse_email_from:
description: Required if synapse_email_smtp_server is set
type: str
synapse_email_app_name:
description: Required if synapse_email_smtp_server is set
type: str
synapse_max_upload_size:
type: str
required: false
default: 50M
synapse_allow_public_rooms_over_federation:
type: bool
required: false
default: false
synapse_auto_accept_invites:
type: dict
required: false
default:
enabled: false
synapse_auto_join_rooms:
type: list
required: false
default: []
elements: str
synapse_postgres_tag:
description: Postgres tag to use for synapse postgres container
type: str
required: false
default: 16-alpine

View File

@@ -0,0 +1,42 @@
---
- name: Assert complexity error is set if complexity limit is
ansible.builtin.assert:
that: synapse_room_complexity_limit == 0 or synapse_room_complexity_error | length > 0
fail_msg: "synapse_room_complexity_error must be set when synapse_room_complexity_limit is"
quiet: true
- name: Assert turn shared secret is set if turn uris is
ansible.builtin.assert:
that: synapse_turn_uris | length == 0 or synapse_turn_shared_secret | length > 0
fail_msg: "synapse_turn_shared_secret must be set when synapse_turn_uris is"
quiet: true
- name: Synapse container
ansible.builtin.include_role:
name: service
vars:
service_name: synapse
service_container_image: "{{ _synapse_image_name }}"
service_database_type: postgres
service_postgres_tag: "{{ synapse_postgres_tag }}"
service_container_mounts:
- type: template
source: homeserver.yaml.j2
destination: /data/homeserver.yaml
- type: template
source: log.yaml.j2
destination: /data/log.yaml
- type: volume
source: media
destination: /data/media
user: "991"
group: "991"
service_container_secrets:
- name: signing-key
value: "{{ synapse_signing_key }}"
service_container_env:
SYNAPSE_SERVER_NAME: "{{ synapse_server_name }}"
SYNAPSE_REPORT_STATS: "no"
UID: 991
GID: 991
service_container_http_port: 8008
service_domains: "{{ [synapse_external_domain] }}"

View File

@@ -0,0 +1,99 @@
---
# vim:ft=yaml
# {{ ansible_managed }}
signing_key_path: /run/secrets/synapse-signing-key
media_store_path: /data/media
log_config: /data/log.yaml
server_name: {{ synapse_server_name }}
public_baseurl: https://{{ synapse_external_domain }}/
report_stats: false
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
database:
name: psycopg2
args:
host: synapse-postgres
user: synapse
password: "{{ _service_database_password }}"
dbname: synapse
caches:
global_factor: 1.0
enable_registration: false
enable_3pid_changes: false
ui_auth:
session_timeout: 5m
trusted_key_servers:
{% for server in synapse_trusted_key_servers %}
- server_name: {{ server }}
{% endfor %}
suppress_key_server_warning: true
max_upload_sixe: "{{ synapse_max_upload_size }}"
{% if synapse_room_complexity_limit > 0 %}
limit_remote_rooms:
enabled: true
complexity: {{ synapse_room_complexity_limit }}
complexity_error: "{{ synapse_room_complexity_error }}"
{% endif %}
url_preview_enabled: true
url_preview_ip_range_blacklist:
- '127.0.0.0/8'
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '100.64.0.0/10'
- '192.0.0.0/24'
- '169.254.0.0/16'
- '192.88.99.0/24'
- '198.18.0.0/15'
- '192.0.2.0/24'
- '198.51.100.0/24'
- '203.0.113.0/24'
- '224.0.0.0/4'
- '::1/128'
- 'fe80::/10'
- 'fc00::/7'
- '2001:db8::/32'
- 'ff00::/8'
- 'fec0::/10'
turn_uris: {{ synapse_turn_uris }}
{% if synapse_turn_uris | length > 0 %}
turn_shared_secret: {{ synapse_turn_shared_secret }}
{% endif %}
turn_user_lifetime: 1d
turn_allow_guests: false
{% if synapse_smtp_server | length > 0 %}
email:
smtp_host: {{ synapse_smtp_server }}
smtp_port: 587
smtp_user: {{ synapse_smtp_user }}
smtp_pass: {{ synapse_smtp_password }}
require_transport_security: true
notif_from: "{{ synapse_email_from }}"
app_name: "{{ synapse_email_app_name }}"
enable_notifs: true
notif_for_new_users: false
notif_delay_before_mail: 1h
{% endif %}
allow_public_rooms_over_federation: {{ synapse_allow_public_rooms_over_federation }}
auto_accept_invites: {{ synapse_auto_accept_invites }}
auto_join_rooms: {{ synapse_auto_join_rooms }}
autocreate_auto_join_rooms: false

View File

@@ -0,0 +1,24 @@
# {{ ansible_managed }}
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: precise
loggers:
synapse:
level: WARNING
synapse.storage.SQL:
level: WARNING
root:
level: WARNING
handlers: [console]
disable_existing_loggers: false

View File

@@ -0,0 +1,2 @@
---
_synapse_image_name: ghcr.io/element-hq/synapse:latest

1
roles/volume/README.md Normal file
View File

@@ -0,0 +1 @@
Sets up podman volume with systemd unit (quadlet)

View File

@@ -0,0 +1,3 @@
---
volume_uid: ""
volume_gid: ""

View File

@@ -0,0 +1,19 @@
---
argument_specs:
main:
description: Sets up podman volume with systemd unit (quadlet)
options:
volume_name:
description: Name of the volume. Must be unique within a host.
type: str
required: true
volume_uid:
description: Volume uid (the volume owner)
type: str
required: false
default: ""
volume_gid:
description: Volume gid (the volume owner)
type: str
required: false
default: ""

View File

@@ -0,0 +1,3 @@
---
dependencies:
- role: podman

View File

@@ -0,0 +1,11 @@
---
- name: Validate inputs
ansible.builtin.import_tasks: validation.yaml
- name: Create container volume service {{ volume_name }}
containers.podman.podman_volume:
name: "{{ volume_name }}"
options: "{{ volume_options }}"
state: quadlet
quadlet_file_mode: "0644"
notify: Reload systemd daemon

View File

@@ -0,0 +1,7 @@
---
- name: Assert volume_uid and volume_gid are strings
ansible.builtin.assert:
that:
- volume_uid is string
- volume_gid is string
fail_msg: "volume_uid and volume_gid must be strings, not int."

View File

@@ -0,0 +1,6 @@
---
volume_mount_options_incl_empty:
- "{{ 'uid=' ~ volume_uid if volume_uid | length > 0 else '' }}"
- "{{ 'gid=' ~ volume_gid if volume_gid | length > 0 else '' }}"
volume_mount_options: "{{ volume_mount_options_incl_empty | select('!=', '') | list }}"
volume_options: "{{ ['o=' ~ volume_mount_options | join(',')] if volume_mount_options | length > 0 else [] }}"