From e816a2e563262c4ebce83727189b491946287523 Mon Sep 17 00:00:00 2001 From: Amir Zarrinkafsh Date: Tue, 30 Mar 2021 09:17:19 +1100 Subject: [PATCH] ci: publish docker images to ghcr (#1860) * ci: publish docker images to ghcr * ci: remove ghcr images with no tags * ci: remove unnecessary ghcr jq args for empty tags * ci: move ghcr empty tag clean up Publishes Docker container images on both DockerHub and GitHub Container Registry. --- .buildkite/hooks/post-command | 11 ++ .buildkite/steps/ghartifacts.sh | 2 +- .github/probot.js | 10 +- .golangci.yml | 2 - cmd/authelia-scripts/cmd_docker.go | 163 +++++++++++++++++------------ cmd/authelia-scripts/const.go | 5 + cmd/authelia-scripts/docker.go | 4 +- 7 files changed, 119 insertions(+), 78 deletions(-) diff --git a/.buildkite/hooks/post-command b/.buildkite/hooks/post-command index f83bc9ad..e03fdab9 100755 --- a/.buildkite/hooks/post-command +++ b/.buildkite/hooks/post-command @@ -37,6 +37,7 @@ fi if [[ $BUILDKITE_LABEL =~ ":docker: Deploy" ]]; then docker logout + docker logout ghcr.io fi if [[ $BUILDKITE_LABEL == ":docker: Deploy Manifests" ]] && [[ $BUILDKITE_BRANCH == "master" ]] && [[ $BUILDKITE_PULL_REQUEST == "false" ]]; then @@ -49,6 +50,9 @@ if [[ $BUILDKITE_LABEL == ":docker: Deploy Manifests" ]] && [[ $BUILDKITE_BRANCH comm -23 <(echo "${dockerbranchtags}") <(echo "${githubbranches}")); do echo "Removing tag ${BRANCH_TAG}" curl -fsL --retry 3 -o /dev/null -X "DELETE" -H "Authorization: JWT ${authtoken}" https://hub.docker.com/v2/repositories/authelia/authelia/tags/${BRANCH_TAG}/ + for GHCR_VERSION in $(curl -fsL --retry 3 -H "Authorization: Bearer ${GHCR_PASSWORD}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/orgs/authelia/packages/container/authelia/versions | jq --arg tag ${BRANCH_TAG} '.[] | select(.metadata.container.tags[] | contains($tag)) | .id'); do + curl -fsL --retry 3 -o /dev/null -X "DELETE" -H "Authorization: Bearer ${GHCR_PASSWORD}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/orgs/authelia/packages/container/authelia/versions/${GHCR_VERSION} + done done echo "--- :docker: Removing tags for merged or closed pull requests" for PR_TAG in $(dockerprtags=$(curl -fsL --retry 3 -H "Authorization: Bearer ${anontoken}" https://registry-1.docker.io/v2/authelia/authelia/tags/list | jq -r '.tags[] | select(startswith("PR"))' | \ @@ -57,5 +61,12 @@ if [[ $BUILDKITE_LABEL == ":docker: Deploy Manifests" ]] && [[ $BUILDKITE_BRANCH comm -23 <(echo "${dockerprtags}") <(echo "${githubprs}")); do echo "Removing tag ${PR_TAG}" curl -fsL --retry 3 -o /dev/null -X "DELETE" -H "Authorization: JWT ${authtoken}" https://hub.docker.com/v2/repositories/authelia/authelia/tags/${PR_TAG}/ + for GHCR_VERSION in $(curl -fsL --retry 3 -H "Authorization: Bearer ${GHCR_PASSWORD}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/orgs/authelia/packages/container/authelia/versions | jq --arg tag ${PR_TAG} '.[] | select(.metadata.container.tags[] | contains($tag)) | .id'); do + curl -fsL --retry 3 -o /dev/null -X "DELETE" -H "Authorization: Bearer ${GHCR_PASSWORD}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/orgs/authelia/packages/container/authelia/versions/${GHCR_VERSION} + done + done + echo "--- :docker: Removing empty tags for old images on GHCR" + for GHCR_VERSION in $(curl -fsL --retry 3 -H "Authorization: Bearer ${GHCR_PASSWORD}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/orgs/authelia/packages/container/authelia/versions | jq '.[] | select((.metadata.container.tags|length) == 0) | .id'); do + curl -fsL --retry 3 -o /dev/null -X "DELETE" -H "Authorization: Bearer ${GHCR_PASSWORD}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/orgs/authelia/packages/container/authelia/versions/${GHCR_VERSION} done fi \ No newline at end of file diff --git a/.buildkite/steps/ghartifacts.sh b/.buildkite/steps/ghartifacts.sh index 1f3d4407..5b93eeba 100755 --- a/.buildkite/steps/ghartifacts.sh +++ b/.buildkite/steps/ghartifacts.sh @@ -13,7 +13,7 @@ do done echo "--- :github: Deploy artifacts for release: ${BUILDKITE_TAG}" -hub release create "${BUILDKITE_TAG}" "${artifacts[@]}" -F <(echo -e "${BUILDKITE_TAG}\n$(conventional-changelog -p angular -o /dev/stdout -r 2 | sed -e '1,3d')\n\n### Docker Container\n* \`docker pull authelia/authelia:${BUILDKITE_TAG//v}\`"); EXIT=$? +hub release create "${BUILDKITE_TAG}" "${artifacts[@]}" -F <(echo -e "${BUILDKITE_TAG}\n$(conventional-changelog -p angular -o /dev/stdout -r 2 | sed -e '1,3d')\n\n### Docker Container\n* \`docker pull authelia/authelia:${BUILDKITE_TAG//v}\`\n* \`docker pull ghcr.io/authelia/authelia:${BUILDKITE_TAG//v}\`"); EXIT=$? if [[ $EXIT == 0 ]]; then diff --git a/.github/probot.js b/.github/probot.js index 8f49a507..7caf6aab 100644 --- a/.github/probot.js +++ b/.github/probot.js @@ -13,10 +13,11 @@ on('pull_request.opened') context.payload.pull_request.head.ref.slice(0, 9) !== 'renovate/' ) .comment(`## Artifacts -These changes are published for testing on Buildkite and DockerHub. +These changes are published for testing on Buildkite, DockerHub and GitHub Container Registry. ### Docker Container -* \`docker pull authelia/authelia:{{ pull_request.head.ref }}\``) +* \`docker pull authelia/authelia:{{ pull_request.head.ref }}\` +* \`docker pull ghcr.io/authelia/authelia:{{ pull_request.head.ref }}\``) // PR commentary for third party based contributions on('pull_request.opened') @@ -29,7 +30,8 @@ on('pull_request.opened') You are free to apply the changes if you're comfortable, alternatively you are welcome to ask a team member for advice. ## Artifacts -These changes once approved by a team member will be published for testing on Buildkite and DockerHub. +These changes once approved by a team member will be published for testing on Buildkite, DockerHub and GitHub Container Registry. ### Docker Container -* \`docker pull authelia/authelia:PR{{ pull_request.number }}\``) \ No newline at end of file +* \`docker pull authelia/authelia:PR{{ pull_request.number }}\` +* \`docker pull ghcr.io/authelia/authelia:PR{{ pull_request.number }}\``) \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 25132395..ec2af8df 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -23,8 +23,6 @@ linters: - goimports - golint - gosec - - interfacer - - maligned - misspell - nolintlint - prealloc diff --git a/cmd/authelia-scripts/cmd_docker.go b/cmd/authelia-scripts/cmd_docker.go index 2960401c..63209d1c 100644 --- a/cmd/authelia-scripts/cmd_docker.go +++ b/cmd/authelia-scripts/cmd_docker.go @@ -23,7 +23,7 @@ var ciPullRequest = os.Getenv("BUILDKITE_PULL_REQUEST") var ciTag = os.Getenv("BUILDKITE_TAG") var dockerTags = regexp.MustCompile(`v(?P(?P(?P\d+)\.\d+)\.\d+.*)`) var ignoredSuffixes = regexp.MustCompile("alpha|beta") -var publicRepo = regexp.MustCompile(`.*\:.*`) +var publicRepo = regexp.MustCompile(`.*:.*`) var tags = dockerTags.FindStringSubmatch(ciTag) func init() { @@ -144,30 +144,39 @@ var DockerManifestCmd = &cobra.Command{ }, } -func login(docker *Docker) { - username := os.Getenv("DOCKER_USERNAME") - password := os.Getenv("DOCKER_PASSWORD") +func login(docker *Docker, registry string) { + username := "" + password := "" + + switch registry { + case dockerhub: + username = os.Getenv("DOCKER_USERNAME") + password = os.Getenv("DOCKER_PASSWORD") + case ghcr: + username = os.Getenv("GHCR_USERNAME") + password = os.Getenv("GHCR_PASSWORD") + } if username == "" { - log.Fatal(errors.New("DOCKER_USERNAME is empty")) + log.Fatal(errors.New("DOCKER_USERNAME/GHCR_USERNAME is empty")) } if password == "" { - log.Fatal(errors.New("DOCKER_PASSWORD is empty")) + log.Fatal(errors.New("DOCKER_PASSWORD/GHCR_PASSWORD is empty")) } - log.Infof("Login to Docker Hub as %s", username) - err := docker.Login(username, password) + log.Infof("Login to %s as %s", registry, username) + err := docker.Login(username, password, registry) if err != nil { - log.Fatal("Login to Docker Hub failed", err) + log.Fatalf("Login to %s failed: %s", registry, err) } } -func deploy(docker *Docker, tag string) { - imageWithTag := DockerImageName + ":" + tag +func deploy(docker *Docker, tag, registry string) { + imageWithTag := registry + "/" + DockerImageName + ":" + tag - log.Infof("Docker image %s will be deployed on Docker Hub", imageWithTag) + log.Infof("Docker image %s will be deployed on %s", imageWithTag, registry) if err := docker.Tag(DockerImageName, imageWithTag); err != nil { log.Fatal(err) @@ -178,10 +187,10 @@ func deploy(docker *Docker, tag string) { } } -func deployManifest(docker *Docker, tag string, amd64tag string, arm32v7tag string, arm64v8tag string) { - dockerImagePrefix := DockerImageName + ":" +func deployManifest(docker *Docker, tag, amd64tag, arm32v7tag, arm64v8tag, registry string) { + dockerImagePrefix := registry + "/" + DockerImageName + ":" - log.Infof("Docker manifest %s%s will be deployed on Docker Hub", dockerImagePrefix, tag) + log.Infof("Docker manifest %s%s will be deployed on %s", dockerImagePrefix, tag, registry) err := docker.Manifest(dockerImagePrefix+tag, dockerImagePrefix+amd64tag, dockerImagePrefix+arm32v7tag, dockerImagePrefix+arm64v8tag) @@ -190,11 +199,14 @@ func deployManifest(docker *Docker, tag string, amd64tag string, arm32v7tag stri } tags := []string{amd64tag, arm32v7tag, arm64v8tag} - for _, t := range tags { - log.Infof("Docker removing tag for %s%s on Docker Hub", dockerImagePrefix, t) - if err := docker.CleanTag(t); err != nil { - panic(err) + if registry == dockerhub { + for _, t := range tags { + log.Infof("Docker removing tag for %s%s on Docker Hub", dockerImagePrefix, t) + + if err := docker.CleanTag(t); err != nil { + panic(err) + } } } } @@ -202,68 +214,81 @@ func deployManifest(docker *Docker, tag string, amd64tag string, arm32v7tag stri func publishDockerImage(arch string) { docker := &Docker{} - switch { - case ciTag != "": - if len(tags) == 4 { - log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) - login(docker) - deploy(docker, tags[1]+"-"+arch) + for _, registry := range registries { + switch { + case ciTag != "": + if len(tags) == 4 { + log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) + login(docker, registry) + deploy(docker, tags[1]+"-"+arch, registry) - if !ignoredSuffixes.MatchString(ciTag) { - deploy(docker, tags[2]+"-"+arch) - deploy(docker, tags[3]+"-"+arch) - deploy(docker, "latest-"+arch) + if !ignoredSuffixes.MatchString(ciTag) { + deploy(docker, tags[2]+"-"+arch, registry) + deploy(docker, tags[3]+"-"+arch, registry) + deploy(docker, "latest-"+arch, registry) + } + } else { + log.Fatal("Docker image will not be published, the specified tag does not conform to the standard") } - } else { - log.Fatal("Docker image will not be published, the specified tag does not conform to the standard") + case ciBranch != masterTag && !publicRepo.MatchString(ciBranch): + login(docker, registry) + deploy(docker, ciBranch+"-"+arch, registry) + case ciBranch != masterTag && publicRepo.MatchString(ciBranch): + login(docker, registry) + deploy(docker, "PR"+ciPullRequest+"-"+arch, registry) + case ciBranch == masterTag && ciPullRequest == stringFalse: + login(docker, registry) + deploy(docker, "master-"+arch, registry) + default: + log.Info("Docker image will not be published") } - case ciBranch != masterTag && !publicRepo.MatchString(ciBranch): - login(docker) - deploy(docker, ciBranch+"-"+arch) - case ciBranch != masterTag && publicRepo.MatchString(ciBranch): - login(docker) - deploy(docker, "PR"+ciPullRequest+"-"+arch) - case ciBranch == masterTag && ciPullRequest == stringFalse: - login(docker) - deploy(docker, "master-"+arch) - default: - log.Info("Docker image will not be published") } } func publishDockerManifest() { docker := &Docker{} - switch { - case ciTag != "": - if len(tags) == 4 { - log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) - login(docker) - deployManifest(docker, tags[1], tags[1]+"-amd64", tags[1]+"-arm32v7", tags[1]+"-arm64v8") - publishDockerReadme(docker) + for _, registry := range registries { + switch { + case ciTag != "": + if len(tags) == 4 { + log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) + login(docker, registry) + deployManifest(docker, tags[1], tags[1]+"-amd64", tags[1]+"-arm32v7", tags[1]+"-arm64v8", registry) - if !ignoredSuffixes.MatchString(ciTag) { - deployManifest(docker, tags[2], tags[2]+"-amd64", tags[2]+"-arm32v7", tags[2]+"-arm64v8") - deployManifest(docker, tags[3], tags[3]+"-amd64", tags[3]+"-arm32v7", tags[3]+"-arm64v8") - deployManifest(docker, "latest", "latest-amd64", "latest-arm32v7", "latest-arm64v8") - publishDockerReadme(docker) - updateMicroBadger(docker) + if registry == dockerhub { + publishDockerReadme(docker) + } + + if !ignoredSuffixes.MatchString(ciTag) { + deployManifest(docker, tags[2], tags[2]+"-amd64", tags[2]+"-arm32v7", tags[2]+"-arm64v8", registry) + deployManifest(docker, tags[3], tags[3]+"-amd64", tags[3]+"-arm32v7", tags[3]+"-arm64v8", registry) + deployManifest(docker, "latest", "latest-amd64", "latest-arm32v7", "latest-arm64v8", registry) + + if registry == dockerhub { + publishDockerReadme(docker) + updateMicroBadger(docker) + } + } + } else { + log.Fatal("Docker manifest will not be published, the specified tag does not conform to the standard") } - } else { - log.Fatal("Docker manifest will not be published, the specified tag does not conform to the standard") + case ciBranch != masterTag && !publicRepo.MatchString(ciBranch): + login(docker, registry) + deployManifest(docker, ciBranch, ciBranch+"-amd64", ciBranch+"-arm32v7", ciBranch+"-arm64v8", registry) + case ciBranch != masterTag && publicRepo.MatchString(ciBranch): + login(docker, registry) + deployManifest(docker, "PR"+ciPullRequest, "PR"+ciPullRequest+"-amd64", "PR"+ciPullRequest+"-arm32v7", "PR"+ciPullRequest+"-arm64v8", registry) + case ciBranch == masterTag && ciPullRequest == stringFalse: + login(docker, registry) + deployManifest(docker, "master", "master-amd64", "master-arm32v7", "master-arm64v8", registry) + + if registry == dockerhub { + publishDockerReadme(docker) + } + default: + log.Info("Docker manifest will not be published") } - case ciBranch != masterTag && !publicRepo.MatchString(ciBranch): - login(docker) - deployManifest(docker, ciBranch, ciBranch+"-amd64", ciBranch+"-arm32v7", ciBranch+"-arm64v8") - case ciBranch != masterTag && publicRepo.MatchString(ciBranch): - login(docker) - deployManifest(docker, "PR"+ciPullRequest, "PR"+ciPullRequest+"-amd64", "PR"+ciPullRequest+"-arm32v7", "PR"+ciPullRequest+"-arm64v8") - case ciBranch == masterTag && ciPullRequest == stringFalse: - login(docker) - deployManifest(docker, "master", "master-amd64", "master-arm32v7", "master-arm64v8") - publishDockerReadme(docker) - default: - log.Info("Docker manifest will not be published") } } diff --git a/cmd/authelia-scripts/const.go b/cmd/authelia-scripts/const.go index efb83cde..87b2ed99 100644 --- a/cmd/authelia-scripts/const.go +++ b/cmd/authelia-scripts/const.go @@ -9,6 +9,11 @@ var DockerImageName = "authelia/authelia" // IntermediateDockerImageName local name of the docker image. var IntermediateDockerImageName = "authelia:dist" +var registries = []string{"docker.io", "ghcr.io"} + +const dockerhub = "docker.io" +const ghcr = "ghcr.io" + const masterTag = "master" const stringFalse = "false" const stringTrue = "true" diff --git a/cmd/authelia-scripts/docker.go b/cmd/authelia-scripts/docker.go index 508ed3aa..10fcc25a 100644 --- a/cmd/authelia-scripts/docker.go +++ b/cmd/authelia-scripts/docker.go @@ -20,8 +20,8 @@ func (d *Docker) Tag(image, tag string) error { } // Login login to the dockerhub registry. -func (d *Docker) Login(username, password string) error { - return utils.CommandWithStdout("docker", "login", "-u", username, "-p", password).Run() +func (d *Docker) Login(username, password, registry string) error { + return utils.CommandWithStdout("docker", "login", registry, "-u", username, "-p", password).Run() } // Push push a docker image to dockerhub.