#!/bin/sh

# If needed, build Dockerfiles specified by $2..n and upload it to the project
# registry. (This is used by CI jobs, not a Charliecloud test suite image.)
#
# Because of this bootstrap context, we need to run *this* job using an image
# available somewhere else. It also needs to have some image builder
# installed. GitLab recommends images provided by Docker that are very bare
# bones (Alpine with no programming language other than Busybox) [1], which is
# why this is a POSIX shell script.
#
# An alternative would be Podman images, which are Fedora based and do have a
# recent Python 3 [2].
#
# The gotcha here is that the local image builder does not know if the correct
# image is already in the registry, and it doesn’t even know the layer digests
# without building the layers, which we want to avoid. Therefore, we tag the
# images with the MD5 of the Dockerfile plus the base image digest, and build
# a new image if either is not in the registry.
#
# PREREQUISITES:
#
#   1. Log in to the gitlab.com container registry. For testing, do a manual
#      “docker login” with your username and a personal access token
#      (PAT) [3]. While your username/password will work, don’t do that
#      because Docker stores it in a plaintext file.
#
#   2. Set $CI_REGISTRY_IMAGE [4] if the default below isn’t appropriate.
#
# [1]: https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker
# [2]: https://quay.io/repository/podman/stable
# [3]: https://docs.gitlab.com/ee/user/packages/container_registry/authenticate_with_container_registry.html
# [4]: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

set -e  # needs to be POSIX so can’t use our fancy traps

branch=$1
shift
echo "branch:        $branch"

# Convenience function for computing MD5 of a file.
md5 () {
    md5sum "$1" | cut -d' ' -f1
}

# Print the digest of the named image.
#shellcheck disable=SC2016
refhash () {
    case $1 in
    scratch)     # scratch is special
        printf '0%.0s' $(seq 1 64)
        echo
        ;;
    *':$branch')  # local file
        distro=$(echo "$1" | sed -E 's/^(\$\{.+\})?ci_(.+):\$branch$/\2/g')
        (cd "$(dirname "$0")" && md5 "$distro.df")
        ;;
    *)           # ask the registry
        # sed(1) drama is because we don’t have # any JSON parsing available.
          docker manifest inspect "$df_base" \
        | tr -d '"[:space:]' \
        | sed -E "s/^(.+)(digest:sha256:([0-9a-f]+),platform:\{architecture:${arch},)(.+)$/\3/"
        ;;
    esac
}

# Set up docker(1) alias if needed.
printf 'docker alias:  '
if docker info > /dev/null 2>&1; then
    echo 'not needed'
else
    alias docker='sudo docker'
    alias docker
fi

# Function to pass through proxy variables if needed.
build () {
    if [ -z "$HTTP_PROXY" ]; then
        docker build "$@"
    else
        docker build --build-arg HTTP_PROXY="${HTTP_PROXY:?}" \
                     --build-arg HTTPS_PROXY="${HTTPS_PROXY:?}" \
                     --build-arg NO_PROXY="${NO_PROXY:?}" \
                     --build-arg http_proxy="${http_proxy:?}" \
                     --build-arg https_proxy="${https_proxy:?}" \
                     --build-arg no_proxy="${no_proxy:?}" \
                     "$@"
    fi
}

# Registry path.
if [ -n "$CI_REGISTRY_IMAGE" ]; then
    regy=$CI_REGISTRY_IMAGE
else
    regy=registry.gitlab.com/charliecloud/main
fi
echo "registry:      $regy"

# Architecture. Don’t we love how uname(1), Docker, and nVidia all disagree on
# arch names?
#
# Also, nVidia versions. These must be manually curated AFAICT to match what’s
# on the GitLab.com CI runners. The packages we need are not in the Debian 12
# repo but these seem to work.
case $(uname -m) in
    x86_64)
        arch=amd64
        #nv_distro=ubuntu2204/x86_64
        #nv_version=535.161.07
        ;;
    aarch64)
        arch=arm64
        #nv_distro=ubuntu2204/sbsa
        #nv_version=535
        ;;
    *)
        echo "error: unknown architecture: $(uname -m)" 1>&2
        exit 1
        ;;
esac
echo "architecture:  $arch"
#echo "nv distro:     $nv_distro"

for df in "$@"; do

    echo
    echo "file:          $df"
    df_hash=$(md5 "$df")
    echo "file digest:   $df_hash"
    df_base=$(sed -En 's/^FROM (.+)/\1/p' "$df")
    echo "base:          $df_base"
    printf "base digest:   "
    base_hash=$(refhash "$df_base")
    echo "$base_hash"
    digest=$(printf '%s%s' "$df_hash" "$base_hash" | md5 -)
    echo "full digest:   $digest"
    name=$(basename "$df" | sed -E 's/^(.+)\.df$/ci_\1/g')
    echo "image name:    $name"

    # Check if image exists in our registry; if not, build and push it.
    ref_digest=$regy/$name:$digest
    ref_branch=$regy/$name:$branch
    ref_latest=$regy/$name:latest
    printf 'in registry:   '
    if out=$(docker manifest inspect "$ref_digest" 2>&1); then
        echo 'yes, skipping build'
    else
        if ! ( echo "$out" | grep -Eq '^no such manifest:' ); then
            echo "bad response:" "$out" 1>&2
            exit 1
        fi
        echo 'no, building and pushing ...'
        build -t "$ref_digest" -f "$df" \
              --build-arg=regy="$regy/" \
              --build-arg=branch="$branch" \
               .
        docker tag "$ref_digest" "$name:$branch"  # for local use
        docker push "$ref_digest"
    fi

    # Re-tag in registry. Do this unconditionally because it’s fast and then we
    # don’t have to worry about whether the previous iteration of the script
    # worked correctly. Use Docker [1] rather than Skopeo because it’s already
    # installed.
    #
    # This does not completely eliminate the race condition in #1981, but now only
    # commits within the same branch will be racing, which seems acceptable.
    #
    # [1]: https://stackoverflow.com/questions/26763427#70526615
    echo 'tagging ...'
    docker buildx imagetools create "$ref_digest" --tag "$ref_branch"
    docker buildx imagetools create "$ref_digest" --tag "$ref_latest"

done