#!/usr/bin/env bash

# This script builds a Docker image of Coder containing the given binary, for
# the given architecture. Only linux binaries are supported at this time.
#
# Usage: ./build_docker.sh --arch amd64 [--version 1.2.3] [--target image_tag] [--build-base image_tag] [--push] path/to/coder
#
# The --arch parameter is required and accepts a Golang arch specification. It
# will be automatically mapped to a suitable architecture that Docker accepts
# before being passed to `docker buildx build`.
#
# The --build-base parameter is optional and specifies to build the base image
# in Dockerfile.base instead of pulling a copy from the registry. The string
# value is the tag to use for the built image (not pushed). This also consumes
# $CODER_IMAGE_BUILD_BASE_TAG for easily forcing a fresh build in CI.
#
# The default base image can be controlled via $CODER_BASE_IMAGE_TAG.
#
# The image will be built and tagged against the image tag returned by
# ./image_tag.sh unless a --target parameter is supplied.
#
# If no version is specified, defaults to the version from ./version.sh.
#
# If the --push parameter is supplied, the image will be pushed.
#
# Prints the image tag on success.

set -euo pipefail
# shellcheck source=scripts/lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"

DEFAULT_BASE="${CODER_BASE_IMAGE_TAG:-ghcr.io/coder/coder-base:latest}"

arch=""
image_tag=""
build_base="${CODER_IMAGE_BUILD_BASE_TAG:-}"
version=""
push=0

args="$(getopt -o "" -l arch:,target:,build-base:,version:,push -- "$@")"
eval set -- "$args"
while true; do
	case "$1" in
	--arch)
		arch="$2"
		shift 2
		;;
	--target)
		image_tag="$2"
		shift 2
		;;
	--version)
		version="$2"
		shift 2
		;;
	--build-base)
		build_base="$2"
		shift 2
		;;
	--push)
		push=1
		shift
		;;
	--)
		shift
		break
		;;
	*)
		error "Unrecognized option: $1"
		;;
	esac
done

if [[ "$arch" == "" ]]; then
	error "The --arch parameter is required"
fi

# Check dependencies
dependencies docker

# Remove the "v" prefix.
version="${version#v}"
if [[ "$version" == "" ]]; then
	version="$(execrelative ./version.sh)"
fi

if [[ "$image_tag" == "" ]]; then
	image_tag="$(execrelative ./image_tag.sh --arch "$arch" --version="$version")"
fi

if [[ "$#" != 1 ]]; then
	error "Exactly one argument must be provided to this script, $# were supplied"
fi
if [[ ! -f "$1" ]]; then
	error "File '$1' does not exist or is not a regular file"
fi
input_file="$(realpath "$1")"

# Remap the arch from Golang to Docker.
declare -A arch_map=(
	[amd64]="linux/amd64"
	[arm64]="linux/arm64"
	[arm]="linux/arm/v7"
	[armv7]="linux/arm/v7"
)
if [[ "${arch_map[$arch]+exists}" != "" ]]; then
	arch="${arch_map[$arch]}"
fi

# Make temporary dir where all source files intended to be in the image will be
# hardlinked from.
cdroot
temp_dir="$(TMPDIR="$(dirname "$input_file")" mktemp -d)"
ln "$input_file" "$temp_dir/coder"
ln ./scripts/Dockerfile.base "$temp_dir/"
ln ./scripts/Dockerfile "$temp_dir/"

cd "$temp_dir"

export DOCKER_BUILDKIT=1

base_image="$DEFAULT_BASE"
if [[ "$build_base" != "" ]]; then
	log "--- Building base Docker image for $arch ($build_base)"
	docker build \
		--platform "$arch" \
		--tag "$build_base" \
		--no-cache \
		-f Dockerfile.base \
		. 1>&2

	base_image="$build_base"
else
	docker pull --platform "$arch" "$base_image" 1>&2
fi

log "--- Building Docker image for $arch ($image_tag)"

docker build \
	--platform "$arch" \
	--build-arg "BASE_IMAGE=$base_image" \
	--build-arg "CODER_VERSION=$version" \
	--no-cache \
	--tag "$image_tag" \
	-f Dockerfile \
	. 1>&2

cdroot
rm -rf "$temp_dir"

if [[ "$push" == 1 ]]; then
	log "--- Pushing Docker image for $arch ($image_tag)"
	docker push "$image_tag" 1>&2
fi

echo "$image_tag"
