coder/scripts/build_docker.sh

137 lines
3.3 KiB
Bash
Executable File

#!/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] [--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 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"
arch=""
image_tag=""
version=""
push=0
args="$(getopt -o "" -l arch:,target:,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
;;
--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 Dockerfile "$temp_dir/"
cd "$temp_dir"
log "--- Building Docker image for $arch ($image_tag)"
# Pull the base image, copy the /etc/group and /etc/passwd files out of it, and
# add the coder group and user. We have to do this in a separate step instead of
# using the RUN directive in the Dockerfile because you can't use RUN if you're
# building the image for a different architecture than the host.
docker pull --platform "$arch" alpine:latest 1>&2
temp_container_id="$(docker create --platform "$arch" alpine:latest)"
docker cp "$temp_container_id":/etc/group ./group 1>&2
docker cp "$temp_container_id":/etc/passwd ./passwd 1>&2
docker rm "$temp_container_id" 1>&2
echo "coder:x:1000:coder" >>./group
echo "coder:x:1000:1000::/:/bin/sh" >>./passwd
mkdir ./empty-dir
docker buildx build \
--platform "$arch" \
--build-arg "CODER_VERSION=$version" \
--tag "$image_tag" \
. 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"