#!/usr/bin/env bash

set -eEo pipefail

declare -A checksums

DOCKER_MACHINE_VERSION=${DOCKER_MACHINE_VERSION:-0.16.2}
checksums['DOCKER_MACHINE_AMD64']=${DOCKER_MACHINE_AMD64_CHECKSUM:-a7f7cbb842752b12123c5a5447d8039bf8dccf62ec2328853583e68eb4ffb097}
checksums['DOCKER_MACHINE_ARM64']=${DOCKER_MACHINE_ARM64_CHECKSUM:-109f534bfb8b9b852c938cad978e60a86b13f5ecf92da5e24320dacd2a7216ac}
DUMB_INIT_VERSION=${DUMB_INIT_VERSION:-1.2.2}
checksums['DUMB_INIT_AMD64']=${DUMB_INIT_AMD64_CHECKSUM:-37f2c1f0372a45554f1b89924fbb134fc24c3756efaedf11e07f599494e0eff9}
checksums['DUMB_INIT_ARM64']=${DUMB_INIT_ARM64_CHECKSUM:-45b1bbf56cc03edda81e4220535a025bfe3ed6e93562222b9be4471005b3eeb3}
GIT_LFS_VERSION=${GIT_LFS_VERSION:-2.7.1}
checksums['GIT_LFS_AMD64']=${GIT_LFS_AMD64_CHECKSUM:-c8952ee72af214e3669f834d829e8a0a3becd160dead18237f99e40d75a3e920}
checksums['GIT_LFS_ARM64']=${GIT_LFS_ARM64_CHECKSUM:-7a80464be13acc23ac99fd33e4b4352894f0fe3a8794b270e964c9532941834d}

if [ -n "${TARGET_ARCHS}" ]; then
    IFS=', ' read -r -a TARGET_ARCHS <<< "${TARGET_ARCHS}"
else
    TARGET_ARCHS=('amd64')
fi

CI_COMMIT_TAG=${CI_COMMIT_TAG:-}
CI_REGISTRY=${CI_REGISTRY:-}
CI_REGISTRY_IMAGE=${CI_REGISTRY_IMAGE:-}
CI_REGISTRY_USER=${CI_REGISTRY_USER:-}
CI_REGISTRY_PASSWORD=${CI_REGISTRY_PASSWORD:-}
DOCKER_HOST=${DOCKER_HOST:-}
DOCKER_CERT_PATH=${DOCKER_CERT_PATH:-}
DOCKER_HUB_REPOSITORY=${DOCKER_HUB_REPOSITORY:-'gitlab/gitlab-runner'}
DOCKER_HUB_USER=${DOCKER_HUB_USER:-}
DOCKER_HUB_PASSWORD=${DOCKER_HUB_PASSWORD:-}
IS_LATEST=${IS_LATEST:-}
PUBLISH_IMAGES=${PUBLISH_IMAGES:-false}
PUSH_TO_DOCKER_HUB=${PUSH_TO_DOCKER_HUB:-false}

docker_builder_name='buildx-builder'
docker_buildx_ctx_name='docker-buildx'

ref_tag="${CI_COMMIT_TAG}"
if [[ -z "${ref_tag}" ]]; then
    ref_tag=${CI_COMMIT_REF_SLUG:-master}
fi

if [[ "${ref_tag}" == "master" ]]; then
    ref_tag=bleeding
fi

REVISION=${REVISION:-}
if [[ -z "${REVISION}" ]]; then
    REVISION=$(git rev-parse --short=8 HEAD || echo "unknown")
fi

_docker() {
    docker "${@}"
}

_docker_experimental() {
    DOCKER_CLI_EXPERIMENTAL=enabled docker "${@}"
}

_docker_buildx() {
    # Run the command in a subshell so that we can safely unset DOCKER_HOST
    (
        unset DOCKER_HOST
        _docker_experimental buildx "${@}"
    )
}

join_by() {
    local IFS="$1"
    shift
    echo "$*"
}

setup_docker_context() {
    # In order for `docker buildx create` to work, we need to replace DOCKER_HOST with a Docker context.
    # Otherwise, we get the following error:
    # > could not create a builder instance with TLS data loaded from environment.
    local docker="host=unix:///var/run/docker.sock"
    if [ -n "${DOCKER_CERT_PATH}" ]; then
        docker="host=${DOCKER_HOST},ca=${DOCKER_CERT_PATH}/ca.pem,cert=${DOCKER_CERT_PATH}/cert.pem,key=${DOCKER_CERT_PATH}/key.pem"
    fi
    _docker context create "${docker_buildx_ctx_name}" \
        --default-stack-orchestrator=swarm \
        --description "Temporary buildx Docker context" \
        --docker "${docker}"

    _docker_buildx create --use --name "${docker_builder_name}" "${docker_buildx_ctx_name}"
}

cleanup_docker_context_trap() {
    local error_code=$?

    cleanup_docker_context

    exit "${error_code}"
}

cleanup_docker_context() {
    set +e
    _docker_buildx rm "${docker_builder_name}" >/dev/null 2>&1
    _docker context rm -f "${docker_buildx_ctx_name}" >/dev/null 2>&1
    set -e
}

# buildx receives an array of tag names, and the context path as the last parameter
buildx() {
    local contextPath="$1"
    local platforms=()
    local os
    os=$(_docker version -f '{{.Server.Os}}')
    for arch in "${TARGET_ARCHS[@]}"; do
        platforms+=("${os}/${arch}")
    done
    shift

    local args=("$@")
    local buildxFlags=()

    local experimental
    experimental=$(_docker_experimental info -f '{{json .ExperimentalBuild}}')
    if [ "${experimental}" = 'false' ]; then
        echo "Docker experimental mode needs to be enabled for multi-platform build support. Aborting."
        echo "See https://github.com/docker/cli/blob/master/experimental/README.md#use-docker-experimental for more information."
        exit 1
    fi

    # Build -t tag name options from remaining arguments
    local tagOpts=()
    for tagName in "${args[@]}"; do
        tagOpts+=("--tag" "${tagName}")
    done

    if [[ "${PUBLISH_IMAGES}" == "true" ]]; then
        echo -e "\033[1mBuilding and pushing image: \033[32m${contextPath}\033[0m"
        buildxFlags+=("--push")
    else
        # If not pushing, just load the resulting image to local Docker
        if [ ${#TARGET_ARCHS[@]} -eq 1 ]; then
            echo -e "\033[1mBuilding and loading image: \033[32m${contextPath}\033[0m"
            # But that is only possible if we are targeting a single platform
            buildxFlags+=("--load")
        else
            echo -e "\033[1mBuilding image: \033[32m${contextPath}\033[0m"
        fi
    fi

    trap cleanup_docker_context_trap ERR SIGINT SIGTERM
    setup_docker_context

    if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ -n "${CI_REGISTRY_USER}" ]] && [[ -n "${CI_REGISTRY_PASSWORD}" ]]; then
        login "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY}"
    fi
    if [[ "${PUSH_TO_DOCKER_HUB}" == "true" ]] && [[ -n "${DOCKER_HUB_USER}" ]] && [[ -n "${DOCKER_HUB_PASSWORD}" ]]; then
        login "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}"
    fi

    local formatted_platforms
    formatted_platforms=$(join_by , "${platforms[@]}")
    _docker_buildx build \
        --build-arg DOCKER_MACHINE_VERSION="${DOCKER_MACHINE_VERSION}" \
        --build-arg DUMB_INIT_VERSION="${DUMB_INIT_VERSION}" \
        --build-arg GIT_LFS_VERSION="${GIT_LFS_VERSION}" \
        --platform "${formatted_platforms}" \
        --no-cache \
        "${tagOpts[@]}" \
        "${buildxFlags[@]}" \
        "${contextPath}"
    trap - ERR SIGINT SIGTERM
    cleanup_docker_context

    if [[ -z "${PUBLISH_IMAGES}" ]] || [[ "${PUBLISH_IMAGES}" != "true" ]]; then
        echo "Skipping images pushing"
    fi

    if [[ "${PUSH_TO_DOCKER_HUB}" == "true" ]] && [[ -n "${DOCKER_HUB_USER}" ]] && [[ -n "${DOCKER_HUB_PASSWORD}" ]]; then
        logout
    fi
    if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ -n "${CI_REGISTRY_USER}" ]] && [[ -n "${CI_REGISTRY_PASSWORD}" ]]; then
        logout "${CI_REGISTRY}"
    fi
}

import() {
    echo -e "\033[1mImporting image: \033[32m${2}\033[0m"
    _docker import "${1}" "${2}"
}

tag() {
    echo -e "\033[1mTagging image: \033[32m${2}\033[0m"
    _docker tag "${1}" "${2}"
}

tag_latest() {
    if [[ -z "${IS_LATEST}" ]]; then
        return
    fi

    tag "${@}"
}

push() {
    echo -e "\033[1mPushing image: \033[32m${1}\033[0m"
    _docker push "${1}"
}

push_latest() {
    if [[ -z "${IS_LATEST}" ]]; then
        return
    fi

    push "${@}"
}

release_docker_helper_images() {
    helper_image_x86_64="gitlab/gitlab-runner-helper:x86_64-${REVISION}"
    helper_image_x86_64_latest="gitlab/gitlab-runner-helper:x86_64-latest"
    helper_image_arm="gitlab/gitlab-runner-helper:arm-${REVISION}"
    helper_image_arm_latest="gitlab/gitlab-runner-helper:arm-latest"
    helper_image_arm64="gitlab/gitlab-runner-helper:arm64-${REVISION}"
    helper_image_arm64_latest="gitlab/gitlab-runner-helper:arm64-latest"

    import out/helper-images/prebuilt-x86_64.tar.xz "${helper_image_x86_64}"
    import out/helper-images/prebuilt-arm.tar.xz "${helper_image_arm}"
    import out/helper-images/prebuilt-arm64.tar.xz "${helper_image_arm64}"

    tag_latest "${helper_image_x86_64}" "${helper_image_x86_64_latest}"
    tag_latest "${helper_image_arm}" "${helper_image_arm_latest}"
    tag_latest "${helper_image_arm64}" "${helper_image_arm64_latest}"

    push "${helper_image_x86_64}"
    push "${helper_image_arm}"
    push "${helper_image_arm64}"

    push_latest "${helper_image_x86_64_latest}"
    push_latest "${helper_image_arm_latest}"
    push_latest "${helper_image_arm64_latest}"
}

login() {
    echo "${2}" | _docker login --username "${1}" --password-stdin "${3}"
}

logout() {
    _docker logout "${1}"
}

add_tags() {
    local -n tags=$1
    local base_image="$2"
    local user="$3"
    local password="$4"
    local repository="$5"
    local default_image='ubuntu'

    if [[ -z "${user}" ]] || [[ -z "${password}" ]]; then
        return
    fi

    tags+=("${repository}:${base_image}-${ref_tag}")
    if [[ "${base_image}" == "${default_image}" ]]; then
        tags+=("${repository}:${ref_tag}")
    fi
    if [[ -n "${IS_LATEST}" ]]; then
        tags+=("${repository}:${base_image}")
        if [[ "${base_image}" == "${default_image}" ]]; then
            tags+=("${repository}:latest")
        fi
    fi
}

[ "${#TARGET_ARCHS[@]}" -eq 0 ] && TARGET_ARCHS=("$(_docker version -f '{{.Server.Arch}}')")

for arch in "${TARGET_ARCHS[@]}"; do
    echo "${arch}:"
    ARCH=$(echo "${arch}" | tr '[:lower:]' '[:upper:]')
    tee "dockerfiles/checksums-${arch}" <<EOF
${checksums["DOCKER_MACHINE_${ARCH}"]}  /usr/bin/docker-machine
${checksums["DUMB_INIT_${ARCH}"]}  /usr/bin/dumb-init
${checksums["GIT_LFS_${ARCH}"]}  /usr/bin/git-lfs
EOF
done

cp dockerfiles/install-deps dockerfiles/ubuntu/
cp dockerfiles/install-deps dockerfiles/alpine/
for arch in "${TARGET_ARCHS[@]}"; do
    cp "dockerfiles/checksums-${arch}" "out/deb/gitlab-runner_${arch}.deb" \
        dockerfiles/ubuntu/
    cp "dockerfiles/checksums-${arch}" "out/binaries/gitlab-runner-linux-${arch}" \
        dockerfiles/alpine/
done

if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ -n "${PUSH_TO_DOCKER_HUB}" ]]; then
   add_tags alpineTags "alpine" "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}" "${DOCKER_HUB_REPOSITORY}" "${ref_tag}"
   add_tags ubuntuTags "ubuntu" "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}" "${DOCKER_HUB_REPOSITORY}" "${ref_tag}"
fi
if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ -n "${CI_REGISTRY}" ]] && [[ -n "${CI_REGISTRY_IMAGE}" ]]; then
   add_tags alpineTags "alpine" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY_IMAGE}" "${ref_tag}"
   add_tags ubuntuTags "ubuntu" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY_IMAGE}" "${ref_tag}"
fi

# Build and publish multi-platform images using `docker buildx`
buildx dockerfiles/ubuntu "${ubuntuTags[@]}"
buildx dockerfiles/alpine "${alpineTags[@]}"

if [[ -z "${PUSH_TO_DOCKER_HUB}" ]] || [[ "${PUSH_TO_DOCKER_HUB}" != "true" ]]; then
    echo "Skipping push to Docker Hub"
    exit 0
fi

if [[ -n "${DOCKER_HUB_USER}" ]] && [[ -n "${DOCKER_HUB_PASSWORD}" ]]; then
    login "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}"

    release_docker_helper_images

    logout
fi
