diff --git a/tools/docker/README.md b/tools/docker/README.md index e346218da..966c6be74 100644 --- a/tools/docker/README.md +++ b/tools/docker/README.md @@ -12,7 +12,7 @@ High-level behavior When a new version tag (eg. v0.35.0) is pushed to this repository, it triggers a new build in each DockerHub project, to construct and publish the new version of the Docker, containing the Certbot version corresponding to the pushed tag. With the example of the v0.35.0, the DockerHub projects will contain after few minutes a new tag v0.35.0, -whose the Docker contains Certbot v0.35.0. +whose the Docker contains Certbot v0.35.0. Configuration ------------- @@ -58,3 +58,14 @@ This script will trigger the publication on DockerHub of all Dockers for the giv - commit locally the modifications, - tag this commit with the given version, - push this tag and the updated `master` branch. + +Assuming the version to publish is `v0.35.0`, the following docker images will be created at DockerHub. + +- certbot/certbot:v0.35.0 *(amd64 architecture)* +- certbot/certbot:amd64-v0.35.0 +- certbot/certbot:arm32v6-v0.35.0 +- certbot/certbot:arm64v8-v0.35.0 +- certbot/certbot:latest *(amd64 architecture)* +- certbot/certbot:amd64-latest +- certbot/certbot:arm32v6-latest +- certbot/certbot:arm64v8-latest diff --git a/tools/docker/build.sh b/tools/docker/build.sh index bd1b8ec19..765aa79c5 100755 --- a/tools/docker/build.sh +++ b/tools/docker/build.sh @@ -13,34 +13,32 @@ trap Cleanup 1 2 3 6 Cleanup() { if [ ! -z "$WORK_DIR" ]; then - rm -rf "$WORK_DIR/plugin/certbot" || true - rm -rf "$WORK_DIR/core/certbot" || true + rm -rf "$WORK_DIR"/core/qemu-*-static || true + rm -rf "$WORK_DIR"/plugin/qemu-*-static || true fi popd 2> /dev/null || true } +Build() { + DOCKER_REPO="$1" + CERTBOT_VERSION="$2" + CONTEXT_PATH="$3" + DOCKERFILE_PATH="$CONTEXT_PATH/Dockerfile" + DOCKER_TAG="$CERTBOT_VERSION" + pushd "$CONTEXT_PATH" + DOCKER_TAG="$DOCKER_TAG" DOCKER_REPO="$DOCKER_REPO" DOCKERFILE_PATH="$DOCKERFILE_PATH" bash hooks/pre_build + DOCKER_TAG="$DOCKER_TAG" DOCKER_REPO="$DOCKER_REPO" DOCKERFILE_PATH="$DOCKERFILE_PATH" bash hooks/build + popd +} + WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -DOCKER_TAG="$1" -SOURCE_BRANCH="$DOCKER_TAG" - -Cleanup +CERTBOT_VERSION="$1" # Step 1: Certbot core Docker - -DOCKER_REPO="certbot/certbot" -CONTEXT_PATH="$WORK_DIR/core" -DOCKERFILE_PATH="$CONTEXT_PATH/Dockerfile" -IMAGE_NAME="$DOCKER_REPO:$DOCKER_TAG" - -pushd "$CONTEXT_PATH" - DOCKER_TAG="$DOCKER_TAG" DOCKER_REPO="$DOCKER_REPO" DOCKERFILE_PATH="$DOCKERFILE_PATH" IMAGE_NAME="$IMAGE_NAME" bash hooks/build -popd - -Cleanup +Build "certbot/certbot" "$CERTBOT_VERSION" "$WORK_DIR/core" # Step 2: Certbot dns plugins Dockers - CERTBOT_PLUGINS_DOCKER_REPOS=( "certbot/dns-dnsmadeeasy" "certbot/dns-dnsimple" @@ -58,15 +56,8 @@ CERTBOT_PLUGINS_DOCKER_REPOS=( "certbot/dns-sakuracloud" ) -for DOCKER_REPO in ${CERTBOT_PLUGINS_DOCKER_REPOS[@]}; do - CONTEXT_PATH="$WORK_DIR/plugin" - DOCKERFILE_PATH="$CONTEXT_PATH/Dockerfile" - IMAGE_NAME="$DOCKER_REPO:$DOCKER_TAG" - - pushd "$CONTEXT_PATH" - DOCKER_TAG="$DOCKER_TAG" DOCKER_REPO="$DOCKER_REPO" DOCKERFILE_PATH="$DOCKERFILE_PATH" IMAGE_NAME="$IMAGE_NAME" bash hooks/pre_build - DOCKER_TAG="$DOCKER_TAG" DOCKER_REPO="$DOCKER_REPO" DOCKERFILE_PATH="$DOCKERFILE_PATH" IMAGE_NAME="$IMAGE_NAME" bash hooks/build - popd - - Cleanup +for DOCKER_REPO in "${CERTBOT_PLUGINS_DOCKER_REPOS[@]}"; do + Build "${DOCKER_REPO}" "$CERTBOT_VERSION" "$WORK_DIR/plugin" done + +Cleanup diff --git a/tools/docker/core/.gitignore b/tools/docker/core/.gitignore new file mode 100644 index 000000000..4cc493afa --- /dev/null +++ b/tools/docker/core/.gitignore @@ -0,0 +1 @@ +qemu-*-static diff --git a/tools/docker/core/Dockerfile b/tools/docker/core/Dockerfile index 2b0ca2b0c..1cf71dc2f 100644 --- a/tools/docker/core/Dockerfile +++ b/tools/docker/core/Dockerfile @@ -1,4 +1,11 @@ -FROM python:3.7-alpine3.10 +# Docker Arch (amd64, arm32v6, ...) +ARG TARGET_ARCH +FROM ${TARGET_ARCH}/python:3.7-alpine3.10 + +# Qemu Arch (x86_64, arm, ...) +ARG QEMU_ARCH +ENV QEMU_ARCH=${QEMU_ARCH} +COPY qemu-${QEMU_ARCH}-static /usr/bin/ ARG CERTBOT_VERSION ENV CERTBOT_VERSION=${CERTBOT_VERSION} diff --git a/tools/docker/core/hooks/build b/tools/docker/core/hooks/build index e0e4d6571..9f3f035d9 100644 --- a/tools/docker/core/hooks/build +++ b/tools/docker/core/hooks/build @@ -1,5 +1,11 @@ #!/bin/bash set -ex -CERTBOT_VERSION=${DOCKER_TAG//v/} -docker build --build-arg CERTBOT_VERSION=${CERTBOT_VERSION} -f ${DOCKERFILE_PATH} -t ${IMAGE_NAME} . +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$WORK_DIR/../../lib/common" + +CERTBOT_VERSION=$(GetCerbotVersionFromTag "$DOCKER_TAG") + +for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + BuildDockerCoreImage "${TARGET_ARCH}" "${CERTBOT_VERSION}" +done diff --git a/tools/docker/core/hooks/post_push b/tools/docker/core/hooks/post_push index 6b8459521..6bac191fd 100644 --- a/tools/docker/core/hooks/post_push +++ b/tools/docker/core/hooks/post_push @@ -1,5 +1,12 @@ #!/bin/bash set -ex -docker tag ${IMAGE_NAME} "${DOCKER_REPO}:latest" -docker push "${DOCKER_REPO}:latest" +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$WORK_DIR/../../lib/common" + +CERTBOT_VERSION=$(GetCerbotVersionFromTag "$DOCKER_TAG") + +for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + TagDockerImageAliases "${TARGET_ARCH}" "${CERTBOT_VERSION}" + PushDockerImageAliases "${TARGET_ARCH}" "${CERTBOT_VERSION}" +done diff --git a/tools/docker/core/hooks/pre_build b/tools/docker/core/hooks/pre_build new file mode 100755 index 000000000..723e35161 --- /dev/null +++ b/tools/docker/core/hooks/pre_build @@ -0,0 +1,10 @@ +#!/bin/bash +set -ex + +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$WORK_DIR/../../lib/common" + +RegisterQemuHandlers +for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + DownloadQemuStatic "${TARGET_ARCH}" +done diff --git a/tools/docker/core/hooks/push b/tools/docker/core/hooks/push new file mode 100644 index 000000000..4dc5ea080 --- /dev/null +++ b/tools/docker/core/hooks/push @@ -0,0 +1,11 @@ +#!/bin/bash +set -ex + +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$WORK_DIR/../../lib/common" + +CERTBOT_VERSION=$(GetCerbotVersionFromTag "$DOCKER_TAG") + +for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + PushDockerImage "${TARGET_ARCH}" "${CERTBOT_VERSION}" +done diff --git a/tools/docker/deploy.sh b/tools/docker/deploy.sh index e6960f67d..9ff4f52e5 100755 --- a/tools/docker/deploy.sh +++ b/tools/docker/deploy.sh @@ -18,14 +18,14 @@ Cleanup() { WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" CERTBOT_DOCKER_VERSION="$1" # Eg. v0.35.0 or v0.35.0-1 -CERTBOT_VERSION=$(sed -E -e 's|(v[0-9+]\.[0-9]+\.[0-9]+).*|\1|g' <<< $CERTBOT_DOCKER_VERSION) # Eg. v0.35.0 -BRANCH_NAME=$(sed -E -e 's|v(.*)\.[0-9]+|\1.x|g' <<< $CERTBOT_VERSION) # Eg. 0.35.x +CERTBOT_VERSION=$(sed -E -e 's|(v[0-9+]\.[0-9]+\.[0-9]+).*|\1|g' <<< "$CERTBOT_DOCKER_VERSION") # Eg. v0.35.0 +BRANCH_NAME=$(sed -E -e 's|v(.*)\.[0-9]+|\1.x|g' <<< "$CERTBOT_VERSION") # Eg. 0.35.x -sed -i -e "s|current-.*-blue\.svg|current-$CERTBOT_VERSION-blue.svg|g" core/README.md -sed -i -e "s|branch=.*)\]|branch=$BRANCH_NAME)]|g" core/README.md +sed -i -e "s|current-.*-blue\\.svg|current-$CERTBOT_VERSION-blue.svg|g" core/README.md +sed -i -e "s|branch=.*)\\]|branch=$BRANCH_NAME)]|g" core/README.md -sed -i -e "s|current-.*-blue\.svg|current-$CERTBOT_VERSION-blue.svg|g" plugin/README.md -sed -i -e "s|branch=.*)\]|branch=$BRANCH_NAME)]|g" plugin/README.md +sed -i -e "s|current-.*-blue\\.svg|current-$CERTBOT_VERSION-blue.svg|g" plugin/README.md +sed -i -e "s|branch=.*)\\]|branch=$BRANCH_NAME)]|g" plugin/README.md pushd "$WORK_DIR" git commit -a -m "Release version $CERTBOT_DOCKER_VERSION" --allow-empty diff --git a/tools/docker/lib/common b/tools/docker/lib/common new file mode 100644 index 000000000..35f473603 --- /dev/null +++ b/tools/docker/lib/common @@ -0,0 +1,142 @@ +#!/bin/bash +set -ex + +# Current supported architectures +export ALL_TARGET_ARCH=(amd64 arm32v6 arm64v8) + +# Architecture used in tags with no architecture especified (certbot/certbot:latest, certbot/cerbot:v0.35.0, ...) +export DEFAULT_ARCH=amd64 + +# Returns certbot version (ex. v0.35.0 returns 0.35.0) +# Usage: GetCerbotVersionFromTag +GetCerbotVersionFromTag() { + TAG=$1 + echo "${TAG//v/}" +} + +# Returns the translation from Docker to QEMU architecture +# Usage: GetQemuArch [amd64|arm32v6|arm64v8] +GetQemuArch() { + ARCH=$1 + + case "$ARCH" in + "amd64") + echo "x86_64" + ;; + "arm32v6") + echo "arm" + ;; + "arm64v8") + echo "aarch64" + ;; + "*") + echo "Not supported build architecture '$1'." >&2 + exit -1 + esac +} + +# Downloads QEMU static binary file for architecture +# Usage: DownloadQemuStatic [x86_64|arm|aarch64] +DownloadQemuStatic() { + ARCH=$1 + + QEMU_ARCH=$(GetQemuArch "$ARCH") + if [ ! -f "qemu-${QEMU_ARCH}-static" ]; then + QEMU_DOWNLOAD_URL="https://github.com/multiarch/qemu-user-static/releases/download" + QEMU_LATEST_TAG=$(curl -s https://api.github.com/repos/multiarch/qemu-user-static/tags \ + | grep 'name.*v[0-9]' \ + | head -n 1 \ + | cut -d '"' -f 4) + curl -SL "${QEMU_DOWNLOAD_URL}/${QEMU_LATEST_TAG}/x86_64_qemu-$QEMU_ARCH-static.tar.gz" \ + | tar xzv + fi +} + +# Executes the QEMU register script +# Usage: RegisterQemuHandlers +RegisterQemuHandlers() { + docker run --rm --privileged multiarch/qemu-user-static:register --reset +} + +# Builds docker certbot core image for a specific architecture and certbot version (ex. 0.35.0). +# Usage: BuildDockerCoreImage [amd64|arm32v6|arm64v8] +BuildDockerCoreImage() { + ARCH=$1 + VERSION=$2 + + QEMU=$(GetQemuArch "$ARCH") + docker build \ + --build-arg CERTBOT_VERSION="${VERSION}" \ + --build-arg TARGET_ARCH="${ARCH}" \ + --build-arg QEMU_ARCH="${QEMU}" \ + -f "${DOCKERFILE_PATH}" \ + -t "${DOCKER_REPO}:${ARCH}-v${VERSION}" \ + . +} + +# Builds docker certbot plugin image for a specific architecture and certbot version (ex. 0.35.0). +# Usage: BuildDockerPluginImage [amd64|arm32v6|arm64v8] +BuildDockerPluginImage() { + ARCH=$1 + VERSION=$2 + PLUGIN=$3 + + QEMU=$(GetQemuArch "$ARCH") + docker build \ + --build-arg CERTBOT_VERSION="${VERSION}" \ + --build-arg TARGET_ARCH="${ARCH}" \ + --build-arg QEMU_ARCH="${QEMU}" \ + --build-arg PLUGIN_NAME="${PLUGIN}" \ + -f "${DOCKERFILE_PATH}" \ + -t "${DOCKER_REPO}:${ARCH}-v${VERSION}" \ + . +} + +# Pushes docker image for a specific architecture and certbot version (ex. 0.35.0). +# Usage: BuildDockerCoreImage [amd64|arm32v6|arm64v8] +PushDockerImage() { + ARCH=$1 + VERSION=$2 + + docker push "${DOCKER_REPO}:${ARCH}-v${VERSION}" +} + +# Creates docker image "latest" tag for a specific architecture and certbot version. +# In case of default architecture, it also creates tags without architecture part. +# As an example, for version 0.35.0 in amd64 (default arquitecture): +# - certbot/certbot:v0.35.0 +# - certbot/certbot:latest +# - certbot/certbot:amd64-latest +# For version 0.35.0 in arm32v6: +# - certbot/certbot:arm32v6-latest +# Usage: TagDockerImageAliases [amd64|arm32v6|arm64v8] +TagDockerImageAliases() { + ARCH=$1 + VERSION=$2 + + docker tag "${DOCKER_REPO}:${ARCH}-v${VERSION}" "${DOCKER_REPO}:${ARCH}-latest" + if [ "${ARCH}" == "${DEFAULT_ARCH}" ]; then + docker tag "${DOCKER_REPO}:${ARCH}-v${VERSION}" "${DOCKER_REPO}:v${VERSION}" + docker tag "${DOCKER_REPO}:${ARCH}-v${VERSION}" "${DOCKER_REPO}:latest" + fi +} + +# Pushes docker "latest" image for a specific architecture and certbot version. +# In case of default architecture, it also pushes image without architecture part. +# As an example, for version 0.35.0 in amd64 (default arquitecture): +# - certbot/certbot:v0.35.0 +# - certbot/certbot:latest +# - certbot/certbot:amd64-latest +# For version 0.35.0 in arm32v6: +# - certbot/certbot:arm32v6-latest +# Usage: PushDockerImageAliases [amd64|arm32v6|arm64v8] +PushDockerImageAliases() { + ARCH=$1 + VERSION=$2 + + docker push "${DOCKER_REPO}:${ARCH}-latest" + if [ "${ARCH}" == "${DEFAULT_ARCH}" ]; then + docker push "${DOCKER_REPO}:v${VERSION}" + docker push "${DOCKER_REPO}:latest" + fi +} diff --git a/tools/docker/plugin/.gitignore b/tools/docker/plugin/.gitignore new file mode 100644 index 000000000..4cc493afa --- /dev/null +++ b/tools/docker/plugin/.gitignore @@ -0,0 +1 @@ +qemu-*-static diff --git a/tools/docker/plugin/Dockerfile b/tools/docker/plugin/Dockerfile index f249a64e6..9369ba0d3 100644 --- a/tools/docker/plugin/Dockerfile +++ b/tools/docker/plugin/Dockerfile @@ -1,4 +1,12 @@ -FROM certbot/certbot +# Docker Arch (amd64, arm32v6, ...) +ARG TARGET_ARCH +ARG CERTBOT_VERSION +FROM certbot/certbot:${TARGET_ARCH}-v${CERTBOT_VERSION} + +# Qemu Arch (x86_64, arm, ...) +ARG QEMU_ARCH +ENV QEMU_ARCH=${QEMU_ARCH} +COPY qemu-${QEMU_ARCH}-static /usr/bin/ ARG PLUGIN_NAME @@ -6,7 +14,7 @@ ARG PLUGIN_NAME RUN wget -O certbot-${CERTBOT_VERSION}.tar.gz https://github.com/certbot/certbot/archive/v${CERTBOT_VERSION}.tar.gz \ && tar xf certbot-${CERTBOT_VERSION}.tar.gz \ && cp -r certbot-${CERTBOT_VERSION}/certbot-${PLUGIN_NAME} /opt/certbot/src/certbot-${PLUGIN_NAME} \ - && rm -rf certbot-${CERTBOT_VERSION}.tar.gz certbot-${CERTBOT_VERSION} + && rm -rf certbot-${CERTBOT_VERSION}.tar.gz certbot-${CERTBOT_VERSION} # Install the DNS plugin RUN pip install --constraint /opt/certbot/docker_constraints.txt --no-cache-dir --editable /opt/certbot/src/certbot-${PLUGIN_NAME} diff --git a/tools/docker/plugin/hooks/build b/tools/docker/plugin/hooks/build index 435b1d42b..4545bbb3a 100644 --- a/tools/docker/plugin/hooks/build +++ b/tools/docker/plugin/hooks/build @@ -1,5 +1,12 @@ #!/bin/bash set -ex +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$WORK_DIR/../../lib/common" + +CERTBOT_VERSION=$(GetCerbotVersionFromTag "$DOCKER_TAG") PLUGIN_NAME=${DOCKER_REPO//*\//} -docker build --build-arg PLUGIN_NAME=${PLUGIN_NAME} -f ${DOCKERFILE_PATH} -t ${IMAGE_NAME} . + +for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + BuildDockerPluginImage "${TARGET_ARCH}" "${CERTBOT_VERSION}" "${PLUGIN_NAME}" +done diff --git a/tools/docker/plugin/hooks/post_push b/tools/docker/plugin/hooks/post_push index 6b8459521..6bac191fd 100644 --- a/tools/docker/plugin/hooks/post_push +++ b/tools/docker/plugin/hooks/post_push @@ -1,5 +1,12 @@ #!/bin/bash set -ex -docker tag ${IMAGE_NAME} "${DOCKER_REPO}:latest" -docker push "${DOCKER_REPO}:latest" +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$WORK_DIR/../../lib/common" + +CERTBOT_VERSION=$(GetCerbotVersionFromTag "$DOCKER_TAG") + +for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + TagDockerImageAliases "${TARGET_ARCH}" "${CERTBOT_VERSION}" + PushDockerImageAliases "${TARGET_ARCH}" "${CERTBOT_VERSION}" +done diff --git a/tools/docker/plugin/hooks/pre_build b/tools/docker/plugin/hooks/pre_build index 22c582758..723e35161 100644 --- a/tools/docker/plugin/hooks/pre_build +++ b/tools/docker/plugin/hooks/pre_build @@ -1,5 +1,10 @@ #!/bin/bash set -ex -CERTBOT_VERSION=${DOCKER_TAG//v/} -docker build --build-arg CERTBOT_VERSION=${CERTBOT_VERSION} -f ../core/Dockerfile -t certbot/certbot ../core +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$WORK_DIR/../../lib/common" + +RegisterQemuHandlers +for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + DownloadQemuStatic "${TARGET_ARCH}" +done diff --git a/tools/docker/plugin/hooks/push b/tools/docker/plugin/hooks/push new file mode 100644 index 000000000..4dc5ea080 --- /dev/null +++ b/tools/docker/plugin/hooks/push @@ -0,0 +1,11 @@ +#!/bin/bash +set -ex + +WORK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +source "$WORK_DIR/../../lib/common" + +CERTBOT_VERSION=$(GetCerbotVersionFromTag "$DOCKER_TAG") + +for TARGET_ARCH in "${ALL_TARGET_ARCH[@]}"; do + PushDockerImage "${TARGET_ARCH}" "${CERTBOT_VERSION}" +done