Skip to content

Commit

Permalink
fix(ci): handle the case where multiple images are defined for a give…
Browse files Browse the repository at this point in the history
…n service (#698)
  • Loading branch information
malept authored Nov 27, 2023
1 parent 5e9c2e8 commit 5698c2b
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 211 deletions.
83 changes: 47 additions & 36 deletions shell/ci/release/docker-stitch.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# Stitch together different manifests to be pulled as one (multi-arch)
# Stitch together different manifests to be pulled as one
# (multi-arch) manifest.

DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
CI_AUTH_DIR="$DIR/../auth"
Expand All @@ -11,6 +12,9 @@ source "${LIB_DIR}/bootstrap.sh"
# shellcheck source=../../lib/box.sh
source "${LIB_DIR}/box.sh"

# shellcheck source=../../lib/docker.sh
source "${LIB_DIR}/docker.sh"

imageRegistry="$(get_box_field 'devenv.imageRegistry')"

# setup docker authentication
Expand All @@ -19,50 +23,57 @@ if [[ $imageRegistry =~ ^gcr.io/ ]]; then
source "$CI_AUTH_DIR/gcr.sh"
fi

IMAGE_NAME="$imageRegistry/$(get_app_name)"
APPNAME="$(get_app_name)"
VERSION="$(make --no-print-directory version)"
MANIFEST="$(get_repo_directory)/deployments/docker.yaml"

archs=(amd64 arm64)
tags=(latest "$VERSION")

for image in /home/circleci/*.tar; do
echo "Loading docker image: $image"
docker load -i "$image"
done
stitch_and_push_image() {
local image="$1"
local remote_image_name
remote_image_name="$(determine_remote_image_name "$APPNAME" "$imageRegistry" "$image")"

if [[ -z $CIRCLE_TAG ]]; then
echo "Skipping manifest creation, not pushing images ..."
exit
fi

for tag in "${tags[@]}"; do
suffixedTags=()
for arch in "${archs[@]}"; do
suffixedTags+=("$tag-$arch")
for img_filename in /home/circleci/"$image"-*.tar; do
echo "Loading docker image: $img_filename"
run_docker load -i "$img_filename"
done

amendedArgs=()
for suffixedTag in "${suffixedTags[@]}"; do
amendedArgs+=("--amend" "$IMAGE_NAME:$suffixedTag")
done
if [[ -z $CIRCLE_TAG ]]; then
echo "Skipping manifest creation, not pushing images ..."
return
fi

for suffixedTag in "${suffixedTags[@]}"; do
echo "Pushing suffixed tag: $suffixedTag"
set -x
docker push "$IMAGE_NAME:$suffixedTag"
set +x
done
for tag in "${tags[@]}"; do
suffixedTags=()
for arch in "${archs[@]}"; do
suffixedTags+=("$tag-$arch")
done

amendedArgs=()
for suffixedTag in "${suffixedTags[@]}"; do
amendedArgs+=("--amend" "$remote_image_name:$suffixedTag")
done

for suffixedTag in "${suffixedTags[@]}"; do
echo "Pushing suffixed tag: $suffixedTag"
run_docker push "$remote_image_name:$suffixedTag"
done

echo "Creating Manifest for '$tag' from suffixed tags"
set -x
docker manifest create \
"$IMAGE_NAME:$tag" "${amendedArgs[@]}"
set +x

for suffixedTag in "${suffixedTags[@]}"; do
echo "Pushing Manifest: $tag"
set -x
docker manifest push "$IMAGE_NAME:$tag"
set +x
echo "Creating Manifest for '$tag' from suffixed tags"
run_docker manifest create \
"$remote_image_name:$tag" "${amendedArgs[@]}"

for suffixedTag in "${suffixedTags[@]}"; do
echo "Pushing Manifest: $tag"
run_docker manifest push "$remote_image_name:$tag"
done
done
}

# stitch and push all images in the manifest
mapfile -t images < <(yq -r 'keys[]' "$MANIFEST")
for image in "${images[@]}"; do
stitch_and_push_image "$image"
done
146 changes: 18 additions & 128 deletions shell/ci/release/docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,165 +6,55 @@ set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
LIB_DIR="${DIR}/../../lib"

# shellcheck source=../../lib/bootstrap.sh
source "${LIB_DIR}/bootstrap.sh"

# shellcheck source=../../lib/box.sh
source "${LIB_DIR}/box.sh"

APPNAME="$(get_app_name)"
VERSION="$(make --no-print-directory version)"
MANIFEST="$(get_repo_directory)/deployments/docker.yaml"

if [[ -z $TESTING_DO_NOT_BUILD ]]; then
# shellcheck source=../../lib/buildx.sh
source "${LIB_DIR}/buildx.sh"
fi
# shellcheck source=../../lib/buildx.sh
source "${LIB_DIR}/buildx.sh"

# shellcheck source=../../lib/logging.sh
source "${LIB_DIR}/logging.sh"

# shellcheck source=../../lib/yaml.sh
source "${LIB_DIR}/yaml.sh"

# get_image_field is a helper to return a field from the manifest
# for a given image. It will return an empty string if the field
# is not set.
#
# Arguments:
# $1 - image name
# $2 - field name
# $3 - type (array or string) (default: string)
# $4 - manifest file (default: $MANIFEST)
get_image_field() {
local image="$1"
local field="$2"

# type can be 'array' or 'string'. Array values are
# returned as newline separated values, string values
# are returned as a single string. A string value can
# be strings, ints, bools, etc.
local type=${3:-string}
local manifest=${4:-$MANIFEST}

local filter="$(yaml_construct_object_filter "$image" "$field")"
if [[ $type == "array" ]]; then
yaml_get_array "$filter" "$manifest"
return
elif [[ $type == "string" ]]; then
yaml_get_field "$filter" "$manifest"
return
else
error "Unknown type '$type' for get_image_field"
fatal "Expected 'array' or 'string'"
fi
}
# shellcheck source=../../lib/docker.sh
source "${LIB_DIR}/docker.sh"

# build_and_save_image builds and optionally saves the image to disk.
build_and_save_image() {
local image="$1"

# Platforms to build this image for, expected format (in YAML):
# platforms:
# - linux/arm64
# - linux/amd64
#
# See buildkit docs: https://github.com/docker/buildx#building-multi-platform-images
mapfile -t platforms < <(get_image_field "$image" "platforms" "array")
if [[ -z $platforms ]]; then
if [[ -z $IMAGE_ARCH ]]; then
platforms=("linux/arm64" "linux/amd64")
else
platforms=("linux/${IMAGE_ARCH}")
fi
fi

# Expose secrets to a docker container, expected format (in YAML):
#
# secrets:
# - id=secretID,env=ENV_VAR
#
# See docker docs:
# https://docs.docker.com/develop/develop-images/build_enhancements/#new-docker-build-secret-information
mapfile -t secrets < <(get_image_field "$image" "secrets" "array")
if [[ -z $secrets ]]; then
secrets=("id=npmtoken,env=NPM_TOKEN")
fi

local dockerfile="deployments/$image/Dockerfile"
if [[ ! -e $dockerfile ]]; then
echo "No dockerfile found at path: $dockerfile. Skipping."
return
fi

local args=(
"--ssh" "default"
"--progress=plain" "--file" "$dockerfile"
"--build-arg" "VERSION=${VERSION}"
)

for secret in "${secrets[@]}"; do
args+=("--secret" "$secret")
done

# Argument format: os/arch,os/arch
local platformArgumentString=""
for platform in "${platforms[@]}"; do
if [[ -n $platformArgumentString ]]; then
platformArgumentString+=","
fi
platformArgumentString+="$platform"
done
args+=("--platform" "$platformArgumentString")

# tags are the tags to apply to the image. If we're on a git tag,
# we'll tag the image with that tag and latest. Otherwise, we'll just
# build a latest image for the name "$image" (the name of the image as
# shown in the manifest) instead.
local tags=()
if [[ -z $CIRCLE_TAG ]]; then
tags+=("$image")
fi
for tag in "${tags[@]}"; do
args+=("--tag" "$tag")
done

mkdir -p docker-images
args+=("--output" "type=docker,dest=./docker-images/$(uname -m).tar")

# If we're not the main image, the build context should be
# the image directory instead.
local buildContext="$(get_image_field "$image" "buildContext")"
if [[ -z $buildContext ]]; then
buildContext="."
if [[ $APPNAME != "$image" ]]; then
buildContext="$(get_repo_directory)/deployments/$image"
fi
fi
args+=("$buildContext")
local args
args="$(docker_buildx_args "$APPNAME" "$VERSION" "$image" "$dockerfile" "$IMAGE_ARCH")"

echo "🔨 Building and saving Docker image to disk"
(
if [[ $OSTYPE == "linux-gnu"* ]]; then
docker buildx --builder devbase build "${args[@]}"
docker buildx prune --force --keep-storage 6GB
run_docker buildx --builder devbase build "$args"
run_docker buildx prune --force --keep-storage 6GB
else
docker buildx build "${args[@]}"
run_docker buildx build "$args"
fi
)
}

# HACK(jaredallard): Skips building images if TESTING_DO_NOT_BUILD is set. We
# should break out the functions from this script instead.
if [[ -z $TESTING_DO_NOT_BUILD ]]; then
if [[ ! -f $MANIFEST ]]; then
error "Manifest file '$MANIFEST' required for building Docker images"
fatal "See https://github.com/getoutreach/devbase#building-docker-images for details"
fi

# Build and save all images in the manifest
mapfile -t images < <(yq -r 'keys[]' "$MANIFEST")
for image in "${images[@]}"; do
build_and_save_image "$image"
done
if [[ ! -f $MANIFEST ]]; then
error "Manifest file '$MANIFEST' required for building Docker images"
fatal "See https://github.com/getoutreach/devbase#building-docker-images for details"
fi

# Build and save all images in the manifest
mapfile -t images < <(yq -r 'keys[]' "$MANIFEST")
for image in "${images[@]}"; do
build_and_save_image "$image"
done
40 changes: 0 additions & 40 deletions shell/ci/release/docker_test.bats

This file was deleted.

7 changes: 0 additions & 7 deletions shell/lib/buildx.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@ fi

# On macOS we don't need to create a builder or support QEMU.
if [[ $OSTYPE == "linux-gnu"* ]]; then
# Taken from setup-qemu Github Action
echo "💎 Installing QEMU static binaries..."
docker run --rm --privileged tonistiigi/binfmt:latest --install all

echo "� Extracting available platforms..."
docker run --rm --privileged tonistiigi/binfmt:latest

# Take from setup-buildx Github Action
echo "� Creating a new builder instance"
docker buildx create --use --name devbase
Expand Down
Loading

0 comments on commit 5698c2b

Please sign in to comment.