From bc02d7cca034b5de4000c2933f529ca06510bc34 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Mon, 2 Dec 2024 11:49:04 +1100 Subject: [PATCH] [#43] Switch to new MariaDB image. t2 --- .circleci/config.yml | 4 +- 9999-mariadb-init.bash | 34 ++++++---- Dockerfile | 17 +++-- Dockerfile.seed | 16 ++--- README.md | 4 +- seed-db.sh | 114 +++++++++++++++++++++++++++------- tests/bats/_helper.bash | 22 ++++--- tests/bats/data.bats | 124 ++++++++++++++++++++++--------------- tests/bats/fixtures/db.sql | 66 ++++++++++++++++++++ tests/dgoss/goss.yaml | 2 +- 10 files changed, 290 insertions(+), 113 deletions(-) create mode 100644 tests/bats/fixtures/db.sql diff --git a/.circleci/config.yml b/.circleci/config.yml index 315b58c..50556db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,8 +6,8 @@ jobs: - image: drevops/ci-runner:24.11.0 environment: BUILDX_VERSION: v0.10.4 - BUILDX_PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7 working_directory: /root/project + steps: - checkout - run: @@ -39,6 +39,8 @@ jobs: command: codecov --fail-on-error -t $CODECOV_TOKEN -s coverage - run: name: Deploy image + environment: + BUILDX_PLATFORMS: linux/amd64,linux/arm64 command: | if [ -n "${CIRCLE_TAG}" ]; then export TAG="${CIRCLE_TAG}" diff --git a/9999-mariadb-init.bash b/9999-mariadb-init.bash index 39f3357..a594777 100644 --- a/9999-mariadb-init.bash +++ b/9999-mariadb-init.bash @@ -52,6 +52,9 @@ if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then chown -R mysql:mysql /run/mysqld fi + MARIADB_INIT_WAIT_SECONDS=${MARIADB_INIT_WAIT_SECONDS:-30} + MARIADB_INIT_PERIOD_SECONDS=${MARIADB_INIT_PERIOD_SECONDS:-1} + # @note: If data dir exists and is not empty - most likely the DB has # already been initialised. if [ -d ${MARIADB_DATA_DIR:-/var/lib/mysql} ] && [ "$(ls -A "${MARIADB_DATA_DIR:-/var/lib/mysql}")" ]; then @@ -70,18 +73,21 @@ if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then pid="$!" echo "pid is $pid" - for i in {30..0}; do + for i in $(seq 0 $MARIADB_INIT_WAIT_SECONDS); do if echo 'SELECT 1' | mysql -u root; then break fi echo 'MySQL init process in progress...' - sleep 1 + sleep $MARIADB_INIT_PERIOD_SECONDS done # @note: Added a flag to force upgrade. if [ "${FORCE_MYSQL_UPGRADE:-}" = "1" ]; then echo "starting mysql upgrade" - mysql_upgrade --force + # @note: mariadb-upgrade may fail on the first run due to the unresolved + # permissions, but will succeed on the second run. + # @see https://mariadb.com/kb/en/mariadb-upgrade/# + mariadb-upgrade --force || mariadb-upgrade --force fi if ! kill -s TERM "$pid" || ! wait "$pid"; then @@ -98,12 +104,12 @@ if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then pid="$!" echo "pid is $pid" - for i in {30..0}; do + for i in $(seq 0 $MARIADB_INIT_WAIT_SECONDS); do if echo 'SELECT 1' | mysql -u root; then break fi echo 'MySQL init process in progress...' - sleep 1 + sleep $MARIADB_INIT_PERIOD_SECONDS done if [ "$MARIADB_ROOT_PASSWORD" = "" ]; then @@ -124,6 +130,8 @@ if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then DROP DATABASE IF EXISTS test; USE mysql; ALTER USER root@localhost IDENTIFIED VIA mysql_native_password USING PASSWORD("$MARIADB_ROOT_PASSWORD"); +DELETE FROM global_priv WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1'); +DELETE FROM proxies_priv WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1'); FLUSH PRIVILEGES; EOF @@ -148,13 +156,14 @@ EOF echo "[mysql]" >> ${MARIADB_DATA_DIR:-/var/lib/mysql}/.my.cnf echo "database=${MARIADB_DATABASE}" >> ${MARIADB_DATA_DIR:-/var/lib/mysql}/.my.cnf - for f in `ls /docker-entrypoint-initdb.d/*`; do - case "$f" in - *.sh) echo "$0: running $f"; . "$f" ;; - *.sql) echo "$0: running $f"; cat $f| envsubst | tee | mysql -u root -p${MARIADB_ROOT_PASSWORD}; echo ;; - *) echo "$0: ignoring $f" ;; - esac - echo + for f in /docker-entrypoint-initdb.d/*; do + if [ -e "$f" ]; then + case "$f" in + *.sh) echo "$0: running $f"; . "$f" ;; + *.sql) echo "$0: running $f"; cat $f| envsubst | tee | mysql -u root -p${MARIADB_ROOT_PASSWORD}; echo ;; + *) echo "$0: ignoring $f" ;; + esac + fi done if ! kill -s TERM "$pid" || ! wait "$pid"; then @@ -165,6 +174,7 @@ EOF fi echo "done, now starting daemon" + touch /tmp/mariadb-init-complete fi # LCOV_EXCL_END diff --git a/Dockerfile b/Dockerfile index b4ff7e3..d02d335 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,22 +7,21 @@ # support setting data directory as an environment variable) to support new # location and overriding default CMD to include our custom data directory. # -FROM uselagoon/mariadb-10.11-drupal:24.8.0 +FROM uselagoon/mariadb-10.11-drupal:24.11.0 # Set the data directory to a different location that a mounted volume. -ENV MARIADB_DATA_DIR=/var/lib/db-data +ENV MARIADB_DATA_DIR=/home/db-data # Add customised entrypoint script. COPY 9999-mariadb-init.bash /lagoon/entrypoints/ +# Create the custom data directory and set permissions. USER root - -RUN mkdir -p /var/lib/db-data \ - && chown -R mysql /var/lib/db-data \ - && chgrp -R mysql /var/lib/db-data \ - && /bin/fix-permissions /var/lib/db-data - +RUN mkdir -p /home/db-data \ + && chown -R mysql:mysql /home/db-data \ + && /bin/fix-permissions /home/db-data \ + && chmod -R 775 /home/db-data USER mysql # @todo Try removing the CMD override. -CMD ["mysqld", "--datadir=/var/lib/db-data"] +CMD ["mysqld", "--datadir=/home/db-data"] diff --git a/Dockerfile.seed b/Dockerfile.seed index a1b11e9..5567cd7 100644 --- a/Dockerfile.seed +++ b/Dockerfile.seed @@ -1,16 +1,12 @@ -ARG SEED_IMAGE=drevops/mariadb-drupal-data:latest +ARG BASE_IMAGE=drevops/mariadb-drupal-data:latest -FROM ${SEED_IMAGE} - -# Data directory to copy from. -ARG SRC_DATADIR=.data - -COPY ${SRC_DATADIR} /var/lib/db-data/ +FROM ${BASE_IMAGE} USER root -RUN chown -R mysql /var/lib/db-data \ - && chgrp -R mysql /var/lib/db-data \ - && /bin/fix-permissions /var/lib/db-data +COPY .data /home/db-data/ +RUN chown -R mysql:mysql /home/db-data \ + && /bin/fix-permissions /home/db-data \ + && chmod -R 775 /home/db-data USER mysql diff --git a/README.md b/README.md index 465cf1c..91f27d4 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ captured as a Docker layer and stored as an image to docker registry. Image consumers download the image and start containers with instantaneously available data (no time-consuming database imports required). -Technically, majority of the functionality is relying on upstream [`uselagoon/mariadb-drupal`](https://github.com/uselagoon/lagoon-images/blob/main/images/mariadb-drupal/10.6.Dockerfile) Docker image. +Technically, majority of the functionality is relying on upstream [`uselagoon/mariadb-drupal`](https://github.com/uselagoon/lagoon-images/blob/main/images/mariadb-drupal/10.11.Dockerfile) Docker image. [Entrypoint script](entrypoint.bash) had to be copied from [upstream script](https://github.com/uselagoon/lagoon-images/blob/main/images/mariadb/entrypoints/9999-mariadb-init.bash) and adjusted to support custom data directory. ## Use case @@ -80,4 +80,4 @@ This image is built and pushed automatically to DockerHub: Versions are following versions of the [upstream image](https://hub.docker.com/r/uselagoon/mariadb-drupal/tags) to ease maintenance. --- -Repository created using https://getscaffold.dev/ project scaffold template +_This repository was created using the [Scaffold](https://getscaffold.dev/) project template_ diff --git a/seed-db.sh b/seed-db.sh index 410ea31..1df3ee1 100755 --- a/seed-db.sh +++ b/seed-db.sh @@ -2,8 +2,8 @@ ## # Seed image with a database from file. # -# The seeding process has 3-phases build: -# 1. Create extracted DB files by starting a temporary container and importing database. +# The seeding process has 3-phases: +# 1. Create extracted DB files by starting a temporary container and importing the database. # 2. Build a new image from the base image and extracted DB files. # 3. Start a container from the new image and verify that the database was imported. # @@ -15,7 +15,7 @@ # shellcheck disable=SC2002,SC2015 set -eu -[ -n "${DREVOPS_DEBUG:-}" ] && set -x +[ -n "${DEBUG:-}" ] && set -x # Database dump file as a first argument to the script. DB_FILE="${DB_FILE:-$1}" @@ -42,11 +42,12 @@ LOG_DIR="${LOG_DIR:-.logs}" # Temporary data directory on host. TMP_DATA_DIR="${TMP_DATA_DIR:-.data}" +# Show verbose output. +LOG_IS_VERBOSE="${LOG_IS_VERBOSE:-}" + # ------------------------------------------------------------------------------ # @formatter:off -#info() { printf "%s\n" "$1"; echo;} - info() { [ -z "${TERM_NO_COLOR:-}" ] && tput colors >/dev/null 2>&1 && printf "\n[\033[36mINFO\033[0m] %s\n\n" "$1" || printf "\n[INFO] %s\n" "$1"; } task() { [ -z "${TERM_NO_COLOR:-}" ] && tput colors >/dev/null 2>&1 && printf "[\033[34mTASK\033[0m] %s\n" "$1" || printf "[TASK] %s\n" "$1"; } pass() { [ -z "${TERM_NO_COLOR:-}" ] && tput colors >/dev/null 2>&1 && printf "[ \033[32mOK\033[0m ] %s\n" "$1" || printf "[ OK ] %s\n" "$1"; } @@ -60,20 +61,65 @@ note() { printf " %s\n" "$1"; } [ "${BASE_IMAGE##*/}" = "$BASE_IMAGE" ] && fail "${BASE_IMAGE} should be in a format myorg/myimage." && exit 1 [ "${DST_IMAGE##*/}" = "$DST_IMAGE" ] && fail "${DST_IMAGE} should be in a format myorg/myimage." && exit 1 +# Function to collect logs and display them on script exit. +cleanup() { + if [ $? -ne 0 ]; then + fail "Collecting logs after failure." + if [ -d "${LOG_DIR}" ] && [ -z "${LOG_IS_VERBOSE}" ]; then + for log_file in "${LOG_DIR}"/*.log; do + echo + note "--- Displaying ${log_file} ---" + echo + cat "${log_file}" + echo + done + else + note "No logs available to display." + fi + fi +} + +trap cleanup EXIT + log_container() { + name="${1?Missing log name}" + prefix=${2:-} + mkdir -p "${LOG_DIR}" >/dev/null - docker logs "${1}" >>"${LOG_DIR}/${2:-}${1}.log" 2>&1 + log_file="${LOG_DIR}/${prefix}${name}.log" + + if [ -n "${LOG_IS_VERBOSE}" ]; then + docker logs "${1}" | tee -a "${log_file}" + else + docker logs "${1}" &>>"${log_file}" + fi } wait_for_db_service() { + cid="${1}" + + user=() + if [ -n "${2-}" ]; then + user=("--user=${2}") + fi + echo -n " Waiting for the service to become ready." - docker exec --user 1000 -i "${1}" sh -c "until nc -z localhost 3306; do sleep 1; echo -n .; done; echo" + if ! docker exec "${user[@]}" -i "${cid}" sh -c "until nc -z localhost 3306; do sleep 1; echo -n .; done; echo"; then + fail "MYSQL service did not start successfully." + log_container "${cid}" + return 1 + fi log_container "${cid}" pass "MYSQL is running." } assert_db_system_tables_present() { - if docker exec --user 1000 "${1}" /usr/bin/mysql -e "show tables from information_schema;" | grep -q user_variables; then + user=() + if [ -n "${2-}" ]; then + user=("--user=${2}") + fi + + if docker exec "${user[@]}" "${1}" /usr/bin/mysql -e "show tables from information_schema;" | grep -q user_variables; then pass "Database system tables present." else pass "Database system tables are not present in container ${1}" @@ -82,7 +128,12 @@ assert_db_system_tables_present() { } assert_db_was_imported() { - if docker exec --user 1000 "${1}" /usr/bin/mysql -e "show tables;" | grep -q users; then + user=() + if [ -n "${2-}" ]; then + user=("--user=${2}") + fi + + if docker exec "${user[@]}" "${1}" /usr/bin/mysql -e "show tables;" | grep -q users; then pass "Imported database exists." else fail "Imported database does not exist in container ${1}" @@ -92,11 +143,19 @@ assert_db_was_imported() { start_container() { task "Start container from the image ${1}" - cid=$(docker run -d --rm "${1}" 2>"$LOG_DIR"/container-start.log) + + user=() + if [ -n "${2-}" ]; then + user=("--user=${2}") + fi + + cid=$(docker run "${user[@]}" -d "${1}" 2>"$LOG_DIR"/container-start.log) cat "${LOG_DIR}"/container-start.log >>"$LOG_DIR/${cid}.log" && rm "${LOG_DIR}"/container-start.log || true + + wait_for_db_service "${cid}" "${2-}" + assert_db_system_tables_present "${cid}" "${2-}" + pass "Started container ${cid}" - wait_for_db_service "${cid}" - assert_db_system_tables_present "${cid}" } get_started_container_id() { @@ -104,7 +163,7 @@ get_started_container_id() { } stop_container() { - task "Stop and removing container ${1}" + task "Stop and remove container ${1}" # Log container output before stopping it into a separate log file for debugging. log_container "${1}" "stopped-" docker stop "${1}" >/dev/null @@ -134,7 +193,7 @@ fi note "Destination image: ${DST_IMAGE}" note "Destination platform(s): ${DESTINATION_PLATFORMS}" -info "Stage 1: Produce database structure files from dump" +info "Stage 1: Produce database structure files from dump file" start_container "${BASE_IMAGE}" cid="$(get_started_container_id "${BASE_IMAGE}")" @@ -143,29 +202,42 @@ task "Import database from the ${DB_FILE} file." cat "${DB_FILE}" | docker exec -i "${cid}" /usr/bin/mysql assert_db_was_imported "${cid}" +#task "Release locks." +#docker exec "${cid}" /usr/bin/mysql -e "FLUSH TABLES WITH READ LOCK;" +#docker exec "${cid}" /usr/bin/mysql -e "UNLOCK TABLES;" +#docker exec "${cid}" sh -c "mysql_upgrade --force" +#docker exec "${cid}" /usr/bin/mysql -e "FLUSH TABLES WITH READ LOCK;" +#assert_db_was_imported "${cid}" +#pass "Released locks." + task "Update permissions on the seeded database files." -docker exec "${cid}" bash -c "chown -R mysql /var/lib/db-data && /bin/fix-permissions /var/lib/db-data" || true +docker exec "${cid}" bash -c "chown -R mysql /home/db-data && /bin/fix-permissions /home/db-data" || true pass "Updated permissions on the seeded database files." task "Copy expanded database files to host" mkdir -p "${TMP_DATA_DIR}" -docker cp "${cid}":/var/lib/db-data/. "${TMP_DATA_DIR}/" +docker cp "${cid}":/home/db-data/. "${TMP_DATA_DIR}/" >/dev/null [ ! -d "${TMP_DATA_DIR}/mysql" ] && fail "Unable to copy expanded database files to host " && ls -al "${TMP_DATA_DIR}" && exit 1 pass "Copied expanded database files to host" +task "Remove logs and temporary database files" +#rm -f "${TMP_DATA_DIR}"/ib_logfile* || true +rm -f "${TMP_DATA_DIR}"/aria_log* || true +pass "Removed logs and temporary database files" + stop_container "${cid}" info "Stage 2: Build image" -task "Build image ${DST_IMAGE} for ${DESTINATION_PLATFORMS} platform(s)." -docker buildx build --no-cache --platform "${DESTINATION_PLATFORMS}" --tag "${DST_IMAGE}" --push -f Dockerfile.seed . -pass "Built image ${DST_IMAGE} for ${DESTINATION_PLATFORMS} platform(s)." +task "Build image ${DST_IMAGE} for ${DESTINATION_PLATFORMS} platform(s) from ${BASE_IMAGE}." +docker buildx build -D --no-cache --build-arg="BASE_IMAGE=${BASE_IMAGE}" --platform "${DESTINATION_PLATFORMS}" --tag "${DST_IMAGE}" --push -f Dockerfile.seed . +pass "Built image ${DST_IMAGE} for ${DESTINATION_PLATFORMS} platform(s) from ${BASE_IMAGE}." info "Stage 3: Test image" -start_container "${DST_IMAGE}" +start_container "${DST_IMAGE}" 1000 cid="$(get_started_container_id "${DST_IMAGE}")" -assert_db_was_imported "${cid}" +assert_db_was_imported "${cid}" 1000 stop_container "${cid}" info "Finished database seeding." diff --git a/tests/bats/_helper.bash b/tests/bats/_helper.bash index bb19eec..fc1dfc3 100644 --- a/tests/bats/_helper.bash +++ b/tests/bats/_helper.bash @@ -50,16 +50,13 @@ setup() { fi # LCOV_EXCL_END - if [ "$(uname -m)" = "arm64" ]; then - export DOCKER_DEFAULT_PLATFORM=linux/amd64 - fi - - if [ -n "${DOCKER_DEFAULT_PLATFORM-}" ]; then - step "Using ${DOCKER_DEFAULT_PLATFORM} platform architecture." - fi + export DOCKER_DEFAULT_PLATFORM="${DOCKER_DEFAULT_PLATFORM:-linux/amd64}" + step "Using ${DOCKER_DEFAULT_PLATFORM} platform architecture." - # Due to a limitation in buildx, we are building for a single platform for these tests. + # Due to a limitation in buildx driver to build multi-platform images in some + # OSes (like MacOS), we are building for a single platform by default. export BUILDX_PLATFORMS="${DOCKER_DEFAULT_PLATFORM:-linux/amd64}" + step "Building for ${BUILDX_PLATFORMS} platforms." export DOCKER_BUILDKIT=1 export TEST_DOCKER_TAG_PREFIX="bats-test-" @@ -106,3 +103,12 @@ random_string_lower() { ret=$(cat /dev/urandom | env LC_CTYPE=C tr -dc 'a-z0-9' | fold -w "${len}" | head -n 1) echo "${ret}" } + +wait_mysql() { + cid="${1?Missing container ID}" + substep "Wait for mysql to start in container ${cid}." + if ! docker exec --user 1000 -i "${cid}" sh -c "until nc -z localhost 3306; do sleep 1; echo -n .; done; echo" 1>&3; then + docker logs "${cid}" + exit 1 + fi +} diff --git a/tests/bats/data.bats b/tests/bats/data.bats index efc4be8..6bb5864 100644 --- a/tests/bats/data.bats +++ b/tests/bats/data.bats @@ -2,78 +2,92 @@ # # Test functionality. # -# bats --tap tests/bats/data.bats +# tests/bats/node_modules/.bin/bats --tap tests/bats/data.bats # -# In some cases, shell may report platform incorrectly. Run with forced platform: -# DOCKER_DEFAULT_PLATFORM=linux/amd64 bats --tap tests/bats/data.bats +# Note that these tests will always run only for the linux/amd64 platform by +# default. To run the tests for other platforms, set the BUILDX_PLATFORMS and +# DOCKER_DEFAULT_PLATFORM environment variables to the desired platform(s). But +# make sure that the platform is supported by the Docker buildx driver. # +# BUILDX_PLATFORMS=linux/arm64 DOCKER_DEFAULT_PLATFORM=linux/arm64 tests/bats/node_modules/.bin/bats --tap tests/bats/data.bats +# +# Make sure to commit the source code change before running the tests as it +# copies the source code at the last commit to the test directory. load _helper -@test "Data is preserved in an image captured from running container" { +@test "Data is preserved in an image captured from the running container" { tag="${TEST_DOCKER_TAG_PREFIX}$(random_string_lower)" - # Using a local image for this test. - image="testorg/tesimagebase:${tag}" + # Using a local image for this test. The image will be loaded into the Docker + # engine from the buildx cache below. + base_image="testorg/tesimagebase:${tag}" + + step "Prepare base image." - step "Build default image ${image} and load into 'docker images'." - docker buildx build --platform "${BUILDX_PLATFORMS}" --load -t "${image}" . + substep "Build base image ${base_image} and load into 'docker images'." + docker buildx build --platform "${BUILDX_PLATFORMS}" --load -t "${base_image}" . - step "Starting new detached container from the built image." - run docker run --user 1000 -d "${image}" 2>/dev/null + substep "Starting new detached container from the built base image." + run docker run --user 1000 -d "${base_image}" 2>/dev/null assert_success cid="${output}" substep "Started container ${cid}" - step "Assert that the database directory is present." - docker exec --user 1000 "${cid}" test -d /var/lib/db-data + substep "Assert that the database directory is present in the base image." + docker exec --user 1000 "${cid}" test -d /home/db-data + + # The entrypoint script should have created the initial database structure + # and the 'drupal' database directory, but not the database tables. + substep "Assert that the database directory is present, but the database directory is empty in the base image." + docker exec --user 1000 "${cid}" bash -c '[ -d /home/db-data ] && [ -z "$(ls -A /home/db-data/drupal/users*)" ]' - step "Wait for mysql to start." - sleep 5 + wait_mysql "${cid}" - step "Assert that the database is present after start." + substep "Assert that the database is present in the container." run docker exec --user 1000 "${cid}" mysql -e "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'drupal';" assert_success assert_contains "drupal" "${output}" - step "Assert that the table is not present after start." + substep "Assert that the created table is not present in the container." run docker exec --user 1000 "${cid}" mysql -e "USE 'drupal'; show tables like 'mytesttable';" assert_success assert_not_contains "mytesttable" "${output}" - step "Create a table in the database." + step "Assert capturing of the data into the image." + + substep "Create a table in the database." docker exec --user 1000 "${cid}" mysql -e "USE 'drupal'; CREATE TABLE mytesttable(c CHAR(20) CHARACTER SET utf8 COLLATE utf8_bin);" - step "Assert that the table is present after creation." + substep "Assert that the table is present after creation." run docker exec --user 1000 "${cid}" mysql -e "USE 'drupal'; show tables like 'mytesttable';" assert_success assert_contains "mytesttable" "${output}" - step "Commit an image from the last container and get the image ID." + substep "Commit an image from the last container and get the image ID." run docker commit "${cid}" assert_success committed_image_id="${output}" substep "Created new committed image ${committed_image_id}." - step "Create a new tag for committed image." - new_image="${image}-latest" + substep "Create a new tag for committed image." + new_image="${base_image}-latest" docker tag "${committed_image_id}" "${new_image}" substep "Tagged committed image ${committed_image_id} as ${new_image}." - step "Start new container from the tagged committed image ${new_image}." + substep "Start a new container from the tagged committed image ${new_image}." run docker run --user 1000 -d "${new_image}" assert_success new_cid="${output}" substep "Started new container ${new_cid}" - step "Wait for mysql to start." - sleep 5 + wait_mysql "${new_cid}" - step "Assert that the database is present after restart." + substep "Assert that the database is present after restart." run docker exec --user 1000 "${new_cid}" mysql -e "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'drupal';" assert_success assert_contains "drupal" "${output}" - step "Assert that the table is present after restart." + substep "Assert that the table is present after restart." run docker exec --user 1000 "${new_cid}" mysql -e "USE 'drupal'; show tables like 'mytesttable';" assert_success assert_contains "mytesttable" "${output}" @@ -81,44 +95,56 @@ load _helper @test "Seeding of the data works" { tag="${TEST_DOCKER_TAG_PREFIX}$(random_string_lower)" - export BASE_IMAGE="testorg/tesimagebase:${tag}" + export BASE_IMAGE="drevops/mariadb-drupal-data-test:${tag}-base" + dst_image="drevops/mariadb-drupal-data-test:${tag}-dst" - step "Build fresh image tagged with ${BASE_IMAGE}." - docker buildx build --platform "${BUILDX_PLATFORMS}" --load --no-cache -t "${BASE_IMAGE}" . + step "Prepare base image." - step "Download fixture DB dump." + substep "Copying fixture DB dump." file="${BUILD_DIR}/db.sql" - CURL_DB_URL=https://raw.githubusercontent.com/wiki/drevops/drevops/db.demo.sql.md - curl -L "${CURL_DB_URL}" -o "${file}" + cp "${BATS_TEST_DIRNAME}/fixtures/db.sql" "${file}" - step "Run DB seeding script from base image ${BASE_IMAGE}" - dst_image="drevops/mariadb-drupal-data-test:${tag}" - run ./seed-db.sh "${file}" "${dst_image}" - assert_success + substep "Build and push a fresh base image tagged with ${BASE_IMAGE}." + docker buildx build --platform "${BUILDX_PLATFORMS}" --load --push --no-cache -t "${BASE_IMAGE}" . + + step "Assert seeding without mysql upgrade works." - step "Start container from the seeded image ${dst_image}." + # Pass the destination platform to the seeding script. + # Note that the name for the variable `BUILDX_PLATFORMS` in the test was + # chosen to be different from the `DESTINATION_PLATFORMS` in the seeding + # script to separate building images when preparing the test environment + # from the seeding process. + export DESTINATION_PLATFORMS="${BUILDX_PLATFORMS}" + substep "Run DB seeding script for ${dst_image} from the base image ${BASE_IMAGE} for destination platform(s) ${DESTINATION_PLATFORMS}." + ./seed-db.sh "${file}" "${dst_image}" >&3 + + substep "Start container from the seeded image ${dst_image}." # Start container with a non-root user to imitate limited host permissions. - cid=$(docker run --user 1000 -d --rm "${dst_image}") - substep "Waiting for the service to become ready." - docker exec -i "${cid}" sh -c "until nc -z localhost 3306; do sleep 1; echo -n .; done; echo" + cid=$(docker run --user 1000 -d "${dst_image}" 2>&3) + + wait_mysql "${cid}" - step "Assert that data was captured into the new image." - run docker exec "${cid}" /usr/bin/mysql -e "use drupal;show tables;" drupal + substep "Assert that data was captured into the new image." + run docker exec --user 1000 "${cid}" /usr/bin/mysql -e "use drupal;show tables;" drupal assert_output_contains "users" + substep "Assert that the mysql upgrade was skipped by default." run docker logs "${cid}" assert_output_not_contains "starting mysql upgrade" - step "Start container from the seeded image ${dst_image} and request an upgrade." + step "Assert mysql upgrade works in container started from already seeded image." + + substep "Start container from the seeded image ${dst_image} and request an upgrade." # Start container with a non-root user to imitate limited host permissions. - cid=$(docker run --user 1000 -d -e FORCE_MYSQL_UPGRADE=1 --rm "${dst_image}") - substep "Waiting for the service to become ready." - docker exec -i "${cid}" sh -c "until nc -z localhost 3306; do sleep 1; echo -n .; done; echo" + cid=$(docker run --user 1000 -d -e FORCE_MYSQL_UPGRADE=1 "${dst_image}") - step "Assert that data was captured into the new image." - run docker exec "${cid}" /usr/bin/mysql -e "use drupal;show tables;" drupal - assert_output_contains "users" + wait_mysql "${cid}" + substep "Assert that the mysql upgrade was performed." run docker logs "${cid}" assert_output_contains "starting mysql upgrade" + + substep "Assert that data is present in the new image after the upgrade." + run docker exec --user 1000 "${cid}" /usr/bin/mysql -e "use drupal;show tables;" drupal + assert_output_contains "users" } diff --git a/tests/bats/fixtures/db.sql b/tests/bats/fixtures/db.sql new file mode 100644 index 0000000..6f523d9 --- /dev/null +++ b/tests/bats/fixtures/db.sql @@ -0,0 +1,66 @@ +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +SET NAMES utf8mb4; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE='NO_AUTO_VALUE_ON_ZERO', SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +# Dump of table users +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `users`; + +CREATE TABLE `users` ( + `uid` int(10) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(128) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL, + `langcode` varchar(12) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL, + PRIMARY KEY (`uid`), + UNIQUE KEY `user_field__uuid__value` (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='The base table for user entities.'; + +LOCK TABLES `users` WRITE; +/*!40000 ALTER TABLE `users` DISABLE KEYS */; + +INSERT INTO `users` (`uid`, `uuid`, `langcode`) +VALUES + (0,'8f30220c-4289-4316-920f-328e73f0df23','en'), + (1,'a15e5172-e176-4bce-8ce0-87dbbb3156e0','en'); + +/*!40000 ALTER TABLE `users` ENABLE KEYS */; +UNLOCK TABLES; + + +# Dump of table watchdog +# ------------------------------------------------------------ + +DROP TABLE IF EXISTS `watchdog`; + +CREATE TABLE `watchdog` ( + `wid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key: Unique watchdog event ID.', + `uid` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'The "users".uid of the user who triggered the event.', + `type` varchar(64) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT 'Type of log message, for example "user" or "page not found."', + `message` longtext NOT NULL COMMENT 'Text of log message to be passed into the t() function.', + `variables` longblob NOT NULL COMMENT 'Serialized array of variables that match the message string and that is passed into the t() function.', + `severity` tinyint(3) unsigned NOT NULL DEFAULT 0 COMMENT 'The severity level of the event. ranges from 0 (Emergency) to 7 (Debug)', + `link` text DEFAULT NULL COMMENT 'Link to view the result of the event.', + `location` text NOT NULL COMMENT 'URL of the origin of the event.', + `referer` text DEFAULT NULL COMMENT 'URL of referring page.', + `hostname` varchar(128) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT 'Hostname of the user who triggered the event.', + `timestamp` bigint(20) NOT NULL DEFAULT 0 COMMENT 'Unix timestamp of when event occurred.', + PRIMARY KEY (`wid`), + KEY `type` (`type`), + KEY `uid` (`uid`), + KEY `severity` (`severity`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Table that contains logs of all system events.'; + + + + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/tests/dgoss/goss.yaml b/tests/dgoss/goss.yaml index 2e7f166..9c550e3 100644 --- a/tests/dgoss/goss.yaml +++ b/tests/dgoss/goss.yaml @@ -7,7 +7,7 @@ file: exists: true contains: [] - /var/lib/db-data: + /home/db-data: exists: true contains: []