docker-mailserver/test/helper/setup.bash
2023-05-26 14:00:40 +02:00

205 lines
7 KiB
Bash

#!/bin/bash
# ? ABOUT: Functions defined here should be used when initializing tests.
# ! ATTENTION: Functions prefixed with `__` are intended for internal use within this file only, not in tests.
# ! ATTENTION: This script must not use functions from `common.bash` to
# ! avoid dependency hell.
# ! -------------------------------------------------------------------
# ? >> Miscellaneous initialization functionality
# Does pre-flight checks for each test: check whether certain required variables
# are set and exports other variables.
#
# ## Note
#
# This function is internal and should not be used in tests.
function __initialize_variables() {
function __check_if_set() {
if [[ ${!1+set} != 'set' ]]; then
echo "ERROR: (helper/setup.sh) '${1:?No variable name given to __check_if_set}' is not set" >&2
exit 1
fi
}
local REQUIRED_VARIABLES_FOR_TESTS=(
'REPOSITORY_ROOT'
'IMAGE_NAME'
'CONTAINER_NAME'
)
for VARIABLE in "${REQUIRED_VARIABLES_FOR_TESTS[@]}"; do
__check_if_set "${VARIABLE}"
done
export SETUP_FILE_MARKER TEST_TIMEOUT_IN_SECONDS NUMBER_OF_LOG_LINES
SETUP_FILE_MARKER="${BATS_TMPDIR:?}/$(basename "${BATS_TEST_FILENAME:?}").setup_file"
TEST_TIMEOUT_IN_SECONDS=${TEST_TIMEOUT_IN_SECONDS:-120}
NUMBER_OF_LOG_LINES=${NUMBER_OF_LOG_LINES:-10}
}
# ? << Miscellaneous initialization functionality
# ! -------------------------------------------------------------------
# ? >> File setup
# Print the private config path for the given container or test file,
# if no container name was given.
#
# @param ${1} = container name [OPTIONAL]
function _print_private_config_path() {
local TARGET_NAME=${1:-$(basename "${BATS_TEST_FILENAME}")}
echo "${REPOSITORY_ROOT}/test/duplicate_configs/${TARGET_NAME}"
}
# Create a dedicated configuration directory for a test file.
#
# @param ${1} = relative source in test/config folder
# @param ${2} = (optional) container name, defaults to ${BATS_TEST_FILENAME}
# @return = path to the folder where the config is duplicated
function _duplicate_config_for_container() {
local OUTPUT_FOLDER
OUTPUT_FOLDER=$(_print_private_config_path "${2}")
if [[ -z ${OUTPUT_FOLDER} ]]; then
echo "'OUTPUT_FOLDER' in '_duplicate_config_for_container' is empty" >&2
return 1
fi
rm -rf "${OUTPUT_FOLDER:?}/"
mkdir -p "${OUTPUT_FOLDER}"
cp -r "${REPOSITORY_ROOT}/test/config/${1:?}/." "${OUTPUT_FOLDER}" || return $?
echo "${OUTPUT_FOLDER}"
}
# Common defaults appropriate for most tests.
#
# Override variables in test cases within a file when necessary:
# - Use `export <VARIABLE>` in `setup_file()` to overrides for all test cases.
# - Use `local <VARIABLE>` to override within a specific test case.
#
# ## Attenton
#
# The ENV `CONTAINER_NAME` must be set before this method is called. It only affects the
# `TEST_TMP_CONFIG` directory created, but will be used in `common_container_create()`
# and implicitly in other helper methods.
#
# ## Example
#
# For example, if you need an immutable config volume that can't be affected by other tests
# in the file, then use `local TEST_TMP_CONFIG=$(_duplicate_config_for_container . "${UNIQUE_ID_HERE}")`
function _init_with_defaults() {
__initialize_variables
export TEST_TMP_CONFIG
TEST_TMP_CONFIG=$(_duplicate_config_for_container . "${CONTAINER_NAME}")
# Common complimentary test files, read-only safe to share across containers:
export TEST_FILES_CONTAINER_PATH='/tmp/docker-mailserver-test'
export TEST_FILES_VOLUME="${REPOSITORY_ROOT}/test/test-files:${TEST_FILES_CONTAINER_PATH}:ro"
# The config volume cannot be read-only as some data needs to be written at container startup
#
# - two sed failures (unknown lines)
# - dovecot-quotas.cf (setup-stack.sh:_setup_dovecot_quotas)
# - postfix-aliases.cf (setup-stack.sh:_setup_postfix_aliases)
# TODO: Check how many tests need write access. Consider using `docker create` + `docker cp` for easier cleanup.
export TEST_CONFIG_VOLUME="${TEST_TMP_CONFIG}:/tmp/docker-mailserver"
# Default Root CA cert used in TLS tests with `openssl` commands:
export TEST_CA_CERT="${TEST_FILES_CONTAINER_PATH}/ssl/example.test/with_ca/ecdsa/ca-cert.ecdsa.pem"
}
# ? << File setup
# ! -------------------------------------------------------------------
# ? >> Container startup
# Waits until the container has finished starting up.
#
# @param ${1} = container name
#
# TODO: Should also fail early on "docker logs ${1} | egrep '^[ FATAL ]'"?
function _wait_for_finished_setup_in_container() {
local TARGET_CONTAINER_NAME=${1:?Container name must be provided}
local STATUS=0
_repeat_until_success_or_timeout \
--fatal-test "_container_is_running ${1}" \
"${TEST_TIMEOUT_IN_SECONDS}" \
bash -c "docker logs ${TARGET_CONTAINER_NAME} | grep 'is up and running'" || STATUS=1
if [[ ${STATUS} -eq 1 ]]; then
echo "Last ${NUMBER_OF_LOG_LINES} lines of container (${TARGET_CONTAINER_NAME}) log"
docker logs "${1}" | tail -n "${NUMBER_OF_LOG_LINES}"
fi
return "${STATUS}"
}
# Uses `docker create` to create a container with proper defaults without starting it instantly.
#
# @param ${1} = Pass an array by it's variable name as a string; it will be used as a
# reference for appending extra config into the `docker create` below [OPTIONAL]
#
# ## Note
#
# Using array reference for a single input parameter, as this method is still
# under development while adapting tests to it and requirements it must serve
# (eg: support base config matrix in CI)
function _common_container_create() {
[[ -n ${1} ]] && local -n X_EXTRA_ARGS=${1}
run docker create \
--tty \
--name "${CONTAINER_NAME}" \
--hostname "${TEST_FQDN:-mail.example.test}" \
--volume "${TEST_FILES_VOLUME}" \
--volume "${TEST_CONFIG_VOLUME}" \
--env ENABLE_AMAVIS=0 \
--env ENABLE_CLAMAV=0 \
--env ENABLE_UPDATE_CHECK=0 \
--env ENABLE_SPAMASSASSIN=0 \
--env ENABLE_FAIL2BAN=0 \
--env POSTFIX_INET_PROTOCOLS=ipv4 \
--env DOVECOT_INET_PROTOCOLS=ipv4 \
--env LOG_LEVEL=debug \
"${X_EXTRA_ARGS[@]}" \
"${IMAGE_NAME}"
assert_success
}
# Starts a container given by it's name.
# Uses `CONTAINER_NAME` as the name for the `docker start` command.
#
# ## Attenton
#
# The ENV `CONTAINER_NAME` must be set before this method is called.
function _common_container_start() {
run docker start "${CONTAINER_NAME:?Container name must be set}"
assert_success
}
# Using `create` and `start` instead of only `run` allows to modify
# the container prior to starting it. Otherwise use this combined method.
#
# ## Note
#
# This function forwards all arguments to `_common_container_create` at present.
function _common_container_setup() {
_common_container_create "${@}"
_common_container_start
_wait_for_finished_setup_in_container "${CONTAINER_NAME}"
}
# Can be used in BATS' `teardown_file` function as a default value.
#
# @param ${1} = container name [OPTIONAL]
function _default_teardown() {
local TARGET_CONTAINER_NAME=${1:-${CONTAINER_NAME}}
docker rm -f "${TARGET_CONTAINER_NAME}"
}