From f1f495381c04387fd9f0ee4685cba0b597b4f156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Dachary?= Date: Sat, 17 Dec 2022 21:45:28 +0100 Subject: [PATCH] [CI] rootless container images Refs: https://codeberg.org/forgejo/forgejo/issues/25 --- .woodpecker/docker-release-version.yml | 59 +++--- .woodpecker/releases-helper.yml | 24 +++ releases/Dockerfile | 3 + releases/Dockerfile-rootless | 3 + releases/container-images-pull-verify-push.sh | 184 ++++++++++++++++++ 5 files changed, 246 insertions(+), 27 deletions(-) create mode 100644 .woodpecker/releases-helper.yml create mode 100644 releases/Dockerfile create mode 100644 releases/Dockerfile-rootless create mode 100755 releases/container-images-pull-verify-push.sh diff --git a/.woodpecker/docker-release-version.yml b/.woodpecker/docker-release-version.yml index 60ca56a81d..e4a61c345c 100644 --- a/.woodpecker/docker-release-version.yml +++ b/.woodpecker/docker-release-version.yml @@ -11,6 +11,13 @@ variables: - &dind_image 'docker:20.10-dind' - &buildx_image 'woodpeckerci/plugin-docker-buildx:2.0.0' - &integration_image 'codeberg.org/forgejo-integration/forgejo' + - &dockerfile_root 'Dockerfile' +# - &dockerfile_root 'releases/Dockerfile' + - &dockerfile_rootless 'Dockerfile.rootless' +# - &dockerfile_rootless 'releases/Dockerfile-rootless' + - &verify 'true' +# - &verify 'false' + - &archs 'amd64 arm64' pipeline: fetch-tags: @@ -20,17 +27,37 @@ pipeline: - git config --add safe.directory '*' - git fetch --tags --force - publish-integration: + publish-root: image: *buildx_image + group: integration pull: true settings: platforms: linux/amd64,linux/arm64 + dockerfile: *dockerfile_root registry: from_secret: domain tag: ${CI_COMMIT_TAG##v} repo: *integration_image build_args: - - GOPROXY=https://proxy.golang.org,direct + - GOPROXY=https://proxy.golang.org + password: + from_secret: releaseteamtoken + username: + from_secret: releaseteamuser + + publish-rootless: + image: *buildx_image + group: integration + pull: true + settings: + platforms: linux/amd64,linux/arm64 + dockerfile: *dockerfile_rootless + registry: + from_secret: domain + tag: ${CI_COMMIT_TAG##v}-rootless + repo: *integration_image + build_args: + - GOPROXY=https://proxy.golang.org password: from_secret: releaseteamtoken username: @@ -40,32 +67,10 @@ pipeline: image: *dind_image environment: INTEGRATION_IMAGE: *integration_image + VERIFY: *verify + ARCHS: *archs commands: - - apk --update --no-cache add coredns - - ( echo ".:53 {" ; echo " forward . /etc/resolv.conf"; echo "}" ) > /etc/coredns/Corefile - - coredns -conf /etc/coredns/Corefile & - - /usr/local/bin/dockerd --data-root /var/lib/docker --host=unix:///var/run/docker.sock --dns 172.17.0.3 & - - for i in $$(seq 60) ; do DOCKER_HOST=unix:///var/run/docker.sock docker version && break ; sleep 1 ; done - - tag=${CI_COMMIT_TAG##v} - - docker login -p "$RELEASETEAMTOKEN" -u "$RELEASETEAMUSER" $DOMAIN - - tagged_image=$${INTEGRATION_IMAGE}':'$$tag - - manifests="" - - for arch in arm64 amd64 ; do - - arch_image=${CI_REPO_LINK##https://}':'$$tag-$$arch - - docker pull --platform linux/$$arch $$tagged_image - - docker run --platform linux/$$arch --rm $$tagged_image gitea --version | grep 'built with' - - docker tag $$tagged_image $$arch_image - - docker push $$arch_image - - manifests="$$manifests $$arch_image" - - docker image prune --all --force - - done - - published=${CI_REPO_LINK##https://}':'$$tag - - docker manifest create $$published $$manifests - - docker manifest push $$published - - short_tag=$${tag%.*-*} - - short_published=${CI_REPO_LINK##https://}':'$$short_tag - - docker manifest create $$short_published $$manifests - - docker manifest push $$short_published + - ./releases/container-images-pull-verify-push.sh secrets: - releaseteamtoken - releaseteamuser diff --git a/.woodpecker/releases-helper.yml b/.woodpecker/releases-helper.yml new file mode 100644 index 0000000000..6d74d23ba5 --- /dev/null +++ b/.woodpecker/releases-helper.yml @@ -0,0 +1,24 @@ +platform: linux/amd64 + +branches: + includes: [ wip-ci-* ] + +when: + event: push + +variables: + - &dind_image 'docker:20.10-dind' + +pipeline: + container-images-pull-verify-push: + image: *dind_image + commands: +# arm64 would require qemu-user-static which is not available on alpline +# the test coverage does not change much and running the tests test locally +# is possible if there is a doubt + - ARCHS=amd64 ./releases/container-images-pull-verify-push.sh test + - ./releases/container-images-pull-verify-push.sh test_teardown + secrets: + - releaseteamuser + - releaseteamtoken + - domain diff --git a/releases/Dockerfile b/releases/Dockerfile new file mode 100644 index 0000000000..bef4e4f6de --- /dev/null +++ b/releases/Dockerfile @@ -0,0 +1,3 @@ +FROM alpine:3.17 + +RUN echo root > state diff --git a/releases/Dockerfile-rootless b/releases/Dockerfile-rootless new file mode 100644 index 0000000000..561b67e9a8 --- /dev/null +++ b/releases/Dockerfile-rootless @@ -0,0 +1,3 @@ +FROM alpine:3.17 + +RUN echo rootless > state diff --git a/releases/container-images-pull-verify-push.sh b/releases/container-images-pull-verify-push.sh new file mode 100755 index 0000000000..33c333dc32 --- /dev/null +++ b/releases/container-images-pull-verify-push.sh @@ -0,0 +1,184 @@ +#!/bin/sh + +# +# Tests are run when on a wip-ci-* branch, see .woodpecker/releases-helper.yml +# It should be changed to run it every time this file is changed when 1.18 is used because 1.17 does not have +# webhooks with the information for that filtering. +# + +set -ex + +: ${DOCKER_HOST:=unix:///var/run/docker.sock} +: ${ARCHS:=amd64 arm64} +: ${INTEGRATION_USER:=forgejo-integration} +: ${INTEGRATION_IMAGE:=codeberg.org/$INTEGRATION_USER/forgejo} +: ${CI_REPO_OWNER:=dachary} +: ${CI_COMMIT_TAG:=v17.1.42-2} +: ${TAG:=${CI_COMMIT_TAG##v}} +: ${SHORT_TAG=${TAG%.*-*}} +: ${CI_REPO_LINK:=https://codeberg.org/dachary/forgejo} +: ${DOMAIN:=codeberg.org} + +: ${VERIFY:=true} +VERIFY_COMMAND='gitea --version' +VERIFY_STRING='built with' + +publish() { + for suffix in '' '-rootless' ; do + images="" + for arch in $ARCHS ; do + # + # Get the image from the integration user + # + image=$(image_name $INTEGRATION_USER $suffix) + docker pull --platform linux/$arch $image + # + # Verify it is usable + # + if $VERIFY ; then + docker run --platform linux/$arch --rm $image $VERIFY_COMMAND | grep "$VERIFY_STRING" + fi + # + # Push the image with a tag reflecting the architecture to the repo owner + # + arch_image=$(arch_image_name $CI_REPO_OWNER $arch $suffix) + docker tag $image $arch_image + docker push $arch_image + images="$images $arch_image" + done + + # + # Push a manifest with all the architectures to the repo owner + # + manifest=$(image_name $CI_REPO_OWNER $suffix) + docker manifest rm $manifest || true + docker manifest create $manifest $images + image_put $CI_REPO_OWNER $(image_tag $suffix) $manifest + image_put $CI_REPO_OWNER $(short_image_tag $suffix) $manifest + # + # Sanity check to ensure the manifest that are published can actualy + # be used. + # + for arch in $ARCHS ; do + docker pull --platform linux/$arch $(image_name $CI_REPO_OWNER $suffix) + docker pull --platform linux/$arch $(short_image_name $CI_REPO_OWNER $suffix) + done + done +} + +boot() { + if docker version ; then + return + fi + apk --update --no-cache add coredns jq curl + ( echo ".:53 {" ; echo " forward . /etc/resolv.conf"; echo "}" ) > /etc/coredns/Corefile + coredns -conf /etc/coredns/Corefile & + /usr/local/bin/dockerd --data-root /var/lib/docker --host=$DOCKER_HOST --dns 172.17.0.3 & + for i in $(seq 60) ; do + docker version && break + sleep 1 + done + docker version || exit 1 +} + +authenticate() { + echo "$RELEASETEAMTOKEN" | docker login --password-stdin --username "$RELEASETEAMUSER" $DOMAIN + token=$(curl -u$RELEASETEAMUSER:$RELEASETEAMTOKEN -sS https://$DOMAIN/v2/token | jq --raw-output .token) +} + +image_delete() { + curl -sS -H "Authorization: token $token" -X DELETE https://$DOMAIN/v2/$1/forgejo/manifests/$2 +} + +image_put() { + docker manifest inspect $3 > /tmp/manifest.json + curl -sS -H "Authorization: token $token" -X PUT --data-binary @/tmp/manifest.json https://$DOMAIN/v2/$1/forgejo/manifests/$2 +} + +main() { + boot + authenticate + publish +} + +image_name() { + echo $DOMAIN/$1/forgejo:$(image_tag $2) +} + +image_tag() { + echo $TAG$1 +} + +short_image_name() { + echo $DOMAIN/$1/forgejo:$(short_image_tag $2) +} + +short_image_tag() { + echo $SHORT_TAG$1 +} + +arch_image_name() { + echo $DOMAIN/$1/forgejo:$(arch_image_tag $2 $3) +} + +arch_image_tag() { + echo $TAG-$1$2 +} + +# +# Create the same set of images that buildx would +# +test_setup() { + dir=$(dirname $0) + + for suffix in '' '-rootless' ; do + ( + cd $dir + manifests="" + for arch in $ARCHS ; do + image=$(arch_image_name $INTEGRATION_USER $arch $suffix) + docker build -f Dockerfile$suffix --platform linux/$arch -t $image . + docker push $image + images="$images $image" + done + manifest=$(image_name $INTEGRATION_USER $suffix) + docker manifest rm $manifest || true + docker manifest create $manifest $images + image_put $INTEGRATION_USER $(image_tag $suffix) $manifest + ) + done +} + +test_teardown() { + authenticate + for suffix in '' '-rootless' ; do + image_delete $INTEGRATION_USER $(image_tag $suffix) + image_delete $CI_REPO_OWNER $(image_tag $suffix) + image_delete $CI_REPO_OWNER $(short_image_tag $suffix) + for arch in $ARCHS ; do + image_delete $INTEGRATION_USER $(arch_image_tag $arch $suffix) + image_delete $CI_REPO_OWNER $(arch_image_tag $arch $suffix) + done + done +} + +# +# Running the test locally instead of withing Woodpecker +# +# 1. Setup: obtain a token at https://codeberg.org/user/settings/applications +# 2. Run: RELEASETEAMUSER= RELEASETEAMTOKEn= container-images-pull-verify-push.sh test +# 3. Verify: (optional) manual verification at https://codeberg.org//-/packages/container/forgejo/versions +# 4. Cleanup: RELEASETEAMUSER= RELEASETEAMTOKEn= container-images-pull-verify-push.sh test_teardown +# +test() { + boot + test_teardown + test_setup + VERIFY_STRING=something + VERIFY_COMMAND="echo $VERIFY_STRING" + echo "================================ TEST BEGIN" + main + echo "================================ TEST END" +} + +${@:-main}