#!/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:-v0.16.2-gitlab.19}
checksums['DOCKER_MACHINE_AMD64']=${DOCKER_MACHINE_LINUX_AMD64_CHECKSUM:-0c92b4acead3fbca5e27cae1df9f3b2b7ca0874faa7689d4c3fb3988cae11929}
checksums['DOCKER_MACHINE_ARM64']=${DOCKER_MACHINE_LINUX_ARM64_CHECKSUM:-570856722b9bd0f69317e8cbfc906d04b40658857ed65a152ab23abb3e9bcaea}
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:-3.2.0}
checksums['GIT_LFS_AMD64']=${GIT_LFS_LINUX_AMD64_CHECKSUM:-d6730b8036d9d99f872752489a331995930fec17b61c87c7af1945c65a482a50}
checksums['GIT_LFS_ARM64']=${GIT_LFS_LINUX_ARM64_CHECKSUM:-8186f0c0f69c30b55863d698e0a20cf79447a81df006b88221c2033d1e893638}
checksums['GIT_LFS_S390X']=${GIT_LFS_LINUX_S390X_CHECKSUM:-16556f0b2e1097a69e75a6e1bcabfa7bfd2e7ee9b02fe6e5414e1038a223ab97}
checksums['GIT_LFS_PPC64LE']=${GIT_LFS_LINUX_PPC64LE_CHECKSUM:-ff1eeaddde5d964d10ce607f039154fe033073f43b8ff5e7f4eb407293fe1be3}

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:${UBUNTU_VERSION}"
  ["alpine3.12"]="alpine:${ALPINE_312_VERSION}"
  ["alpine3.13"]="alpine:${ALPINE_313_VERSION}"
  ["alpine3.14"]="alpine:${ALPINE_314_VERSION}"
  ["alpine3.15"]="alpine:${ALPINE_315_VERSION}"
  ["alpine-latest"]="alpine:latest"
  ["ubi-fips"]="redhat/ubi8:${UBI_FIPS_VERSION}"
)

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

# This marks the alpine3.15 to be aliased to alpine,
# which makes 3.15 the default alpine version for users
declare -A flavor_aliases
flavor_aliases=( ["alpine3.15"]="alpine,alpine3.15" )

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

if [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]] && [[ -n "${ECR_PUBLIC_USER}" ]]; then
  ECR_PUBLIC_REGISTRY_PASSWORD=$(aws --region us-east-1 ecr-public get-login-password)
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_REGISTRY_PASSWORD}" ]]; then
        ECR_PUBLIC_REGISTRY_PASSWORD=$(aws --region us-east-1 ecr-public get-login-password)
        login "${ECR_PUBLIC_USER}" "${ECR_PUBLIC_REGISTRY_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}"
    buildx_exit_code=$?

    trap - ERR SIGINT SIGTERM
    cleanup_docker_context

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

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

    return $buildx_exit_code
}

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" ]] && [[ -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_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" ]] && [[ "${PUSH_TO_ECR_PUBLIC}" == "true" ]]; then
   add_tags tags "${aliases}" "${ECR_PUBLIC_USER}" "${ECR_PUBLIC_REGISTRY_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
ALLOW_IMAGE_BUILD_FAILURE=${ALLOW_IMAGE_BUILD_FAILURE:-"false"}
DOCKER_BUILDX_RETRY=${DOCKER_BUILDX_RETRY:-5}

for i in $(seq "${DOCKER_BUILDX_RETRY}")
do
  echo "Running buildx build try #$i out of $DOCKER_BUILDX_RETRY"
  buildx_exit_code=0
  buildx "${runner_home_dir}/${image_platform}" "$base_image" "${tags[@]}" || buildx_exit_code=$?

  # Buildx failed
  if [[ "${buildx_exit_code}" -ne 0 ]]; then
    echo -e "\033[2mImage build with buildx have failed!\033[0m"
    # If it's allowed to fail, disregard DOCKER_BUILDX_RETRY
    if [[ "${ALLOW_IMAGE_BUILD_FAILURE}" == "true" ]]; then
      echo "Ignoring the failure as ALLOW_IMAGE_BUILD_FAILURE is set to true"
      exit 0
    fi

    # If it's not allowed to fail and we've reached the maximum amount of retries, fail
    if [ "${i}" -eq "${DOCKER_BUILDX_RETRY}" ]; then
      exit $buildx_exit_code
    fi
  else
    # Buildx succeeded
    exit $buildx_exit_code
  fi
done
