scripts/lifecycle-tests.sh
#!/usr/bin/env bash

set -e

IMAGE=server
CONTAINER=server
LINKED_IMAGE=linked
LINKED_CONTAINER=linked
WATCHTOWER_INTERVAL=2

function remove_container {
	docker kill $1 >> /dev/null || true && docker rm -v $1 >> /dev/null || true
}

function cleanup {
  # Do cleanup on exit or error
  echo "Final cleanup"
  sleep 2
  remove_container $CONTAINER
  remove_container $LINKED_CONTAINER
  pkill -9 -f watchtower >> /dev/null || true
}
trap cleanup EXIT

DEFAULT_WATCHTOWER="$(dirname "${BASH_SOURCE[0]}")/../watchtower"
WATCHTOWER=$1
WATCHTOWER=${WATCHTOWER:-$DEFAULT_WATCHTOWER}
echo "watchtower path is $WATCHTOWER"

##################################################################################
##### PREPARATION ################################################################
##################################################################################

#  Create Dockerfile template
DOCKERFILE=$(cat << EOF
FROM node:alpine

LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="cat /opt/test/value.txt"
LABEL com.centurylinklabs.watchtower.lifecycle.post-update="echo image > /opt/test/value.txt"

ENV IMAGE_TIMESTAMP=TIMESTAMP

WORKDIR /opt/test
ENTRYPOINT ["/usr/local/bin/node", "/opt/test/server.js"]

EXPOSE 8888

RUN mkdir -p /opt/test && echo "default" > /opt/test/value.txt
COPY server.js /opt/test/server.js
EOF
)

# Create temporary directory to build docker image
TMP_DIR="/tmp/watchtower-commands-test"
mkdir -p $TMP_DIR

# Create simple http server
cat > $TMP_DIR/server.js << EOF
const http = require("http");
const fs = require("fs");

http.createServer(function(request, response) {
	const fileContent = fs.readFileSync("/opt/test/value.txt");
	response.writeHead(200, {"Content-Type": "text/plain"});
	response.write(fileContent);
	response.end();
}).listen(8888, () => { console.log('server is listening on 8888'); });
EOF

function builddocker {
	TIMESTAMP=$(date +%s)
	echo "Building image $TIMESTAMP"
	echo "${DOCKERFILE/TIMESTAMP/$TIMESTAMP}" > $TMP_DIR/Dockerfile
	docker build $TMP_DIR -t $IMAGE >> /dev/null
}

# Start watchtower
echo "Starting watchtower"
$WATCHTOWER -i $WATCHTOWER_INTERVAL --no-pull --stop-timeout 2s --enable-lifecycle-hooks $CONTAINER $LINKED_CONTAINER &
sleep 3

echo "#################################################################"
echo "##### TEST CASE 1: Execute commands from base image"
echo "#################################################################"

# Build base image
builddocker

# Run container
docker run -d -p 0.0.0.0:8888:8888 --name $CONTAINER $IMAGE:latest >> /dev/null
sleep 1
echo "Container $CONTAINER is running"

# Test default value
RESP=$(curl -s http://localhost:8888)
if [ $RESP != "default" ]; then
	echo "Default value of container response is invalid" 1>&2
	exit 1
fi

# Build updated image to trigger watchtower update
builddocker

WAIT_AMOUNT=$(($WATCHTOWER_INTERVAL * 3))
echo "Wait for $WAIT_AMOUNT seconds"
sleep $WAIT_AMOUNT

# Test value after post-update-command
RESP=$(curl -s http://localhost:8888)
if [[ $RESP != "image" ]]; then
	echo "Value of container response is invalid. Expected: image. Actual: $RESP"
	exit 1
fi

remove_container $CONTAINER

echo "#################################################################"
echo "##### TEST CASE 2: Execute commands from container and base image"
echo "#################################################################"

# Build base image
builddocker

# Run container
docker run -d -p 0.0.0.0:8888:8888 \
	--label=com.centurylinklabs.watchtower.lifecycle.post-update="echo container > /opt/test/value.txt" \
	--name $CONTAINER $IMAGE:latest >> /dev/null
sleep 1
echo "Container $CONTAINER is running"

# Test default value
RESP=$(curl -s http://localhost:8888)
if [ $RESP != "default" ]; then
	echo "Default value of container response is invalid" 1>&2
	exit 1
fi

# Build updated image to trigger watchtower update
builddocker

WAIT_AMOUNT=$(($WATCHTOWER_INTERVAL * 3))
echo "Wait for $WAIT_AMOUNT seconds"
sleep $WAIT_AMOUNT

# Test value after post-update-command
RESP=$(curl -s http://localhost:8888)
if [[ $RESP != "container" ]]; then
	echo "Value of container response is invalid. Expected: container. Actual: $RESP"
	exit 1
fi

remove_container $CONTAINER

echo "#################################################################"
echo "##### TEST CASE 3: Execute commands with a linked container"
echo "#################################################################"

# Tag the current image to keep a version for the linked container
docker tag $IMAGE:latest $LINKED_IMAGE:latest

# Build base image
builddocker

# Run container
docker run -d -p 0.0.0.0:8888:8888 \
	--label=com.centurylinklabs.watchtower.lifecycle.post-update="echo container > /opt/test/value.txt" \
	--name $CONTAINER $IMAGE:latest >> /dev/null
docker run -d -p 0.0.0.0:8989:8888 \
	--label=com.centurylinklabs.watchtower.lifecycle.post-update="echo container > /opt/test/value.txt" \
	--link $CONTAINER \
	--name $LINKED_CONTAINER $LINKED_IMAGE:latest >> /dev/null
sleep 1
echo "Container $CONTAINER and $LINKED_CONTAINER are running"

# Test default value
RESP=$(curl -s http://localhost:8888)
if [ $RESP != "default" ]; then
	echo "Default value of container response is invalid" 1>&2
	exit 1
fi

# Test default value for linked container
RESP=$(curl -s http://localhost:8989)
if [ $RESP != "default" ]; then
	echo "Default value of linked container response is invalid" 1>&2
	exit 1
fi

# Build updated image to trigger watchtower update
builddocker

WAIT_AMOUNT=$(($WATCHTOWER_INTERVAL * 3))
echo "Wait for $WAIT_AMOUNT seconds"
sleep $WAIT_AMOUNT

# Test value after post-update-command
RESP=$(curl -s http://localhost:8888)
if [[ $RESP != "container" ]]; then
	echo "Value of container response is invalid. Expected: container. Actual: $RESP"
	exit 1
fi

# Test that linked container did not execute pre/post-update-command
RESP=$(curl -s http://localhost:8989)
if [[ $RESP != "default" ]]; then
	echo "Value of linked container response is invalid. Expected: default. Actual: $RESP"
	exit 1
fi