|
|
@@ -1,117 +1,138 @@
|
|
|
#!/bin/bash
|
|
|
|
|
|
-echo ">>> Pushing images..."
|
|
|
+source ./hooks/arches.sh
|
|
|
|
|
|
export DOCKER_CLI_EXPERIMENTAL=enabled
|
|
|
|
|
|
-declare -A annotations=(
|
|
|
- [amd64]="--os linux --arch amd64"
|
|
|
- [arm32v6]="--os linux --arch arm --variant v6"
|
|
|
- [arm32v7]="--os linux --arch arm --variant v7"
|
|
|
- [arm64v8]="--os linux --arch arm64 --variant v8"
|
|
|
-)
|
|
|
-
|
|
|
-source ./hooks/arches.sh
|
|
|
+# Join a list of args with a single char.
|
|
|
+# Ref: https://stackoverflow.com/a/17841619
|
|
|
+join() { local IFS="$1"; shift; echo "$*"; }
|
|
|
|
|
|
set -ex
|
|
|
|
|
|
-declare -A images
|
|
|
+echo ">>> Starting local Docker registry..."
|
|
|
+
|
|
|
+# Docker Buildx's `docker-container` driver is needed for multi-platform
|
|
|
+# builds, but it can't access existing images on the Docker host (like the
|
|
|
+# cross-compiled ones we just built). Those images first need to be pushed to
|
|
|
+# a registry -- Docker Hub could be used, but since it's not trivial to clean
|
|
|
+# up those intermediate images on Docker Hub, it's easier to just run a local
|
|
|
+# Docker registry, which gets cleaned up automatically once the build job ends.
|
|
|
+#
|
|
|
+# https://docs.docker.com/registry/deploying/
|
|
|
+# https://hub.docker.com/_/registry
|
|
|
+#
|
|
|
+# Use host networking so the buildx container can access the registry via
|
|
|
+# localhost.
|
|
|
+#
|
|
|
+docker run -d --name registry --network host registry:2 # defaults to port 5000
|
|
|
+
|
|
|
+# Docker Hub sets a `DOCKER_REPO` env var with the format `index.docker.io/user/repo`.
|
|
|
+# Strip the registry portion to construct a local repo path for use in `Dockerfile.buildx`.
|
|
|
+LOCAL_REGISTRY="localhost:5000"
|
|
|
+REPO="${DOCKER_REPO#*/}"
|
|
|
+LOCAL_REPO="${LOCAL_REGISTRY}/${REPO}"
|
|
|
+
|
|
|
+echo ">>> Pushing images to local registry..."
|
|
|
+
|
|
|
for arch in ${arches[@]}; do
|
|
|
- images[$arch]="${DOCKER_REPO}:${DOCKER_TAG}-${arch}"
|
|
|
+ docker_image="${DOCKER_REPO}:${DOCKER_TAG}-${arch}"
|
|
|
+ local_image="${LOCAL_REPO}:${DOCKER_TAG}-${arch}"
|
|
|
+ docker tag "${docker_image}" "${local_image}"
|
|
|
+ docker push "${local_image}"
|
|
|
done
|
|
|
|
|
|
-# Push the images that were just built; manifest list creation fails if the
|
|
|
-# images (manifests) referenced don't already exist in the Docker registry.
|
|
|
-for image in "${images[@]}"; do
|
|
|
- docker push "${image}"
|
|
|
-done
|
|
|
+echo ">>> Setting up Docker Buildx..."
|
|
|
+
|
|
|
+# Same as earlier, use host networking so the buildx container can access the
|
|
|
+# registry via localhost.
|
|
|
+#
|
|
|
+# Ref: https://github.com/docker/buildx/issues/94#issuecomment-534367714
|
|
|
+#
|
|
|
+docker buildx create --name builder --use --driver-opt network=host
|
|
|
|
|
|
-manifest_lists=("${DOCKER_REPO}:${DOCKER_TAG}")
|
|
|
+echo ">>> Running Docker Buildx..."
|
|
|
|
|
|
-# If the Docker tag starts with a version number, assume the latest release is
|
|
|
-# being pushed. Add an extra manifest (`latest` or `alpine`, as appropriate)
|
|
|
+tags=("${DOCKER_REPO}:${DOCKER_TAG}")
|
|
|
+
|
|
|
+# If the Docker tag starts with a version number, assume the latest release
|
|
|
+# is being pushed. Add an extra tag (`latest` or `alpine`, as appropriate)
|
|
|
# to make it easier for users to track the latest release.
|
|
|
if [[ "${DOCKER_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
|
|
if [[ "${DOCKER_TAG}" == *alpine ]]; then
|
|
|
- manifest_lists+=(${DOCKER_REPO}:alpine)
|
|
|
+ tags+=(${DOCKER_REPO}:alpine)
|
|
|
else
|
|
|
- manifest_lists+=(${DOCKER_REPO}:latest)
|
|
|
-
|
|
|
- # Add an extra `latest-arm32v6` tag; Docker can't seem to properly
|
|
|
- # auto-select that image on Armv6 platforms like Raspberry Pi 1 and Zero
|
|
|
- # (https://github.com/moby/moby/issues/41017).
|
|
|
- #
|
|
|
- # Add this tag only for the SQLite image, as the MySQL and PostgreSQL
|
|
|
- # builds don't currently work on non-amd64 arches.
|
|
|
- #
|
|
|
- # TODO: Also add an `alpine-arm32v6` tag if multi-arch support for
|
|
|
- # Alpine-based bitwarden_rs images is implemented before this Docker
|
|
|
- # issue is fixed.
|
|
|
- if [[ ${DOCKER_REPO} == *server ]]; then
|
|
|
- docker tag "${DOCKER_REPO}:${DOCKER_TAG}-arm32v6" "${DOCKER_REPO}:latest-arm32v6"
|
|
|
- docker push "${DOCKER_REPO}:latest-arm32v6"
|
|
|
- fi
|
|
|
+ tags+=(${DOCKER_REPO}:latest)
|
|
|
fi
|
|
|
fi
|
|
|
|
|
|
-for manifest_list in "${manifest_lists[@]}"; do
|
|
|
- # Create the (multi-arch) manifest list of arch-specific images.
|
|
|
- docker manifest create ${manifest_list} ${images[@]}
|
|
|
-
|
|
|
- # Make sure each image manifest is annotated with the correct arch info.
|
|
|
- # Docker does not auto-detect the arch of each cross-compiled image, so
|
|
|
- # everything would appear as `linux/amd64` otherwise.
|
|
|
- for arch in "${arches[@]}"; do
|
|
|
- docker manifest annotate ${annotations[$arch]} ${manifest_list} ${images[$arch]}
|
|
|
- done
|
|
|
-
|
|
|
- # Push the manifest list.
|
|
|
- docker manifest push --purge ${manifest_list}
|
|
|
+tag_args=()
|
|
|
+for tag in "${tags[@]}"; do
|
|
|
+ tag_args+=(--tag "${tag}")
|
|
|
done
|
|
|
|
|
|
-# Avoid logging credentials and tokens.
|
|
|
-set +ex
|
|
|
-
|
|
|
-# Delete the arch-specific tags, if credentials for doing so are available.
|
|
|
-# Note that `DOCKER_PASSWORD` must be the actual user password. Passing a JWT
|
|
|
-# obtained using a personal access token results in a 403 error with
|
|
|
-# {"detail": "access to the resource is forbidden with personal access token"}
|
|
|
-if [[ -z "${DOCKER_USERNAME}" || -z "${DOCKER_PASSWORD}" ]]; then
|
|
|
- exit 0
|
|
|
-fi
|
|
|
-
|
|
|
-# Given a JSON input on stdin, extract the string value associated with the
|
|
|
-# specified key. This avoids an extra dependency on a tool like `jq`.
|
|
|
-extract() {
|
|
|
- local key="$1"
|
|
|
- # Extract "<key>":"<val>" (assumes key/val won't contain double quotes).
|
|
|
- # The colon may have whitespace on either side.
|
|
|
- grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]\+\"" |
|
|
|
- # Extract just <val> by deleting the last '"', and then greedily deleting
|
|
|
- # everything up to '"'.
|
|
|
- sed -e 's/"$//' -e 's/.*"//'
|
|
|
-}
|
|
|
-
|
|
|
-echo ">>> Getting API token..."
|
|
|
-jwt=$(curl -sS -X POST \
|
|
|
- -H "Content-Type: application/json" \
|
|
|
- -d "{\"username\":\"${DOCKER_USERNAME}\",\"password\": \"${DOCKER_PASSWORD}\"}" \
|
|
|
- "https://hub.docker.com/v2/users/login" |
|
|
|
- extract 'token')
|
|
|
-
|
|
|
-# Strip the registry portion from `index.docker.io/user/repo`.
|
|
|
-repo="${DOCKER_REPO#*/}"
|
|
|
-
|
|
|
+# Docker Buildx takes a list of target platforms (OS/arch/variant), so map
|
|
|
+# the arch list to a platform list (assuming the OS is always `linux`).
|
|
|
+declare -A arch_to_platform=(
|
|
|
+ [amd64]="linux/amd64"
|
|
|
+ [armv6]="linux/arm/v6"
|
|
|
+ [armv7]="linux/arm/v7"
|
|
|
+ [arm64]="linux/arm64"
|
|
|
+)
|
|
|
+platforms=()
|
|
|
for arch in ${arches[@]}; do
|
|
|
- # Don't delete the `arm32v6` tag; Docker can't seem to properly
|
|
|
- # auto-select that image on Armv6 platforms like Raspberry Pi 1 and Zero
|
|
|
- # (https://github.com/moby/moby/issues/41017).
|
|
|
- if [[ ${arch} == 'arm32v6' ]]; then
|
|
|
- continue
|
|
|
- fi
|
|
|
- tag="${DOCKER_TAG}-${arch}"
|
|
|
- echo ">>> Deleting '${repo}:${tag}'..."
|
|
|
- curl -sS -X DELETE \
|
|
|
- -H "Authorization: Bearer ${jwt}" \
|
|
|
- "https://hub.docker.com/v2/repositories/${repo}/tags/${tag}/"
|
|
|
+ platforms+=("${arch_to_platform[$arch]}")
|
|
|
done
|
|
|
+platforms="$(join "," "${platforms[@]}")"
|
|
|
+
|
|
|
+# Run the build, pushing the resulting images and multi-arch manifest list to
|
|
|
+# Docker Hub. The Dockerfile is read from stdin to avoid sending any build
|
|
|
+# context, which isn't needed here since the actual cross-compiled images
|
|
|
+# have already been built.
|
|
|
+docker buildx build \
|
|
|
+ --network host \
|
|
|
+ --build-arg LOCAL_REPO="${LOCAL_REPO}" \
|
|
|
+ --build-arg DOCKER_TAG="${DOCKER_TAG}" \
|
|
|
+ --platform "${platforms}" \
|
|
|
+ "${tag_args[@]}" \
|
|
|
+ --push \
|
|
|
+ - < ./docker/Dockerfile.buildx
|
|
|
+
|
|
|
+# Add an extra arch-specific tag for `arm32v6`; Docker can't seem to properly
|
|
|
+# auto-select that image on ARMv6 platforms like Raspberry Pi 1 and Zero
|
|
|
+# (https://github.com/moby/moby/issues/41017).
|
|
|
+#
|
|
|
+# Note that we use `arm32v6` instead of `armv6` to be consistent with the
|
|
|
+# existing bitwarden_rs tags, which adhere to the naming conventions of the
|
|
|
+# Docker per-architecture repos (e.g., https://hub.docker.com/u/arm32v6).
|
|
|
+# Unfortunately, these per-arch repo names aren't always consistent with the
|
|
|
+# corresponding platform (OS/arch/variant) IDs, particularly in the case of
|
|
|
+# 32-bit ARM arches (e.g., `linux/arm/v6` is used, not `linux/arm32/v6`).
|
|
|
+#
|
|
|
+# TODO: It looks like this issue should be fixed starting in Docker 20.10.0,
|
|
|
+# so this step can be removed once fixed versions are in wider distribution.
|
|
|
+#
|
|
|
+# Tags:
|
|
|
+#
|
|
|
+# testing => testing-arm32v6
|
|
|
+# testing-alpine => <ignored>
|
|
|
+# x.y.z => x.y.z-arm32v6, latest-arm32v6
|
|
|
+# x.y.z-alpine => <ignored>
|
|
|
+#
|
|
|
+if [[ "${DOCKER_TAG}" != *alpine ]]; then
|
|
|
+ image="${DOCKER_REPO}":"${DOCKER_TAG}"
|
|
|
+
|
|
|
+ # Fetch the multi-arch manifest list and find the digest of the armv6 image.
|
|
|
+ filter='.manifests|.[]|select(.platform.architecture=="arm" and .platform.variant=="v6")|.digest'
|
|
|
+ digest="$(docker manifest inspect "${image}" | jq -r "${filter}")"
|
|
|
+
|
|
|
+ # Pull the armv6 image by digest, retag it, and repush it.
|
|
|
+ docker pull "${DOCKER_REPO}"@"${digest}"
|
|
|
+ docker tag "${DOCKER_REPO}"@"${digest}" "${image}"-arm32v6
|
|
|
+ docker push "${image}"-arm32v6
|
|
|
+
|
|
|
+ if [[ "${DOCKER_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
|
|
+ docker tag "${image}"-arm32v6 "${DOCKER_REPO}:latest"-arm32v6
|
|
|
+ docker push "${DOCKER_REPO}:latest"-arm32v6
|
|
|
+ fi
|
|
|
+fi
|