#!/bin/bash

source ./hooks/arches.sh

export DOCKER_CLI_EXPERIMENTAL=enabled

# Join a list of args with a single char.
# Ref: https://stackoverflow.com/a/17841619
join() { local IFS="$1"; shift; echo "$*"; }

set -ex

echo ">>> Starting local Docker registry when needed..."

# Docker Buildx's `docker-container` driver is needed for multi-platform
# builds, but it can't access existing images on the Docker host (like the
# cross-compiled ones we just built). Those images first need to be pushed to
# a registry -- Docker Hub could be used, but since it's not trivial to clean
# up those intermediate images on Docker Hub, it's easier to just run a local
# Docker registry, which gets cleaned up automatically once the build job ends.
#
# https://docs.docker.com/registry/deploying/
# https://hub.docker.com/_/registry
#
# Use host networking so the buildx container can access the registry via
# localhost.
#
# First check if there already is a registry container running, else skip it.
# This will only happen either locally or running it via Github Actions
#
if ! timeout 5 bash -c 'cat < /dev/null > /dev/tcp/localhost/5000'; then
    # defaults to port 5000
    docker run -d --name registry --network host registry:2
fi

# Docker Hub sets a `DOCKER_REPO` env var with the format `index.docker.io/user/repo`.
# Strip the registry portion to construct a local repo path for use in `Dockerfile.buildx`.
LOCAL_REGISTRY="localhost:5000"
REPO="${DOCKER_REPO#*/}"
LOCAL_REPO="${LOCAL_REGISTRY}/${REPO}"

echo ">>> Pushing images to local registry..."

for arch in ${arches[@]}; do
    docker_image="${DOCKER_REPO}:${DOCKER_TAG}-${arch}"
    local_image="${LOCAL_REPO}:${DOCKER_TAG}-${arch}"
    docker tag "${docker_image}" "${local_image}"
    docker push "${local_image}"
done

echo ">>> Setting up Docker Buildx..."

# Same as earlier, use host networking so the buildx container can access the
# registry via localhost.
#
# Ref: https://github.com/docker/buildx/issues/94#issuecomment-534367714
#
# Check if there already is a builder running, else skip this and use the existing.
# This will only happen either locally or running it via Github Actions
#
if ! docker buildx inspect builder > /dev/null 2>&1 ; then
    docker buildx create --name builder --use --driver-opt network=host
fi

echo ">>> Running Docker Buildx..."

tags=("${DOCKER_REPO}:${DOCKER_TAG}")

# If the Docker tag starts with a version number, assume the latest release
# is being pushed. Add an extra tag (`latest` or `alpine`, as appropriate)
# to make it easier for users to track the latest release.
if [[ "${DOCKER_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
    if [[ "${DOCKER_TAG}" == *alpine ]]; then
        tags+=(${DOCKER_REPO}:alpine)
    else
        tags+=(${DOCKER_REPO}:latest)
    fi
fi

tag_args=()
for tag in "${tags[@]}"; do
    tag_args+=(--tag "${tag}")
done

# Docker Buildx takes a list of target platforms (OS/arch/variant), so map
# the arch list to a platform list (assuming the OS is always `linux`).
declare -A arch_to_platform=(
    [amd64]="linux/amd64"
    [armv6]="linux/arm/v6"
    [armv7]="linux/arm/v7"
    [arm64]="linux/arm64"
)
platforms=()
for arch in ${arches[@]}; do
    platforms+=("${arch_to_platform[$arch]}")
done
platforms="$(join "," "${platforms[@]}")"

# Run the build, pushing the resulting images and multi-arch manifest list to
# Docker Hub. The Dockerfile is read from stdin to avoid sending any build
# context, which isn't needed here since the actual cross-compiled images
# have already been built.
docker buildx build \
       --network host \
       --build-arg LOCAL_REPO="${LOCAL_REPO}" \
       --build-arg DOCKER_TAG="${DOCKER_TAG}" \
       --platform "${platforms}" \
       "${tag_args[@]}" \
       --push \
       - < ./docker/Dockerfile.buildx

# Add an extra arch-specific tag for `arm32v6`; Docker can't seem to properly
# auto-select that image on ARMv6 platforms like Raspberry Pi 1 and Zero
# (https://github.com/moby/moby/issues/41017).
#
# Note that we use `arm32v6` instead of `armv6` to be consistent with the
# existing vaultwarden tags, which adhere to the naming conventions of the
# Docker per-architecture repos (e.g., https://hub.docker.com/u/arm32v6).
# Unfortunately, these per-arch repo names aren't always consistent with the
# corresponding platform (OS/arch/variant) IDs, particularly in the case of
# 32-bit ARM arches (e.g., `linux/arm/v6` is used, not `linux/arm32/v6`).
#
# TODO: It looks like this issue should be fixed starting in Docker 20.10.0,
# so this step can be removed once fixed versions are in wider distribution.
#
# Tags:
#
#   testing        => testing-arm32v6
#   testing-alpine => <ignored>
#   x.y.z          => x.y.z-arm32v6, latest-arm32v6
#   x.y.z-alpine   => <ignored>
#
if [[ "${DOCKER_TAG}" != *alpine ]]; then
    image="${DOCKER_REPO}":"${DOCKER_TAG}"

    # Fetch the multi-arch manifest list and find the digest of the armv6 image.
    filter='.manifests|.[]|select(.platform.architecture=="arm" and .platform.variant=="v6")|.digest'
    digest="$(docker manifest inspect "${image}" | jq -r "${filter}")"

    # Pull the armv6 image by digest, retag it, and repush it.
    docker pull "${DOCKER_REPO}"@"${digest}"
    docker tag "${DOCKER_REPO}"@"${digest}" "${image}"-arm32v6
    docker push "${image}"-arm32v6

    if [[ "${DOCKER_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
        docker tag "${image}"-arm32v6 "${DOCKER_REPO}:latest"-arm32v6
        docker push "${DOCKER_REPO}:latest"-arm32v6
    fi
fi