From bbef332e25f2e9045c70d7219d7d7f1a38cda316 Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Wed, 10 Jun 2020 01:51:38 -0700 Subject: [PATCH 1/4] Dockerfile.j2: remove dead code --- docker/Dockerfile.j2 | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docker/Dockerfile.j2 b/docker/Dockerfile.j2 index 691f5fb3..71de1adc 100644 --- a/docker/Dockerfile.j2 +++ b/docker/Dockerfile.j2 @@ -109,18 +109,6 @@ RUN apt-get update \ ENV CARGO_HOME "/root/.cargo" ENV USER "root" -{% elif "armv6" in target_file %} -RUN apt-get update \ - && apt-get install -y \ - --no-install-recommends \ - gcc-arm-linux-gnueabihf \ - && mkdir -p ~/.cargo \ - && echo '[target.armv7-unknown-linux-gnueabihf]' >> ~/.cargo/config \ - && echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config - -ENV CARGO_HOME "/root/.cargo" -ENV USER "root" - {% elif "armv7" in target_file %} RUN apt-get update \ && apt-get install -y \ From 4559e85daa31afb5120c5320760a708f2e5ea862 Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Wed, 10 Jun 2020 02:01:41 -0700 Subject: [PATCH 2/4] Multi-arch image support --- docker/Dockerfile.j2 | 38 ++++++------- docker/README.md | 3 + docker/{armv6 => arm32v6}/mysql/Dockerfile | 0 docker/{armv6 => arm32v6}/sqlite/Dockerfile | 0 docker/{armv7 => arm32v7}/mysql/Dockerfile | 0 docker/{armv7 => arm32v7}/sqlite/Dockerfile | 0 docker/{aarch64 => arm64v8}/mysql/Dockerfile | 0 docker/{aarch64 => arm64v8}/sqlite/Dockerfile | 0 hooks/README.md | 19 +++++++ hooks/arches.sh | 30 ++++++++++ hooks/build | 14 +++++ hooks/push | 55 +++++++++++++++++++ 12 files changed, 140 insertions(+), 19 deletions(-) create mode 100644 docker/README.md rename docker/{armv6 => arm32v6}/mysql/Dockerfile (100%) rename docker/{armv6 => arm32v6}/sqlite/Dockerfile (100%) rename docker/{armv7 => arm32v7}/mysql/Dockerfile (100%) rename docker/{armv7 => arm32v7}/sqlite/Dockerfile (100%) rename docker/{aarch64 => arm64v8}/mysql/Dockerfile (100%) rename docker/{aarch64 => arm64v8}/sqlite/Dockerfile (100%) create mode 100644 hooks/README.md create mode 100644 hooks/arches.sh create mode 100755 hooks/build create mode 100755 hooks/push diff --git a/docker/Dockerfile.j2 b/docker/Dockerfile.j2 index 71de1adc..d6c3dc2e 100644 --- a/docker/Dockerfile.j2 +++ b/docker/Dockerfile.j2 @@ -9,13 +9,13 @@ {% elif "amd64" in target_file %} {% set runtime_stage_base_image = "debian:buster-slim" %} {% set package_arch_name = "" %} -{% elif "aarch64" in target_file %} +{% elif "arm64v8" in target_file %} {% set runtime_stage_base_image = "balenalib/aarch64-debian:buster" %} {% set package_arch_name = "arm64" %} -{% elif "armv6" in target_file %} +{% elif "arm32v6" in target_file %} {% set runtime_stage_base_image = "balenalib/rpi-debian:buster" %} {% set package_arch_name = "armel" %} -{% elif "armv7" in target_file %} +{% elif "arm32v7" in target_file %} {% set runtime_stage_base_image = "balenalib/armv7hf-debian:buster" %} {% set package_arch_name = "armhf" %} {% endif %} @@ -73,7 +73,7 @@ RUN rustup set profile minimal ENV USER "root" ENV RUSTFLAGS='-C link-arg=-s' -{% elif "aarch64" in target_file or "armv" in target_file %} +{% elif "arm32" in target_file or "arm64" in target_file %} # Install required build libs for {{ package_arch_name }} architecture. RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \ /etc/apt/sources.list.d/deb-src.list \ @@ -85,7 +85,7 @@ RUN sed 's/^deb/deb-src/' /etc/apt/sources.list > \ libc6-dev{{ package_arch_prefix }} {% endif -%} -{% if "aarch64" in target_file %} +{% if "arm64v8" in target_file %} RUN apt-get update \ && apt-get install -y \ --no-install-recommends \ @@ -97,7 +97,7 @@ RUN apt-get update \ ENV CARGO_HOME "/root/.cargo" ENV USER "root" -{% elif "armv6" in target_file %} +{% elif "arm32v6" in target_file %} RUN apt-get update \ && apt-get install -y \ --no-install-recommends \ @@ -109,7 +109,7 @@ RUN apt-get update \ ENV CARGO_HOME "/root/.cargo" ENV USER "root" -{% elif "armv7" in target_file %} +{% elif "arm32v7" in target_file %} RUN apt-get update \ && apt-get install -y \ --no-install-recommends \ @@ -150,17 +150,17 @@ COPY ./Cargo.* ./ COPY ./rust-toolchain ./rust-toolchain COPY ./build.rs ./build.rs -{% if "aarch64" in target_file %} +{% if "arm64v8" in target_file %} ENV CC_aarch64_unknown_linux_gnu="/usr/bin/aarch64-linux-gnu-gcc" ENV CROSS_COMPILE="1" ENV OPENSSL_INCLUDE_DIR="/usr/include/aarch64-linux-gnu" ENV OPENSSL_LIB_DIR="/usr/lib/aarch64-linux-gnu" -{% elif "armv6" in target_file %} +{% elif "arm32v6" in target_file %} ENV CC_arm_unknown_linux_gnueabi="/usr/bin/arm-linux-gnueabi-gcc" ENV CROSS_COMPILE="1" ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabi" ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabi" -{% elif "armv7" in target_file %} +{% elif "arm32v7" in target_file %} ENV CC_armv7_unknown_linux_gnueabihf="/usr/bin/arm-linux-gnueabihf-gcc" ENV CROSS_COMPILE="1" ENV OPENSSL_INCLUDE_DIR="/usr/include/arm-linux-gnueabihf" @@ -170,13 +170,13 @@ ENV OPENSSL_LIB_DIR="/usr/lib/arm-linux-gnueabihf" {% if "alpine" in target_file %} RUN rustup target add x86_64-unknown-linux-musl -{% elif "aarch64" in target_file %} +{% elif "arm64v8" in target_file %} RUN rustup target add aarch64-unknown-linux-gnu -{% elif "armv6" in target_file %} +{% elif "arm32v6" in target_file %} RUN rustup target add arm-unknown-linux-gnueabi -{% elif "armv7" in target_file %} +{% elif "arm32v7" in target_file %} RUN rustup target add armv7-unknown-linux-gnueabihf {% endif %} # Builds your dependencies and removes the @@ -196,11 +196,11 @@ RUN touch src/main.rs # your actual source files being built {% if "amd64" in target_file %} RUN cargo build --features ${DB} --release -{% elif "aarch64" in target_file %} +{% elif "arm64v8" in target_file %} RUN cargo build --features ${DB} --release --target=aarch64-unknown-linux-gnu -{% elif "armv6" in target_file %} +{% elif "arm32v6" in target_file %} RUN cargo build --features ${DB} --release --target=arm-unknown-linux-gnueabi -{% elif "armv7" in target_file %} +{% elif "arm32v7" in target_file %} RUN cargo build --features ${DB} --release --target=armv7-unknown-linux-gnueabihf {% endif %} @@ -265,11 +265,11 @@ COPY Rocket.toml . COPY --from=vault /web-vault ./web-vault {% if "alpine" in target_file %} COPY --from=build /app/target/x86_64-unknown-linux-musl/release/bitwarden_rs . -{% elif "aarch64" in target_file %} +{% elif "arm64v8" in target_file %} COPY --from=build /app/target/aarch64-unknown-linux-gnu/release/bitwarden_rs . -{% elif "armv6" in target_file %} +{% elif "arm32v6" in target_file %} COPY --from=build /app/target/arm-unknown-linux-gnueabi/release/bitwarden_rs . -{% elif "armv7" in target_file %} +{% elif "arm32v7" in target_file %} COPY --from=build /app/target/armv7-unknown-linux-gnueabihf/release/bitwarden_rs . {% else %} COPY --from=build app/target/release/bitwarden_rs . diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..1dbfe22c --- /dev/null +++ b/docker/README.md @@ -0,0 +1,3 @@ +The arch-specific directory names follow the arch identifiers used by the Docker official images: + +https://github.com/docker-library/official-images/blob/master/README.md#architectures-other-than-amd64 diff --git a/docker/armv6/mysql/Dockerfile b/docker/arm32v6/mysql/Dockerfile similarity index 100% rename from docker/armv6/mysql/Dockerfile rename to docker/arm32v6/mysql/Dockerfile diff --git a/docker/armv6/sqlite/Dockerfile b/docker/arm32v6/sqlite/Dockerfile similarity index 100% rename from docker/armv6/sqlite/Dockerfile rename to docker/arm32v6/sqlite/Dockerfile diff --git a/docker/armv7/mysql/Dockerfile b/docker/arm32v7/mysql/Dockerfile similarity index 100% rename from docker/armv7/mysql/Dockerfile rename to docker/arm32v7/mysql/Dockerfile diff --git a/docker/armv7/sqlite/Dockerfile b/docker/arm32v7/sqlite/Dockerfile similarity index 100% rename from docker/armv7/sqlite/Dockerfile rename to docker/arm32v7/sqlite/Dockerfile diff --git a/docker/aarch64/mysql/Dockerfile b/docker/arm64v8/mysql/Dockerfile similarity index 100% rename from docker/aarch64/mysql/Dockerfile rename to docker/arm64v8/mysql/Dockerfile diff --git a/docker/aarch64/sqlite/Dockerfile b/docker/arm64v8/sqlite/Dockerfile similarity index 100% rename from docker/aarch64/sqlite/Dockerfile rename to docker/arm64v8/sqlite/Dockerfile diff --git a/hooks/README.md b/hooks/README.md new file mode 100644 index 00000000..0932987a --- /dev/null +++ b/hooks/README.md @@ -0,0 +1,19 @@ +The hooks in this directory are used to create multi-arch images using Docker Hub automated builds. + +Docker Hub hooks provide these predefined [environment variables](https://docs.docker.com/docker-hub/builds/advanced/#environment-variables-for-building-and-testing): + +* `SOURCE_BRANCH`: the name of the branch or the tag that is currently being tested. +* `SOURCE_COMMIT`: the SHA1 hash of the commit being tested. +* `COMMIT_MSG`: the message from the commit being tested and built. +* `DOCKER_REPO`: the name of the Docker repository being built. +* `DOCKERFILE_PATH`: the dockerfile currently being built. +* `DOCKER_TAG`: the Docker repository tag being built. +* `IMAGE_NAME`: the name and tag of the Docker repository being built. (This variable is a combination of `DOCKER_REPO:DOCKER_TAG`.) + +The current multi-arch image build relies on the original bitwarden_rs Dockerfiles, which use cross-compilation for architectures other than `amd64`, and don't yet support all arch/database/OS combinations. However, cross-compilation is much faster than QEMU-based builds (e.g., using `docker buildx`). This situation may need to be revisited at some point. + +## References + +* https://docs.docker.com/docker-hub/builds/advanced/ +* https://docs.docker.com/engine/reference/commandline/manifest/ +* https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/ diff --git a/hooks/arches.sh b/hooks/arches.sh new file mode 100644 index 00000000..216179e3 --- /dev/null +++ b/hooks/arches.sh @@ -0,0 +1,30 @@ +# The default Debian-based SQLite images support these arches. +# +# Other images (Alpine-based, or with other database backends) currently +# support only a subset of these. +arches=( + amd64 + arm32v6 + arm32v7 + arm64v8 +) + +case "${DOCKER_REPO}" in + *-mysql) + db=mysql + arches=(amd64) + ;; + *-postgresql) + db=postgresql + arches=(amd64) + ;; + *) + db=sqlite + ;; +esac + +if [[ "${DOCKER_TAG}" == *alpine ]]; then + # The Alpine build currently only works for amd64. + os_suffix=.alpine + arches=(amd64) +fi diff --git a/hooks/build b/hooks/build new file mode 100755 index 00000000..2a534606 --- /dev/null +++ b/hooks/build @@ -0,0 +1,14 @@ +#!/bin/bash + +echo ">>> Building images..." + +source ./hooks/arches.sh + +set -ex + +for arch in "${arches[@]}"; do + docker build \ + -t "${DOCKER_REPO}:${DOCKER_TAG}-${arch}" \ + -f docker/${arch}/${db}/Dockerfile${os_suffix} \ + . +done diff --git a/hooks/push b/hooks/push new file mode 100755 index 00000000..5fd96079 --- /dev/null +++ b/hooks/push @@ -0,0 +1,55 @@ +#!/bin/bash + +echo ">>> Pushing images..." + +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 + +set -ex + +declare -A images +for arch in ${arches[@]}; do + images[$arch]="${DOCKER_REPO}:${DOCKER_TAG}-${arch}" +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 + +manifest_lists=("${DOCKER_REPO}:${DOCKER_TAG}") + +# If the Docker tag starts with a version number, assume the latest release is +# being pushed. Add an extra manifest (`latest-release` or `alpine-release`, +# as appropriate) to make it easier 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-release) + else + manifest_lists+=(${DOCKER_REPO}:latest-release) + 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} +done From 73f0841f17cfa4a1b8f4cc2cf9a2509c517355e4 Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Sun, 14 Jun 2020 00:00:05 -0700 Subject: [PATCH 3/4] Clean up arch-specific tags if Docker Hub credentials are provided --- hooks/README.md | 1 + hooks/push | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/hooks/README.md b/hooks/README.md index 0932987a..402f4bad 100644 --- a/hooks/README.md +++ b/hooks/README.md @@ -17,3 +17,4 @@ The current multi-arch image build relies on the original bitwarden_rs Dockerfil * https://docs.docker.com/docker-hub/builds/advanced/ * https://docs.docker.com/engine/reference/commandline/manifest/ * https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/ +* https://success.docker.com/article/how-do-i-authenticate-with-the-v2-api diff --git a/hooks/push b/hooks/push index 5fd96079..aedd0f04 100755 --- a/hooks/push +++ b/hooks/push @@ -53,3 +53,44 @@ for manifest_list in "${manifest_lists[@]}"; do # Push the manifest list. docker manifest push --purge ${manifest_list} 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 "":"" (assumes key/val won't contain double quotes). + # The colon may have whitespace on either side. + grep -o "\"${key}\"[[:space:]]*:[[:space:]]*\"[^\"]\+\"" | + # Extract just 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#*/}" + +for arch in ${arches[@]}; do + 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}/" +done From 4c78c5a9c922db803172f1f74f30c70b960f8a84 Mon Sep 17 00:00:00 2001 From: Jeremy Lin Date: Wed, 15 Jul 2020 20:02:18 -0700 Subject: [PATCH 4/4] Tag latest releases as `latest` and `alpine` --- hooks/push | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hooks/push b/hooks/push index aedd0f04..c22095ee 100755 --- a/hooks/push +++ b/hooks/push @@ -29,13 +29,13 @@ done manifest_lists=("${DOCKER_REPO}:${DOCKER_TAG}") # If the Docker tag starts with a version number, assume the latest release is -# being pushed. Add an extra manifest (`latest-release` or `alpine-release`, -# as appropriate) to make it easier to track the latest release. +# being pushed. Add an extra manifest (`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-release) + manifest_lists+=(${DOCKER_REPO}:alpine) else - manifest_lists+=(${DOCKER_REPO}:latest-release) + manifest_lists+=(${DOCKER_REPO}:latest) fi fi