#!/usr/bin/env bash # Build the production images on the target Docker context and deploy app.a250.ca. set -Eeuo pipefail DOMAIN="${DOMAIN:-app.a250.ca}" STACK_NAME="${STACK_NAME:-atlas}" DEPLOY_CONTEXT="${DOCKER_DEPLOY_CONTEXT:-${DEPLOY_CONTEXT:-}}" DEPLOY_HOST="${DEPLOY_HOST:-app.a250.ca}" STACK_FILE="${STACK_FILE:-stack.production.yml}" SKIP_TESTS="${SKIP_TESTS:-1}" SKIP_HEALTHCHECK="${SKIP_HEALTHCHECK:-1}" PUSH_IMAGES="${PUSH_IMAGES:-1}" REGISTRY_IMAGE="${REGISTRY_IMAGE:-git.nixc.us/a250/ss-atlas}" SS_ATLAS_LOCAL_IMAGE="${SS_ATLAS_LOCAL_IMAGE:-atlas-ss-atlas:production}" SS_ATLAS_PUSH_IMAGE="${SS_ATLAS_PUSH_IMAGE:-$REGISTRY_IMAGE:production}" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" log() { printf '[ship] %s\n' "$*" } fail() { printf '[ship] ERROR: %s\n' "$*" >&2 exit 1 } run() { log "+ $*" "$@" } run_docker() { log "+ docker ${DOCKER_LABEL} $*" docker "${DOCKER_ARGS[@]}" "$@" } require_command() { command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1" } docker_target() { docker "${DOCKER_ARGS[@]}" "$@" } service_exists() { docker_target service inspect "$1" >/dev/null 2>&1 } force_service_image() { local service="$1" local image="$2" if service_exists "$service"; then run_docker service update --force --image "$image" "$service" else log "Service $service is not present yet; stack deploy will create it." fi } cd "$ROOT_DIR" require_command git require_command docker if [ -z "$DEPLOY_CONTEXT" ]; then if docker context inspect macmini7 >/dev/null 2>&1; then DEPLOY_CONTEXT="macmini7" else DEPLOY_CONTEXT="" fi fi if [ -n "$DEPLOY_CONTEXT" ]; then DOCKER_ARGS=(--context "$DEPLOY_CONTEXT") DOCKER_LABEL="--context $DEPLOY_CONTEXT" else DOCKER_ARGS=(-H "ssh://$DEPLOY_HOST") DOCKER_LABEL="-H ssh://$DEPLOY_HOST" fi [ -f "$STACK_FILE" ] || fail "Missing $STACK_FILE" grep -q "$DOMAIN" "$STACK_FILE" || fail "$STACK_FILE does not reference $DOMAIN" if ! docker_target info >/dev/null 2>&1; then fail "Docker target '${DOCKER_LABEL}' is not reachable" fi if [ "$(docker_target info --format '{{.Swarm.LocalNodeState}}')" != "active" ]; then fail "Docker target '${DOCKER_LABEL}' is not an active swarm manager" fi BUILD_COMMIT="$(git rev-parse --short HEAD)" if [ -n "$(git status --porcelain)" ]; then BUILD_COMMIT="${BUILD_COMMIT}-dirty" log "Working tree has uncommitted changes; shipping current checkout as $BUILD_COMMIT." fi BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" log "Shipping $BUILD_COMMIT to https://$DOMAIN via docker ${DOCKER_LABEL}." if [ "$SKIP_TESTS" != "1" ]; then require_command go log "+ (cd docker/ss-atlas && go test ./...)" (cd docker/ss-atlas && go test ./...) else log "Skipping tests because SKIP_TESTS=1." fi run_docker build \ --pull \ --no-cache \ --build-arg "BUILD_COMMIT=$BUILD_COMMIT" \ --build-arg "BUILD_TIME=$BUILD_TIME" \ --label "org.opencontainers.image.revision=$BUILD_COMMIT" \ --label "org.opencontainers.image.created=$BUILD_TIME" \ -t "$SS_ATLAS_LOCAL_IMAGE" \ -t "$SS_ATLAS_PUSH_IMAGE" \ -f docker/ss-atlas/Dockerfile \ docker/ss-atlas if [ "$PUSH_IMAGES" = "1" ]; then run_docker push "$SS_ATLAS_PUSH_IMAGE" else log "Skipping image pushes because PUSH_IMAGES=0." fi run_docker stack deploy \ --with-registry-auth \ --resolve-image never \ -c "$STACK_FILE" \ "$STACK_NAME" force_service_image "${STACK_NAME}_ss-atlas" "$SS_ATLAS_PUSH_IMAGE" log "Current stack tasks:" run_docker stack ps "$STACK_NAME" --no-trunc if [ "$SKIP_HEALTHCHECK" != "1" ]; then require_command curl run curl -fsS "https://$DOMAIN/health" printf '\n' run curl -fsS "https://$DOMAIN/version" printf '\n' else log "Skipping health checks because SKIP_HEALTHCHECK=1." fi log "Done. Requested $BUILD_COMMIT on https://$DOMAIN."