Compare commits

..

6 Commits

Author SHA1 Message Date
uumas
a10bf366e6 service: Allow custom postgres image 2025-06-27 00:06:28 +03:00
uumas
78860da6a4 service: Add support for redis 2025-06-27 00:06:28 +03:00
uumas
aa9eabf19c service: Actually do something with service_additional_containers 2025-06-27 00:06:23 +03:00
uumas
2e14434c9f service: Set secret target to secret name
This is a breaking change as it was previously prefixed with service
name
2025-06-27 00:05:51 +03:00
uumas
8f29c2815e container: Allow setting secret target 2025-06-26 00:51:06 +03:00
uumas
60529c18cd Revert "container: shell quote environment variables"
This reverts commit 61aa99bcd1.
2025-06-20 13:13:55 +03:00
10 changed files with 182 additions and 23 deletions

View File

@@ -94,7 +94,7 @@ argument_specs:
default: [] default: []
elements: str elements: str
container_secrets: container_secrets:
description: A list of secrets available to the container in /run/secrets/<secret name> description: A list of secrets available to the container as file or environment variable
type: list type: list
required: false required: false
default: [] default: []
@@ -122,6 +122,13 @@ argument_specs:
- mount - mount
- env - env
default: mount default: mount
target:
description: >
Where the secret will be available inside the container. If type is mount, this is
either a full file path or a filename under /run/secrets. If type is env, this is
the name of the environment variable. Defaults to secret name.
type: str
required: false
container_env: container_env:
description: A dict of environment variables for the container description: A dict of environment variables for the container

View File

@@ -45,7 +45,7 @@
network: "{{ container_networks | map('regex_replace', '$', '.network') }}" network: "{{ container_networks | map('regex_replace', '$', '.network') }}"
publish: "{{ container_publish_ports }}" publish: "{{ container_publish_ports }}"
secrets: "{{ _container_secrets }}" secrets: "{{ _container_secrets }}"
env: "{{ container_env.keys() | zip(container_env.values() | map('quote')) | community.general.dict }}" env: "{{ container_env }}"
state: quadlet state: quadlet
quadlet_file_mode: "0600" quadlet_file_mode: "0600"
quadlet_options: "{{ _container_quadlet_options }}" quadlet_options: "{{ _container_quadlet_options }}"

View File

@@ -29,7 +29,14 @@ _container_secrets: >-
| zip( | zip(
container_secrets container_secrets
| map(attribute='type', default='mount') | map(attribute='type', default='mount')
| map('regex_replace', '^', 'type=') | map('regex_replace', '^', 'type='),
container_secrets
| map(attribute='name')
| map('community.general.dict_kv', 'target')
| zip(container_secrets)
| map('combine')
| map(attribute='target')
| map('regex_replace', '^', 'target=')
) )
| map('join', ',') | map('join', ',')
}} }}

View File

@@ -11,6 +11,8 @@ service_container_secrets: []
service_container_env: {} service_container_env: {}
service_database_type: none service_database_type: none
service_postgres_image: docker.io/library/postgres
service_redis: false
service_additional_containers: [] service_additional_containers: []

View File

@@ -114,10 +114,12 @@ argument_specs:
default: "" default: ""
service_container_secrets: service_container_secrets:
description: description:
- A list of secrets available to the service container in /run/secrets/<service name>-<secret name>
- > - >
A dict of secrets and their values (including autogenerated values) is available as `service_podman_secrets` for use A list of secrets available to the service container as file or environment variable
in tepmlates or environment variables. This should only be used if the container doesn't support reading the secret from file - >
A dict of secrets and their values (including autogenerated values) is available as
`service_podman_secrets` for use in templates. This should only be used if the
container doesn't support reading the secret from file or environment variable.
type: list type: list
required: false required: false
default: [] default: []
@@ -129,8 +131,12 @@ argument_specs:
required: true required: true
value: value:
description: 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. 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 type: str
required: false required: false
length: length:
@@ -145,6 +151,14 @@ argument_specs:
- mount - mount
- env - env
default: mount default: mount
target:
description: >
Where the secret will be available inside the container. If type is mount, this is
either a full file path or a filename under /run/secrets. If type is env, this is
the name of the environment variable. Defaults to secret name.
type: str
required: false
service_container_env: service_container_env:
description: A dict of environment variables for the service container(s) description: A dict of environment variables for the service container(s)
type: dict type: dict
@@ -155,10 +169,10 @@ argument_specs:
description: description:
- Database type to set up. - Database type to set up.
- > - >
It will be run in a docker container accessible to the service at It will be run in a container accessible to the service at
host {{ service_name }}-{{ service_database_type }} on the default port. host {{ service_name }}-{{ service_database_type }} on the default port.
- The database user will be {{ service_name }} - The database user will be {{ service_name }}
- The password will be accessible as secret at /run/secrets/{{ service_name }}-{{ service_database_type }} - The password will be accessible as secret at /run/secrets/{{ service_database_type }}
- > - >
The password will also be available as the The password will also be available as the
service_podman_secrets['{{ service_name }}-{{ service_database_type }}'] variable. service_podman_secrets['{{ service_name }}-{{ service_database_type }}'] variable.
@@ -168,17 +182,30 @@ argument_specs:
- postgres - postgres
- none - none
default: none default: none
service_postgres_image:
description: Postgresql image to use.
type: str
required: false
default: docker.io/library/postgres
service_postgres_tag: service_postgres_tag:
description: description:
- Postgresql version to use. - Postgresql version to use.
- Can be debian (n) or alpine-based (n-alpine), where n can be major version like 14 or minor like 14.13. - Can be debian (n) or alpine-based (n-alpine), where n can be major version like 14 or minor like 14.13.
- Required if service_database_type is postgres, does nothing otherwise - Required if service_database_type is postgres, does nothing otherwise
- If a custom postgres image is specified, see that image documentation for supported tags.
type: str type: str
required: false required: false
service_redis:
description: >-
Whether to install redis in a container accessible to the service at host
{{ service_name }}-redis.
type: bool
required: false
default: false
service_additional_containers: service_additional_containers:
description: description:
- List of additional containers for the sercice. - List of additional containers for the service.
- > - >
Will inherit most options from main service container, except for publish_ports. Will inherit most options from main service container, except for publish_ports.
All options can be overridden per-container. All options can be overridden per-container.
@@ -245,6 +272,53 @@ argument_specs:
type: dict type: dict
required: false required: false
default: {} default: {}
secrets:
description:
- >
A list of secrets available to the service container as file or environment
variable
- >
A dict of secrets and their values (including autogenerated values) is available as
`service_podman_secrets` for use in templates. This should only be used if the
container doesn't support reading the secret from file or environment variable.
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
length:
description: Length of randomly generated string
type: int
required: false
default: 128
type:
description: How the secret will be exposed to the container
type: str
choices:
- mount
- env
default: mount
target:
description: >
Where the secret will be available inside the container. If type is mount, this is
either a full file path or a filename under /run/secrets. If type is env, this is
the name of the environment variable. Defaults to secret name.
type: str
required: false
service_requires: service_requires:
description: List of systemd units this service container depends on. description: List of systemd units this service container depends on.

View File

@@ -0,0 +1,20 @@
---
- name: Additional container {{ container ~ ' for ' ~ service_name }}
ansible.builtin.include_role:
name: container
vars:
container_name: "{{ container.name }}"
container_image: "{{ container.image | default(service_container_image) }}"
container_image_creds: "{{ service_container_image_creds }}"
container_user: "{{ service_container_user }}"
container_mounts: "{{ container.mounts | default(_service_container_mounts) }}"
container_publish_ports: "{{ container.publish_ports | default([]) }}"
container_networks: "{{ _service_container_networks }}"
container_secrets: "{{ container.secrets | default(_service_container_secrets) }}"
container_env: "{{ container.env | default(service_container_env) }}"
container_requires: "{{ _service_container_requires }}"
container_wants: "{{ service_wants }}"
container_auto_update: "{{ service_auto_update }}"
loop: "{{ _service_additional_containers }}"
loop_control:
loop_var: container

View File

@@ -4,7 +4,7 @@
name: container name: container
vars: vars:
container_name: "{{ service_name }}-{{ service_database_type }}" # This doesn't use _service_database_name to allow container role handlers to work container_name: "{{ service_name }}-{{ service_database_type }}" # This doesn't use _service_database_name to allow container role handlers to work
container_image: "docker.io/library/postgres:{{ service_postgres_tag }}" container_image: "{{ service_postgres_image }}:{{ service_postgres_tag }}"
container_mounts: container_mounts:
- type: volume - type: volume
source: "{{ _service_database_name }}" source: "{{ _service_database_name }}"
@@ -13,8 +13,9 @@
- "{{ service_name }}" - "{{ service_name }}"
container_secrets: container_secrets:
- name: "{{ _service_database_name }}" - name: "{{ _service_database_name }}"
target: "{{ service_database_type }}"
container_env: container_env:
POSTGRES_USER: "{{ service_name | replace('-', '_') }}" POSTGRES_USER: "{{ service_name | replace('-', '_') }}"
POSTGRES_PASSWORD_FILE: "/run/secrets/{{ _service_database_name }}" POSTGRES_PASSWORD_FILE: "/run/secrets/{{ service_database_type }}"
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
container_auto_update: "{{ service_auto_update }}" container_auto_update: "{{ service_auto_update }}"

View File

@@ -6,10 +6,14 @@
ansible.builtin.set_fact: ansible.builtin.set_fact:
_service_container_mounts: [] _service_container_mounts: []
- name: Databse for {{ service_name }} - name: Database for {{ service_name }}
ansible.builtin.include_tasks: database.yaml ansible.builtin.include_tasks: database.yaml
when: _service_setup_database when: _service_setup_database
- name: Redis for {{ service_name }}
ansible.builtin.include_tasks: redis.yaml
when: service_redis
- name: Secrets for {{ service_name }} - name: Secrets for {{ service_name }}
ansible.builtin.include_tasks: secrets.yaml ansible.builtin.include_tasks: secrets.yaml
when: _service_container_secrets | length > 0 when: _service_container_secrets | length > 0
@@ -18,6 +22,10 @@
ansible.builtin.include_tasks: mounts.yaml ansible.builtin.include_tasks: mounts.yaml
when: service_container_mounts | length > 0 when: service_container_mounts | length > 0
- name: Additional containers for {{ service_name }}
ansible.builtin.include_tasks: additional.yaml
when: _service_additional_containers | length > 0
- name: Main container for {{ service_name }} - name: Main container for {{ service_name }}
ansible.builtin.import_role: ansible.builtin.import_role:
name: container name: container
@@ -28,7 +36,7 @@
container_user: "{{ service_container_user }}" container_user: "{{ service_container_user }}"
container_mounts: "{{ _service_container_mounts }}" container_mounts: "{{ _service_container_mounts }}"
container_publish_ports: "{{ service_container_publish_ports }}" container_publish_ports: "{{ service_container_publish_ports }}"
container_networks: "{{ [service_name] + service_container_additional_networks }}" container_networks: "{{ _service_container_networks }}"
container_secrets: "{{ _service_container_secrets }}" container_secrets: "{{ _service_container_secrets }}"
container_env: "{{ service_container_env }}" container_env: "{{ service_container_env }}"
container_requires: "{{ _service_container_requires }}" container_requires: "{{ _service_container_requires }}"

View File

@@ -0,0 +1,10 @@
---
- name: Redis container for {{ service_name }}
ansible.builtin.import_role:
name: container
vars:
container_name: "{{ service_name }}-redis"
container_image: docker.io/valkey/valkey:alpine
container_networks:
- "{{ service_name }}"
container_auto_update: "{{ service_auto_update }}"

View File

@@ -5,20 +5,50 @@ _service_host_directory: "/srv/{{ service_name }}"
_service_setup_database: "{{ service_database_type != 'none' }}" _service_setup_database: "{{ service_database_type != 'none' }}"
_service_database_name: "{{ service_name }}-{{ service_database_type }}" _service_database_name: "{{ service_name }}-{{ service_database_type }}"
_service_container_secrets: > _service_container_networks: "{{ [service_name] + service_container_additional_networks }}"
_service_container_secrets: >-
{{ {{
service_container_secrets service_container_secrets
| zip(service_container_secrets | map(attribute='name')
| map(attribute='name') | map('community.general.dict_kv', 'target')
| map('regex_replace', '^', service_name ~ '-') | zip(
| map('community.general.dict_kv', 'name') service_container_secrets,
service_container_secrets
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('community.general.dict_kv', 'name')
) )
| map('combine') | map('combine')
+ ([{'name': _service_database_name }] if _service_setup_database else []) + ([{'name': _service_database_name, 'target': service_database_type }] if _service_setup_database else [])
}} }}
_service_container_requires: "{{ service_requires + ([_service_database_name + '.service'] if _service_setup_database else []) }}" _service_additional_containers: >-
_service_container_wants: "{{ service_wants + ([service_name + '-socat.socket'] if service_domains | length > 0 else []) }}" {{
service_additional_containers
| zip(
service_additional_containers
| map(attribute='name')
| map('regex_replace', '^', service_name ~ '-')
| map('community.general.dict_kv', 'name')
)
| map('combine')
}}
_service_container_requires: >-
{{
service_requires
+ ([_service_database_name + '.service'] if _service_setup_database else [])
+ ([service_name + '-redis.service'] if service_redis else [])
}}
_service_container_wants: >-
{{
service_wants
+ ([service_name + '-socat.socket'] if service_domains | length > 0 else [])
+ _service_additional_containers
| map(attribute='name')
| map('regex_replace', '$', '.service')
}}
_service_replacement_host_header: _service_replacement_host_header:
Host: "{{ service_name }}:{{ service_container_http_port }}" Host: "{{ service_name }}:{{ service_container_http_port }}"