#!/usr/bin/env bash # SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors # SPDX-License-Identifier: AGPL-3.0-or-later # Helper script to run the integration tests on a fresh Nextcloud server through # Docker. # # The integration tests are run in its own Docker container; the grandparent # directory of the integration tests directory (that is, the root directory of # the Nextcloud server) is copied to the container and the integration tests are # run inside it; in the container the configuration/data from the original # Nextcloud server is ignored, and a new server installation is performed inside # the container instead. Once the tests end the container is stopped. # # To perform its job, the script requires the "docker" command to be available. # # The Docker Command Line Interface (the "docker" command) requires special # permissions to talk to the Docker daemon, and those permissions are typically # available only to the root user. Please see the Docker documentation to find # out how to give access to a regular user to the Docker daemon: # https://docs.docker.com/engine/installation/linux/linux-postinstall/ # # Note, however, that being able to communicate with the Docker daemon is the # same as being able to get root privileges for the system. Therefore, you must # give access to the Docker daemon (and thus run this script as) ONLY to trusted # and secure users: # https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface # # Finally, take into account that this script will automatically remove the # Docker containers named "database-nextcloud-local-test-integration" and # "nextcloud-local-test-integration", even if the script did not create them # (probably you will not have containers nor images with that name, but just in # case). # Sets the variables that abstract the differences in command names and options # between operating systems. # # Switches between mktemp on GNU/Linux and gmktemp on macOS. function setOperatingSystemAbstractionVariables() { case "$OSTYPE" in darwin*) if [ "$(which gtimeout)" == "" ]; then echo "Please install coreutils (brew install coreutils)" exit 1 fi MKTEMP=gmktemp TIMEOUT=gtimeout ;; linux*) MKTEMP=mktemp TIMEOUT=timeout ;; *) echo "Operating system ($OSTYPE) not supported" exit 1 ;; esac } # Launches the database server in a Docker container. # # No server is started if "SQLite" is being used; in other cases the database # is set up as needed and generic "$DATABASE_NAME/USER/PASSWORD" variables # (independent of the database type) are set to be used when installing the # Nextcloud server. # # The Docker container started here will be automatically stopped when the # script exits (see cleanUp). If the database server can not be started then the # script will be exited immediately with an error state. function prepareDatabase() { if [ "$DATABASE" = "sqlite" ]; then return fi DATABASE_CONTAINER=database-nextcloud-local-test-integration DATABASE_NAME=oc_autotest DATABASE_USER=oc_autotest DATABASE_PASSWORD=nextcloud DATABASE_CONTAINER_OPTIONS="--env MYSQL_ROOT_PASSWORD=nextcloud_root --env MYSQL_USER=$DATABASE_USER --env MYSQL_PASSWORD=$DATABASE_PASSWORD --env MYSQL_DATABASE=$DATABASE_NAME" if [ "$DATABASE" = "pgsql" ]; then DATABASE_CONTAINER_OPTIONS=" --env POSTGRES_USER=$DATABASE_USER --env POSTGRES_PASSWORD=$DATABASE_PASSWORD --env POSTGRES_DB=${DATABASE_NAME}_dummy" fi echo "Starting database server" docker run --detach --name=$DATABASE_CONTAINER $DATABASE_CONTAINER_OPTIONS $DATABASE_IMAGE DATABASE_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $DATABASE_CONTAINER) DATABASE_PORT=3306 if [ "$DATABASE" = "pgsql" ]; then DATABASE_PORT=5432 fi echo "Waiting for database server to be ready" if ! $TIMEOUT 600s bash -c "while ! (/dev/null 2>&1; do sleep 1; done"; then echo "Could not start database server after 600 seconds" >&2 exit 1 fi } # Creates a Docker container to run the integration tests. # # This function starts a Docker container with a copy of the Nextcloud code from # the grandparent directory, although ignoring any configuration or data that it # may provide (for example, if that directory was used directly to deploy a # Nextcloud instance in a web server). As the Nextcloud code is copied to the # container instead of referenced the original code can be modified while the # integration tests are running without interfering in them. function prepareDocker() { NEXTCLOUD_LOCAL_CONTAINER=nextcloud-local-test-integration NEXTCLOUD_LOCAL_CONTAINER_NETWORK_OPTIONS="" if [ -n "$DATABASE_CONTAINER" ]; then # The network stack is shared between the database and the Nextcloud # container, so the Nextcloud server can access the database directly on # 127.0.0.1. NEXTCLOUD_LOCAL_CONTAINER_NETWORK_OPTIONS="--network=container:$DATABASE_CONTAINER" fi echo "Starting the Nextcloud container" # When using "nextcloudci/phpX.Y" images the container exits immediately if # no command is given, so a Bash session is created to prevent that. docker run \ --volume composer_cache:/root/.composer \ --detach --name=$NEXTCLOUD_LOCAL_CONTAINER $NEXTCLOUD_LOCAL_CONTAINER_NETWORK_OPTIONS --interactive --tty $NEXTCLOUD_LOCAL_IMAGE bash # Use the $TMPDIR or, if not set, fall back to /tmp. NEXTCLOUD_LOCAL_TAR="$($MKTEMP --tmpdir="${TMPDIR:-/tmp}" --suffix=.tar nextcloud-local-XXXXXXXXXX)" # Setting the user and group of files in the tar would be superfluous, as # "docker cp" does not take them into account (the extracted files are set # to root). echo "Copying local Git working directory of Nextcloud to the container" tar --create --file="$NEXTCLOUD_LOCAL_TAR" \ --exclude=".git" \ --exclude="./config/config.php" \ --exclude="./config/*.config.php" \ --exclude="./data" \ --exclude="./data-autotest" \ --exclude="./tests" \ --exclude="node_modules" \ --directory=../../ \ . docker exec $NEXTCLOUD_LOCAL_CONTAINER mkdir /nextcloud docker cp - $NEXTCLOUD_LOCAL_CONTAINER:/nextcloud/ < "$NEXTCLOUD_LOCAL_TAR" # Database options are needed only when a database other than SQLite is # used. NEXTCLOUD_LOCAL_CONTAINER_INSTALL_DATABASE_OPTIONS="" if [ -n "$DATABASE_CONTAINER" ]; then NEXTCLOUD_LOCAL_CONTAINER_INSTALL_DATABASE_OPTIONS="--database=$DATABASE --database-name=$DATABASE_NAME --database-user=$DATABASE_USER --database-pass=$DATABASE_PASSWORD --database-host=127.0.0.1" fi echo "Installing Nextcloud in the container" docker exec $NEXTCLOUD_LOCAL_CONTAINER bash -c "cd nextcloud && php occ maintenance:install --admin-pass=admin $NEXTCLOUD_LOCAL_CONTAINER_INSTALL_DATABASE_OPTIONS" } # Removes/stops temporal elements created/started by this script. function cleanUp() { # Disable (yes, "+" disables) exiting immediately on errors to ensure that # all the cleanup commands are executed (well, no errors should occur during # the cleanup anyway, but just in case). set +o errexit echo "Cleaning up" if [ -f "$NEXTCLOUD_LOCAL_TAR" ]; then echo "Removing $NEXTCLOUD_LOCAL_TAR" rm $NEXTCLOUD_LOCAL_TAR fi # The name filter must be specified as "^/XXX$" to get an exact match; using # just "XXX" would match every name that contained "XXX". if [ -n "$(docker ps --all --quiet --filter name="^/$NEXTCLOUD_LOCAL_CONTAINER$")" ]; then echo "Removing Docker container $NEXTCLOUD_LOCAL_CONTAINER" docker rm --volumes --force $NEXTCLOUD_LOCAL_CONTAINER fi if [ -n "$DATABASE_CONTAINER" -a -n "$(docker ps --all --quiet --filter name="^/$DATABASE_CONTAINER$")" ]; then echo "Removing Docker container $DATABASE_CONTAINER" docker rm --volumes --force $DATABASE_CONTAINER fi } # Exit immediately on errors. set -o errexit # Execute cleanUp when the script exits, either normally or due to an error. trap cleanUp EXIT # Ensure working directory is script directory, as some actions (like copying # the Git working directory to the container) expect that. cd "$(dirname $0)" # "--image XXX" option can be provided to set the Docker image to use to run # the integration tests (one of the "nextcloudci/phpX.Y:phpX.Y-Z" or # "ghcr.io/nextcloud/continuous-integration-integration-phpX.Y:latest" images). NEXTCLOUD_LOCAL_IMAGE="ghcr.io/nextcloud/continuous-integration-integration-php8.2:latest" if [ "$1" = "--image" ]; then NEXTCLOUD_LOCAL_IMAGE=$2 shift 2 fi # "--database XXX" option can be provided to set the database to use to run the # integration tests (one of "sqlite", "mysql" or "pgsql"; "sqlite" is used # by default). DATABASE="sqlite" if [ "$1" = "--database" ]; then DATABASE=$2 shift 2 fi if [ "$DATABASE" != "sqlite" ] && [ "$DATABASE" != "mysql" ] && [ "$DATABASE" != "pgsql" ]; then echo "--database must be followed by one of: sqlite, mysql or pgsql" exit 1 fi # "--database-image XXX" option can be provided to set the Docker image to use # for the database container (ignored when using "sqlite"). if [ "$DATABASE" = "mysql" ]; then DATABASE_IMAGE="mysql:8.4" elif [ "$DATABASE" = "pgsql" ]; then DATABASE_IMAGE="postgres:15" fi if [ "$1" = "--database-image" ]; then DATABASE_IMAGE=$2 shift 2 fi # If no parameter is provided to this script all the integration tests are run. SCENARIO_TO_RUN=$1 setOperatingSystemAbstractionVariables prepareDatabase prepareDocker echo "Running tests" # --tty is needed to get colourful output. docker exec --tty $NEXTCLOUD_LOCAL_CONTAINER bash -c "cd nextcloud/build/integration && ./run.sh $SCENARIO_TO_RUN"