From ea5e62ccbade48a491e29c5d054c3370158e0219 Mon Sep 17 00:00:00 2001 From: aartoni Date: Thu, 13 Feb 2025 18:21:09 +0100 Subject: [PATCH] feat: Enable reading env vars from files --- CHANGELOG.md | 1 + docs/content/config/environment.md | 4 ++ target/scripts/startup/variables-stack.sh | 22 +++++++ .../env_vars_from_files.bats | 62 +++++++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 test/tests/parallel/set3/container_configuration/env_vars_from_files.bats diff --git a/CHANGELOG.md b/CHANGELOG.md index 379992c3..d77c2c1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. The format ### Breaking +- **environment variables** ending in `__FILE` will cause the content of the file addressed by their value to be written in the corresponding variable without the `__FILE` suffix - **saslauthd** mechanism support via ENV `SASLAUTHD_MECHANISMS` with `pam`, `shadow`, `mysql` values has been removed. Only `ldap` and `rimap` remain supported ([#4259](https://github.com/docker-mailserver/docker-mailserver/pull/4259)) - **getmail6** has been refactored: ([#4156](https://github.com/docker-mailserver/docker-mailserver/pull/4156)) - The [DMS config volume](https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/optional-config/#volumes) now has support for `getmailrc_general.cf` for overriding [common default settings](https://docker-mailserver.github.io/docker-mailserver/v15.0/config/advanced/mail-getmail/#common-options). If you previously mounted this config file directly to `/etc/getmailrc_general` you should switch to our config volume support. diff --git a/docs/content/config/environment.md b/docs/content/config/environment.md index a16f6bcb..d4056f2e 100644 --- a/docs/content/config/environment.md +++ b/docs/content/config/environment.md @@ -6,6 +6,10 @@ title: Environment Variables Values in **bold** are the default values. If an option doesn't work as documented here, check if you are running the latest image. The current `master` branch corresponds to the image `ghcr.io/docker-mailserver/docker-mailserver:edge`. +!!! tip + + If an environment variable `__FILE` is set and points to a valid file, the content of that file will be loaded into ``. + #### General ##### OVERRIDE_HOSTNAME diff --git a/target/scripts/startup/variables-stack.sh b/target/scripts/startup/variables-stack.sh index f8f5af5e..edc61cae 100644 --- a/target/scripts/startup/variables-stack.sh +++ b/target/scripts/startup/variables-stack.sh @@ -5,6 +5,7 @@ declare -A VARS function _early_variables_setup() { __environment_variables_log_level + __environment_variables_from_files _obtain_hostname_and_domainname __environment_variables_backwards_compatibility __environment_variables_general_setup @@ -247,3 +248,24 @@ function __environment_variables_export() { sort -o /root/.bashrc /root/.bashrc sort -o /etc/dms-settings /etc/dms-settings } + +# This function reads any environment variable ending with `__FILE` from its +# referenced file, then makes it available under the same name without `__FILE`. +function __environment_variables_from_files() { + for file_env_var in $(env | grep -Po '^.+?__FILE'); do + local env_var="${file_env_var/__FILE/}" + local file_path="${!file_env_var}" + + if [[ -n "${!env_var}" ]]; then + _log 'warn' "Ignoring ${env_var} since ${file_env_var} is also set" + continue + fi + + if [[ -f "${file_path}" ]]; then + _log 'info' "Getting secret ${env_var} from ${file_path}" + export "${env_var}"="$(< "${file_path}")" + else + _log 'error' "File ${file_path} does not exist, defined in ${file_env_var}" + fi + done +} diff --git a/test/tests/parallel/set3/container_configuration/env_vars_from_files.bats b/test/tests/parallel/set3/container_configuration/env_vars_from_files.bats new file mode 100644 index 00000000..fadaffd2 --- /dev/null +++ b/test/tests/parallel/set3/container_configuration/env_vars_from_files.bats @@ -0,0 +1,62 @@ +load "${REPOSITORY_ROOT}/test/helper/setup" +load "${REPOSITORY_ROOT}/test/helper/common" + +export CONTAINER1_NAME='dms-test_env-files_success' +export CONTAINER2_NAME='dms-test_env-files_warning' +export CONTAINER3_NAME='dms-test_env-files_error' + +setup_file() { + export TEST__FILE + TEST__FILE=$(mktemp) + export NON_EXISTENT__FILE="/tmp/non_existent_secret" + + echo 1 > "${TEST__FILE}" +} + +teardown_file() { + rm -f "${TEST__FILE}" + docker rm -f "${CONTAINER1_NAME}" "${CONTAINER2_NAME}" "${CONTAINER3_NAME}" +} + +@test "Environment variables are loaded from files" { + export CONTAINER_NAME="${CONTAINER1_NAME}" + local CUSTOM_SETUP_ARGUMENTS=( + --env ENABLE_POP3__FILE="${TEST__FILE}" + -v "${TEST__FILE}:${TEST__FILE}" + ) + _init_with_defaults + _common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + run docker logs "${CONTAINER_NAME}" + + assert_success + assert_line --partial "Getting secret ENABLE_POP3 from ${TEST__FILE}" + _exec_in_container [ -f /etc/dovecot/protocols.d/pop3d.protocol ] + assert_success +} + +@test "Existing environment variables take precedence over __FILE variants" { + export CONTAINER_NAME="${CONTAINER2_NAME}" + local CUSTOM_SETUP_ARGUMENTS=( + --env TEST="manual-secret" + --env TEST__FILE="${TEST__FILE}" + ) + _init_with_defaults + _common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + run docker logs "${CONTAINER_NAME}" + + assert_success + assert_line --partial "Ignoring TEST since TEST__FILE is also set" +} + +@test "Non-existent file triggers an error" { + export CONTAINER_NAME="${CONTAINER3_NAME}" + local CUSTOM_SETUP_ARGUMENTS=( + --env TEST__FILE="${NON_EXISTENT__FILE}" + ) + _init_with_defaults + _common_container_setup 'CUSTOM_SETUP_ARGUMENTS' + run docker logs "${CONTAINER_NAME}" + + assert_success + assert_line --partial "File ${NON_EXISTENT__FILE} does not exist" +}