mirror of
https://github.com/authelia/authelia.git
synced 2026-10-28 09:34:52 +08:00
ci: normalise branch names into container tags (#13249)
We are moving branches in this repository to the `<type>/<summary>` convention, which the container registries cannot take verbatim: a tag admits only `[A-Za-z0-9_.-]`, so pushing `authelia/authelia:fix/oidc-claims` fails with `invalid reference format` on both DockerHub and GHCR. The same branch name reaches `authelia/integration-duo` and friends through `resolve_tag_suffix`, and the `sed` in the `INTEGRATION` hook additionally breaks on the `/` since it is that expression's own delimiter. Branch names are now folded into a tag by replacing anything outside the permitted set with a `-`, so `fix/oidc-claims` publishes as `fix-oidc-claims`. The mapping is implemented once per language that needs it and each copy points at the other: `sanitize_tag` in `.buildkite/libs/common.sh` covers `integration.sh`, `grypescans.sh` and the `INTEGRATION` hook, and `sanitizeTag` in `cmd/authelia-scripts/cmd/docker.go` covers the manifests we deploy. The announcement in `.github/probot.js` keeps naming the tag it always did, with a filter folding `head.ref` before the template renders it, since the body is a mustache template and mustache cannot transform a value itself. The tag reaper needs the mapping too, and silently. It deletes every published tag with no branch of the same name, so once the tags are folded but the branch names it compares them against are not, every branch carrying a `/` looks abandoned and loses its images on the next `master` build. The branch list is now folded the same way before the comparison, and sorted afterwards since folding changes the collation order `comm` depends on. While in that function, the GHCR lookup is matched with `index` rather than `contains`. `contains` is a substring test, so reaping `fix-oidc-claims` also deleted `fix-oidc-claims-policy`; prefixed branch names make shared prefixes considerably more likely. It also emits a version once per tag that matched, where `index` emits it once. Renovate no longer needs `branchPrefix`, which only existed because `renovate/` could not be used as a tag, so it returns to its default and `branchPrefixOld` retires the branches left on the old prefix. The collapse guard in `resolve_tag_suffix` moves with it. Signed-off-by: Amir Zarrinkafsh <3339418+nightah@users.noreply.github.com>
This commit is contained in:
1 parent
9e0d77d0a7
commit
c11a808fc4
6 files changed
+65
-7
No files matched your search
@@ -6,6 +6,9 @@
|
||||
|
||||
set +eu
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/../libs/common.sh"
|
||||
|
||||
cleanup_tags() {
|
||||
local docker_list="${1}" github_list="${2}"
|
||||
local tag i ghcr_version
|
||||
@@ -14,7 +17,7 @@ cleanup_tags() {
|
||||
echo "Removing tag ${tag} from docker.io"
|
||||
curl -fsL --retry 3 -o /dev/null -X DELETE -H "Authorization: JWT ${authtoken}" "https://hub.docker.com/v2/repositories/authelia/authelia/tags/${tag}/"
|
||||
for i in {1..5}; do
|
||||
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?page=${i}&per_page=100" | jq -j --arg tag "${tag}" '.[] | select(.metadata.container.tags[] | contains($tag)) | .metadata.container.tags[], ",", .id, "\n"'); do
|
||||
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?page=${i}&per_page=100" | jq -j --arg tag "${tag}" '.[] | select(.metadata.container.tags | index($tag)) | .metadata.container.tags[], ",", .id, "\n"'); do
|
||||
IFS=',' read -ra tagid <<< "${ghcr_version}"
|
||||
echo "Removing tag ${tagid[0]} with id ${tagid[1]} from ghcr.io"
|
||||
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/${tagid[1]}"
|
||||
@@ -123,7 +126,8 @@ if [[ ${BUILDKITE_LABEL} == ":docker: Deploy Manifest" ]] && [[ ${BUILDKITE_BRAN
|
||||
|
||||
echo "--- :docker: Removing tags for deleted branches"
|
||||
dockerbranchtags=$(curl -fsL --retry 3 -H "Authorization: Bearer ${anontoken}" https://registry-1.docker.io/v2/authelia/authelia/tags/list | jq -r '.tags[] | select(startswith("PR") | not)' | sed -r '/^(latest|master|develop|v.*|([[:digit:]]+)\.?([[:digit:]]+)?\.?([[:digit:]]+)?)$/d' | sort)
|
||||
githubbranches=$(curl -fs --retry 3 https://api.github.com/repos/authelia/authelia/branches?per_page=100 | jq -r '.[].name' | sort)
|
||||
githubbranches=$(curl -fs --retry 3 https://api.github.com/repos/authelia/authelia/branches?per_page=100 | jq -r '.[].name')
|
||||
githubbranches=$(sanitize_tag "${githubbranches}" | sort)
|
||||
cleanup_tags "${dockerbranchtags}" "${githubbranches}"
|
||||
|
||||
echo "--- :docker: Removing tags for merged or closed pull requests"
|
||||
|
||||
@@ -19,6 +19,17 @@
|
||||
# each of them; disabled file-wide since it's a false positive here.
|
||||
# shellcheck disable=SC2034
|
||||
|
||||
# Makes a branch name safe to use in a Docker tag. A Docker tag admits only [A-Za-z0-9_.-], so the "/" in the
|
||||
# "<type>/<summary>" branch convention (and anything else out of that set) is folded down to "-": fix/oidc-claims
|
||||
# becomes fix-oidc-claims. Applied line by line, so a list of branch names can be passed in one call.
|
||||
#
|
||||
# Every place that turns a branch name into a tag must agree on this mapping, including the ones outside this file:
|
||||
# the tag reaper in hooks/post-command, which compares the published tags against the live branch names, and
|
||||
# sanitizeTag() in cmd/authelia-scripts/cmd/docker.go, which tags the manifests this pushes.
|
||||
sanitize_tag() {
|
||||
echo "${1}" | sed -E 's/[^A-Za-z0-9_.-]+/-/g'
|
||||
}
|
||||
|
||||
changed() {
|
||||
local base_ref="${1}"
|
||||
local path_pattern="${2}"
|
||||
@@ -64,7 +75,7 @@ resolve_base_ref() {
|
||||
# ":latest" tag resolve to the same image callers only need to
|
||||
# override this when they explicitly want the literal "master"
|
||||
# tag instead (grypescans.sh does).
|
||||
# $2 - "true" (default) to collapse renovate-* branches to the literal
|
||||
# $2 - "true" (default) to collapse renovate/* branches to the literal
|
||||
# suffix "renovate", matching the image tag integration.sh actually
|
||||
# builds under. Pass "false" to fall through to the literal branch
|
||||
# name instead; grypescans.sh scans the main "authelia/authelia"
|
||||
@@ -79,10 +90,10 @@ resolve_tag_suffix() {
|
||||
local collapse_renovate="${2:-true}"
|
||||
TAG_SUFFIX=""
|
||||
|
||||
if [[ "${collapse_renovate}" == "true" ]] && [[ "${BUILDKITE_BRANCH}" =~ ^renovate- ]]; then
|
||||
if [[ "${collapse_renovate}" == "true" ]] && [[ "${BUILDKITE_BRANCH}" =~ ^renovate/ ]]; then
|
||||
TAG_SUFFIX="renovate"
|
||||
elif [[ "${BUILDKITE_BRANCH}" != "master" ]] && [[ ! "${BUILDKITE_BRANCH}" =~ .*:.* ]]; then
|
||||
TAG_SUFFIX="${BUILDKITE_BRANCH}"
|
||||
TAG_SUFFIX=$(sanitize_tag "${BUILDKITE_BRANCH}")
|
||||
elif [[ "${BUILDKITE_BRANCH}" != "master" ]] && [[ "${BUILDKITE_BRANCH}" =~ .*:.* ]]; then
|
||||
TAG_SUFFIX="PR${BUILDKITE_PULL_REQUEST}"
|
||||
elif [[ "${BUILDKITE_BRANCH}" == "master" ]] && [[ "${BUILDKITE_PULL_REQUEST}" == "false" ]]; then
|
||||
|
||||
@@ -14,6 +14,13 @@ on("pull_request.opened")
|
||||
return !excludedBranchPrefixes.test(context.payload.pull_request.head.ref);
|
||||
})
|
||||
.filter((context) => !context.payload.pull_request.title.startsWith("docs"))
|
||||
// A container tag admits only [A-Za-z0-9_.-], so the ref is folded to the tag the image is published under.
|
||||
.filter((context) => {
|
||||
context.payload.pull_request.head.ref =
|
||||
context.payload.pull_request.head.ref.replace(/[^A-Za-z0-9_.-]+/g, "-");
|
||||
|
||||
return true;
|
||||
})
|
||||
.comment(`## Artifacts
|
||||
These changes are published for testing on Buildkite, DockerHub and GitHub Container Registry.
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
":separatePatchReleases",
|
||||
"workarounds:doNotUpgradeFromAlpineStableToEdge"
|
||||
],
|
||||
"branchPrefix": "renovate-",
|
||||
"branchPrefixOld": "renovate-",
|
||||
"rebaseWhen": "conflicted",
|
||||
"ignorePresets": [
|
||||
":prHourlyLimit2",
|
||||
|
||||
@@ -27,6 +27,7 @@ var (
|
||||
dockerTags = regexp.MustCompile(`v(?P<Patch>(?P<Minor>(?P<Major>\d+)\.\d+)\.\d+.*)`)
|
||||
ignoredSuffixes = regexp.MustCompile("alpha|beta")
|
||||
publicRepo = regexp.MustCompile(`.*:.*`)
|
||||
tagUnsafe = regexp.MustCompile(`[^A-Za-z0-9_.-]+`)
|
||||
tags = dockerTags.FindStringSubmatch(ciTag)
|
||||
)
|
||||
|
||||
@@ -122,7 +123,7 @@ func cmdDockerPushManifestRun(_ *cobra.Command, _ []string) {
|
||||
case ciBranch != masterTag && !publicRepo.MatchString(ciBranch):
|
||||
login(docker, dockerhub)
|
||||
login(docker, ghcr)
|
||||
deployManifest(docker, cve, ciBranch)
|
||||
deployManifest(docker, cve, sanitizeTag(ciBranch))
|
||||
case ciBranch != masterTag && publicRepo.MatchString(ciBranch):
|
||||
login(docker, dockerhub)
|
||||
login(docker, ghcr)
|
||||
@@ -138,6 +139,10 @@ func cmdDockerPushManifestRun(_ *cobra.Command, _ []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeTag(tag string) string {
|
||||
return tagUnsafe.ReplaceAllString(tag, "-")
|
||||
}
|
||||
|
||||
func checkContainerIsSupported(container string) {
|
||||
for _, v := range containers {
|
||||
if container == v {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: 2026 Authelia
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSanitizeTagShouldMakeBranchNamesSafeForDockerTags(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
have string
|
||||
expected string
|
||||
}{
|
||||
{"ShouldFoldTheTypeSeparator", "fix/oidc-claims", "fix-oidc-claims"},
|
||||
{"ShouldFoldEveryRunToOneSeparator", "renovate/docker-haproxy/3.x", "renovate-docker-haproxy-3.x"},
|
||||
{"ShouldPreserveAlreadySafeNames", "feat-oidc-dpop", "feat-oidc-dpop"},
|
||||
{"ShouldPreserveDotsAndUnderscores", "fix/v4.38_backport", "fix-v4.38_backport"},
|
||||
{"ShouldFoldWhitespaceAndOtherUnsafeRunes", "fix/oidc claims (#123)", "fix-oidc-claims-123-"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, sanitizeTag(tc.have))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user