.gitlab/ci/review-app.yml
.review_app_scripts:
assign_port:
# Assign an unprivileged port for the web server based on MR ID.
- |
APP_PORT=$(( $CI_MERGE_REQUEST_ID % 64000 + 1024 ))
WS_PORT=$(( $APP_PORT + 1000 ))
ASSETS_PORT=$(( $APP_PORT + 2000 ))
echo "Assigned port number $APP_PORT."
build_docker_compose_files:
- !reference [.review_app_scripts, assign_port]
- |
echo "Creating docker compose files…"
curl https://raw.githubusercontent.com/zammad/zammad-docker-compose/master/docker-compose.yml > docker-compose.yml
curl https://raw.githubusercontent.com/zammad/zammad-docker-compose/master/.env > .env
cat - <<YML_FILE > docker-compose.review-app.yml
---
version: '3'
services:
zammad-init:
environment:
- MEMCACHE_SERVERS=\${MEMCACHE_SERVERS}
- POSTGRESQL_USER=\${POSTGRES_USER}
- POSTGRESQL_PASS=\${POSTGRES_PASS}
- REDIS_URL=\${REDIS_URL}
- ZAMMAD_HTTP_TYPE=https
- ZAMMAD_FQDN=${APP_FQDN}
zammad-railsserver:
ports:
- "127.0.0.1:${APP_PORT}:3000"
zammad-websocket:
ports:
- "127.0.0.1:${WS_PORT}:6042"
zammad-nginx:
ports:
- "127.0.0.1:${ASSETS_PORT}:8080"
zammad-backup:
profiles:
- do-not-start
YML_FILE
cat - <<ENV_FILE >> .env
IMAGE_REPO=${CI_REGISTRY}/${CI_PROJECT_PATH}
VERSION=${APP_NAME}
ENV_FILE
# cat docker-compose* .env
# Check if containers were previously created, so that this is an update deployment
- |
APP_DEPLOYED_BEFORE=$(${DOCKER_COMPOSE_COMMAND} ps --all --quiet)
if [ "$APP_DEPLOYED_BEFORE" ]
then
echo "This review app was previously deployed."
else
echo "This review app was not deployed yet."
fi
build_and_push_docker_image:
# Build/update docker image and publish it to the GitLab container registry.
- |
echo "Build docker image…"
docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
echo -e "\\e[0Ksection_start:`date +%s`:docker_build[collapsed=true]\\r\\e[0Kdocker build"
if [ ! "${SKIP_DOCKER_BUILD}" ]
then
docker build --pull --no-cache -t $IMAGE_URL .
docker push $IMAGE_URL
fi
echo -e "\\e[0Ksection_end:`date +%s`:docker_build\\r\\e[0K"
create_auto_wizard_json:
- |
cat - <<JSON_FILE > tmp/auto_wizard.json
{
"Token": "${CI_JOB_TOKEN}",
"Users": [
{
"login": "admin@example.com",
"firstname": "Test Admin",
"lastname": "Agent",
"email": "admin@example.com",
"password": "${CI_MERGE_REQUEST_ID}"
},
{
"login": "agent1@example.com",
"firstname": "Agent 1",
"lastname": "Test",
"email": "agent1@example.com",
"password": "${CI_MERGE_REQUEST_ID}",
"roles": ["Agent"]
}
],
"Groups": [
{
"name": "some group1",
"users": ["admin@example.com", "agent1@example.com"]
},
{
"name": "Users",
"users": ["admin@example.com", "agent1@example.com"],
"signature": "default",
"email_address_id": 1
}
],
"Channels": [
{
"id": 1,
"area": "Email::Account",
"group": "Users",
"options": {
"inbound": {
"adapter": "imap",
"options": {
"host": "mx1.example.com",
"user": "not_existing",
"password": "not_existing",
"ssl": true
}
},
"outbound": {
"adapter": "sendmail"
}
}
}
],
"EmailAddresses": [
{
"id": 1,
"channel_id": 1,
"name": "Zammad Helpdesk",
"email": "zammad@localhost"
}
],
"Settings": [
{
"name": "product_name",
"value": "Zammad Review App ${APP_NAME}"
},
{
"name": "http_type",
"value": "https"
},
{
"name": "fqdn",
"value": "${APP_FQDN}"
},
{
"name": "developer_mode",
"value": true
}
],
"TextModuleLocale": {
"Locale": "de-de"
}
}
JSON_FILE
create_vhost_config:
- |
cat - <<VHOST_CONFIG > /etc/nginx/conf.d/vhost-includes/${APP_NAME}.conf
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${APP_SLUG}.${SERVER_NAME};
client_max_body_size 50M;
location ~ ^/(assets/|robots.txt|humans.txt|favicon.ico|apple-touch-icon.png) {
expires max;
proxy_pass http://127.0.0.1:${ASSETS_PORT};
}
location / {
proxy_set_header Host \$http_host;
proxy_set_header CLIENT_IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
# Change this line in an SSO setup
proxy_set_header X-Forwarded-User "";
proxy_read_timeout 300;
proxy_pass http://127.0.0.1:${APP_PORT};
gzip on;
gzip_types text/plain text/xml text/css image/svg+xml application/javascript application/x-javascript application/json application/xml;
gzip_proxied any;
}
# legacy web socket server
location /ws {
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header CLIENT_IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 86400;
proxy_pass http://127.0.0.1:${WS_PORT};
}
# action cable
location /cable {
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header CLIENT_IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 86400;
proxy_pass http://127.0.0.1:${APP_PORT};
}
}
VHOST_CONFIG
delete_vhost_config:
- rm -f /etc/nginx/conf.d/vhost-includes/${APP_NAME}.conf
reload_nginx:
- sudo systemctl reload nginx.service
.review-app:
tags:
- review-apps
stage: pre
cache: []
variables:
SERVER_NAME: review-apps.dc.zammad.com
APP_SLUG: $CI_COMMIT_REF_SLUG
APP_NAME: review-app-${APP_SLUG}
APP_FQDN: ${APP_SLUG}.${SERVER_NAME}
IMAGE_URL: ${CI_REGISTRY}/${CI_PROJECT_PATH}:${APP_NAME}
DOCKER_COMPOSE_COMMAND: docker compose -f docker-compose.yml -f docker-compose.review-app.yml -p ${APP_NAME}
environment:
name: ${APP_NAME}
url: https://${APP_FQDN}#getting_started/auto_wizard/${CI_JOB_TOKEN}
rules:
- if: $CI_MERGE_REQUEST_ID
when: manual
allow_failure: true
- when: never
before_script:
- !reference [.review_app_scripts, build_docker_compose_files]
after_script:
- echo "Clean-up project build directory…" # shell runners don't clean-up, see https://gitlab.com/gitlab-org/gitlab/-/issues/243716
- rm -rf $CI_PROJECT_DIR
- echo "Perform docker clean-up…"
- docker image prune --all --force
- docker volume prune --all --force
- docker network prune --force
review-app:deploy:
extends:
- .review-app
script:
- !reference [.review_app_scripts, build_and_push_docker_image]
# Spawn a new or update an existing docker compose application on the GitLab runner.
# If starting did not complete, bring down the container again to free up resources.
- |
echo "Deploy review app…"
${DOCKER_COMPOSE_COMMAND} up --force-recreate --detach || FAILED=true
if [ $FAILED ]
then
${DOCKER_COMPOSE_COMMAND} down
echo "Failed to bring up container, exiting."
exit 1
fi
# Always provide auto_wizard.json, because we don't know when it will be activated.
- !reference [.review_app_scripts, create_auto_wizard_json]
- docker cp tmp/auto_wizard.json ${APP_NAME}-zammad-railsserver-1:/opt/zammad
- !reference [.review_app_scripts, create_vhost_config]
- !reference [.review_app_scripts, reload_nginx]
environment:
on_stop: review-app:stop
auto_stop_in: 1 month
review-app:stop:
extends:
- .review-app
variables:
GIT_STRATEGY: none
script:
- echo "Stop review app…"
- !reference [.review_app_scripts, delete_vhost_config]
- !reference [.review_app_scripts, reload_nginx]
- ${DOCKER_COMPOSE_COMMAND} down
environment:
action: stop