#!/usr/bin/env bash

set -eEo pipefail

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"

# shellcheck source=ci/docker_commands
source "${SCRIPTPATH}/docker_commands"

declare -A checksums

DOCKER_MACHINE_VERSION=${DOCKER_MACHINE_VERSION:-0.16.2}
checksums['DOCKER_MACHINE_AMD64']=${DOCKER_MACHINE_LINUX_AMD64_CHECKSUM:-a7f7cbb842752b12123c5a5447d8039bf8dccf62ec2328853583e68eb4ffb097}
checksums['DOCKER_MACHINE_ARM64']=${DOCKER_MACHINE_LINUX_ARM64_CHECKSUM:-109f534bfb8b9b852c938cad978e60a86b13f5ecf92da5e24320dacd2a7216ac}
checksums['DOCKER_MACHINE_S390X']="" # No binary available yet for s390x, see https://gitlab.com/gitlab-org/gitlab-runner/-/issues/26551
checksums['DOCKER_MACHINE_PPC64LE']="" # No binary available
DUMB_INIT_VERSION=${DUMB_INIT_VERSION:-1.2.2}
checksums['DUMB_INIT_AMD64']=${DUMB_INIT_LINUX_AMD64_CHECKSUM:-37f2c1f0372a45554f1b89924fbb134fc24c3756efaedf11e07f599494e0eff9}
checksums['DUMB_INIT_ARM64']=${DUMB_INIT_LINUX_ARM64_CHECKSUM:-45b1bbf56cc03edda81e4220535a025bfe3ed6e93562222b9be4471005b3eeb3}
checksums['DUMB_INIT_S390X']=${DUMB_INIT_LINUX_S390X_CHECKSUM:-8b3808c3c06d008b8f2eeb2789c7c99e0450b678d94fb50fd446b8f6a22e3a9d}
checksums['DUMB_INIT_PPC64LE']=${DUMB_INIT_LINUX_PPC64LE_CHECKSUM:-88b02a3bd014e4c30d8d54389597adc4f5a36d1d6b49200b5a4f6a71026c2246}
GIT_LFS_VERSION=${GIT_LFS_VERSION:-2.11.0}
checksums['GIT_LFS_AMD64']=${GIT_LFS_LINUX_AMD64_CHECKSUM:-46508eb932c2ec0003a940f179246708d4ddc2fec439dcacbf20ff9e98b957c9}
checksums['GIT_LFS_ARM64']=${GIT_LFS_LINUX_ARM64_CHECKSUM:-ba6a2820d6afcdf94a83c9307bfbabcc2f8146b27404b450c673567798a81f67}
checksums['GIT_LFS_S390X']=${GIT_LFS_LINUX_S390X_CHECKSUM:-ca73776cb1cdc855aaf743c09ae70caae97f67d8d5e4147f19dcc4f959f9fc4d}
checksums['GIT_LFS_PPC64LE']=${GIT_LFS_LINUX_PPC64LE_CHECKSUM:-76196d06a79eec11c202d9cbafbab98f52b9a7fda8538c2d94748461ba192209}

IMAGE_FLAVOR=${IMAGE_FLAVOR:-'ubuntu'}

image_platform=""
if [[ "${IMAGE_FLAVOR}" == ubuntu* ]]; then
  image_platform="ubuntu"
elif [[ "${IMAGE_FLAVOR}" == ubi-fips* ]]; then
  image_platform="ubi-fips"
else
  image_platform="alpine"
fi

declare -A base_images_from_flavor
base_images_from_flavor=(
  ["ubuntu"]="ubuntu:20.04"
  ["alpine3.12"]="alpine:3.12.0"
  ["alpine3.13"]="alpine:3.13.6"
  ["alpine3.14"]="alpine:3.14.2"
  ["alpine3.15"]="alpine:3.15.0"
  ["ubi-fips"]="redhat/ubi8:8.5-214"
)

get_image_version_from_flavor() {
  local flavor="$1"
  echo "${base_images_from_flavor[${flavor}]}"
}

declare -A flavor_aliases
flavor_aliases=( ["alpine3.12"]="alpine,alpine3.12" )

get_flavor_aliases() {
  local flavor="$1"
  if [[ "${flavor_aliases[$flavor]}" != "" ]]; then
      echo "${flavor_aliases[$flavor]}"
  else
      echo "$flavor"
  fi
}

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

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

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

    shift
    shift

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

    # 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
    if [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]] && [[ -n "${ECR_PUBLIC_USER}" ]] && [[ -n "${ECR_PUBLIC_PASSWORD}" ]]; then
        login "${ECR_PUBLIC_USER}" "${ECR_PUBLIC_PASSWORD}" "${ECR_PUBLIC_REGISTRY}"
    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}" \
        --build-arg BASE_IMAGE="${base_image}" \
        --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
    if [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]] && [[ -n "${ECR_PUBLIC_USER}" ]] && [[ -n "${ECR_PUBLIC_PASSWORD}" ]]; then
        logout "${ECR_PUBLIC_REGISTRY}"
    fi
}

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

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

    for base_image in ${base_images//,/ }; do
      add_to_tags+=("${repository}:${base_image}-${ref_tag}")
      if [[ "${base_image}" == "${default_image}" ]]; then
          add_to_tags+=("${repository}:${ref_tag}")
      fi
      if [[ -n "${IS_LATEST}" ]]; then
          add_to_tags+=("${repository}:${base_image}")
          if [[ "${base_image}" == "${default_image}" ]]; then
              add_to_tags+=("${repository}:latest")
          fi
      fi
    done
}

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

runner_home_dir="dockerfiles/runner"

function writeChecksum() {
  local binVarNamePrefix="$1"
  local targetArch="$2"
  local binFile="$3"
  local archVarNameSuffix=$(echo "${targetArch}" | tr '[:lower:]' '[:upper:]')
  local checksum="${checksums["${binVarNamePrefix}_${archVarNameSuffix}"]}"

  [[ -n "${checksum}" ]] && echo "${checksum}  ${binFile}" >> "${runner_home_dir}/checksums-${targetArch}" || return 0
}

for arch in "${TARGET_ARCHS[@]}"; do
    echo "${arch}:"
    rm -f "${runner_home_dir}/checksums-${arch}"
    writeChecksum 'DOCKER_MACHINE' "${arch}" '/usr/bin/docker-machine'
    writeChecksum 'DUMB_INIT' "${arch}" '/usr/bin/dumb-init'
    writeChecksum 'GIT_LFS' "${arch}" '/tmp/git-lfs.tar.gz'
done

cp "${runner_home_dir}/install-deps" "${runner_home_dir}/ubuntu/"
cp "${runner_home_dir}/install-deps" "${runner_home_dir}/alpine/"
cp "${runner_home_dir}/install-deps" "${runner_home_dir}/ubi-fips/"
for arch in "${TARGET_ARCHS[@]}"; do
    deb_arch=$(if [ "${arch}" == "ppc64le" ]; then echo "ppc64el"; else echo "${arch}"; fi)
    cp "${runner_home_dir}/checksums-${arch}" "out/deb/gitlab-runner_${deb_arch}.deb" \
        "${runner_home_dir}/ubuntu/"
    cp "${runner_home_dir}/checksums-${arch}" "out/binaries/gitlab-runner-linux-${arch}" \
        "${runner_home_dir}/alpine/"
    if [[ $arch == "amd64" ]]; then
        cp "${runner_home_dir}/checksums-${arch}" \
           "out/binaries/gitlab-runner-linux-${arch}-fips" \
           "out/rpm/gitlab-runner_${arch}-fips.rpm" \
           "${runner_home_dir}/ubi-fips/"
    fi
done

tags=()
aliases="$(get_flavor_aliases "$IMAGE_FLAVOR")"
if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ "${PUSH_TO_DOCKER_HUB}" == "true" ]]; then
   add_tags tags "${aliases}" "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}" "${DOCKER_HUB_NAMESPACE}/gitlab-runner" "${ref_tag}"
fi

if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ -n "${CI_REGISTRY}" ]] && [[ -n "${CI_REGISTRY_IMAGE}" ]]; then
   add_tags tags "${aliases}" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" "${CI_REGISTRY_IMAGE}" "${ref_tag}"
fi

if [[ "${PUBLISH_IMAGES}" == "true" ]] && [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]]; then
   add_tags tags "${aliases}" "${ECR_PUBLIC_USER}" "${ECR_PUBLIC_PASSWORD}" "${ECR_PUBLIC_REGISTRY}/gitlab-runner" "${ref_tag}"
fi

# Build and publish multi-platform images using `docker buildx`
# shellcheck disable=SC2154
base_image="$(get_image_version_from_flavor "${IMAGE_FLAVOR}")"

# Workaround for linux/s390x building problem when Go>=1.5 is used
buildx_call_failed="false"
ALLOW_IMAGE_BUILD_FAILURE=${ALLOW_IMAGE_BUILD_FAILURE:-"false"}

buildx "${runner_home_dir}/${image_platform}" "$base_image" "${tags[@]}" || buildx_call_failed="true"

if [[ "${buildx_call_failed}" == "true" ]]; then
  echo -e "\033[2mImage build with buildx have failed!\033[0m"
  if [[ "${ALLOW_IMAGE_BUILD_FAILURE}" == "true" ]]; then
    echo "Ignoring the failure as ALLOW_IMAGE_BUILD_FAILURE is set to true"
    exit 0
  else
    exit 1
  fi
fi
