Explorar o código

Merge pull request #1029 from jjlin/multi-arch

Multi-arch image support
Daniel García %!s(int64=5) %!d(string=hai) anos
pai
achega
0b04caab78

+ 19 - 31
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,19 +109,7 @@ 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 %}
+{% elif "arm32v7" in target_file %}
 RUN apt-get update \
     && apt-get install -y \
         --no-install-recommends \
@@ -162,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"
@@ -182,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
@@ -208,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 %}
 
@@ -277,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 .

+ 3 - 0
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

+ 0 - 0
docker/armv6/mysql/Dockerfile → docker/arm32v6/mysql/Dockerfile


+ 0 - 0
docker/armv6/sqlite/Dockerfile → docker/arm32v6/sqlite/Dockerfile


+ 0 - 0
docker/armv7/mysql/Dockerfile → docker/arm32v7/mysql/Dockerfile


+ 0 - 0
docker/armv7/sqlite/Dockerfile → docker/arm32v7/sqlite/Dockerfile


+ 0 - 0
docker/aarch64/mysql/Dockerfile → docker/arm64v8/mysql/Dockerfile


+ 0 - 0
docker/aarch64/sqlite/Dockerfile → docker/arm64v8/sqlite/Dockerfile


+ 20 - 0
hooks/README.md

@@ -0,0 +1,20 @@
+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/
+* https://success.docker.com/article/how-do-i-authenticate-with-the-v2-api

+ 30 - 0
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

+ 14 - 0
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

+ 96 - 0
hooks/push

@@ -0,0 +1,96 @@
+#!/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` 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)
+    else
+        manifest_lists+=(${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}
+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#*/}"
+
+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