aboutsummaryrefslogtreecommitdiffstats
path: root/build/integration/run-docker.sh
blob: 11563c9aa2140a6faaa1e4569e7f9ab257baa269 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/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/tcp/$DATABASE_IP/$DATABASE_PORT) >/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.1: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"