Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ build_and_retag: &build_and_retag
- images/renamed-agentgateway.yaml
- images/renamed-agent-sandbox.yaml
- images/renamed-kagent.yaml
- images/renamed-substrate.yaml
- images/renamed-cilium-prereleases.yaml
- images/renamed-upbound-aws.yaml
- images/renamed-upbound-azure.yaml
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ type RenamedImage struct {
AddTagSuffix string `yaml:"add_tag_suffix,omitempty"`
// OverrideRepoName allows user to rewrite the name of the image entirely.
// Example: "alpinegit", so "alpine" would become
// "gsoci.azurecr.io/giantswarm/alpinegit"
// "gsoci.azurecr.io/giantswarm/alpinegit". A slash keeps a nested path:
// "kagent/controller" becomes "gsoci.azurecr.io/giantswarm/kagent/controller".
OverrideRepoName string `yaml:"override_repo_name,omitempty"`
// StripSemverPrefix removes the initial 'v' in 'v1.2.3' if enabled. Works
// only when Semver is defined.
Expand Down
10 changes: 10 additions & 0 deletions images/renamed-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@
- image: fluxcd/source-watcher
override_repo_name: fluxcd-source-watcher
semver: ">= v2.1.1"
# The same two controllers under their upstream path, gsoci.azurecr.io/giantswarm/fluxcd/<controller>:
# the flux-operator composes a controller image as <registry>/<controller>:<tag> from the one
# registry value of a FluxInstance (spec.distribution.registry), which the flattened fluxcd-*
# names above cannot serve. From the Flux 2.9 line on (source-controller v1.9, helm-controller v1.6).
- image: ghcr.io/fluxcd/helm-controller
override_repo_name: fluxcd/helm-controller
semver: ">= v1.6.0"
- image: ghcr.io/fluxcd/source-controller
override_repo_name: fluxcd/source-controller
semver: ">= v1.9.0"
- image: registry.k8s.io/cluster-api-vsphere/cluster-api-vsphere-controller
override_repo_name: cluster-api-vsphere-controller
semver: ">= v1.5.1"
Expand Down
25 changes: 25 additions & 0 deletions images/renamed-kagent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,28 @@
- image: ghcr.io/kagent-dev/kagent/tools
override_repo_name: kagent-tools
semver: ">= 0.1.3"
# The Giant Swarm line of kagent (github.com/giantswarm/kagent-upstream, FORK.md):
# upstream release + carried patches, built and published there under
# ghcr.io/giantswarm/kagent/<image>. Only its release tags (X.Y.Z-gs.N) are
# mirrored; dev builds (X.Y.Z-dev.…) stay in ghcr. The destination keeps the
# upstream path, gsoci.azurecr.io/giantswarm/kagent/<image>, so a consumer
# switches the registry alone and keeps the repositories the chart publishes.
# golang-adk and claude-harness are referenced by digest, stamped into the kagent
# chart at publish; `skopeo copy --all` copies the image index unchanged, so the
# stamped digest resolves at the destination too.
- image: ghcr.io/giantswarm/kagent/controller
override_repo_name: kagent/controller
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.11.0-gs.16"
- image: ghcr.io/giantswarm/kagent/ui
override_repo_name: kagent/ui
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.11.0-gs.16"
- image: ghcr.io/giantswarm/kagent/golang-adk
override_repo_name: kagent/golang-adk
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.11.0-gs.16"
- image: ghcr.io/giantswarm/kagent/claude-harness
override_repo_name: kagent/claude-harness
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.11.0-gs.16"
30 changes: 30 additions & 0 deletions images/renamed-substrate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# The Giant Swarm line of Agent Substrate (github.com/giantswarm/substrate, FORK.md):
# upstream release + carried patches, built and published there under
# ghcr.io/giantswarm/substrate/<component>. Only its release tags (X.Y.Z-gs.N) are
# mirrored; SBOM and signature tags (sha256-….sbom) stay in ghcr. The destination
# keeps the upstream path, gsoci.azurecr.io/giantswarm/substrate/<component>, so a
# consumer switches the registry alone and keeps the repositories the chart names.
- image: ghcr.io/giantswarm/substrate/ateapi
override_repo_name: substrate/ateapi
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.0.30-gs.4"
- image: ghcr.io/giantswarm/substrate/atecontroller
override_repo_name: substrate/atecontroller
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.0.30-gs.4"
- image: ghcr.io/giantswarm/substrate/atelet
override_repo_name: substrate/atelet
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.0.30-gs.4"
- image: ghcr.io/giantswarm/substrate/atenet
override_repo_name: substrate/atenet
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.0.30-gs.4"
- image: ghcr.io/giantswarm/substrate/podcertcontroller
override_repo_name: substrate/podcertcontroller
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.0.30-gs.4"
- image: ghcr.io/giantswarm/substrate/ateom-gvisor
override_repo_name: substrate/ateom-gvisor
filter: '^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$'
semver: ">= 0.0.30-gs.4"
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ type RenamedImage struct {
AddTagSuffix string `yaml:"add_tag_suffix,omitempty"`
// OverrideRepoName allows user to rewrite the name of the image entirely.
// Example: "alpinegit", so "alpine" would become
// "gsoci.azurecr.io/giantswarm/alpinegit"
// "gsoci.azurecr.io/giantswarm/alpinegit". A slash keeps a nested path:
// "kagent/controller" becomes "gsoci.azurecr.io/giantswarm/kagent/controller".
OverrideRepoName string `yaml:"override_repo_name,omitempty"`
// StripSemverPrefix removes the initial 'v' in 'v1.2.3' if enabled. Works
// only when Semver is defined.
Expand Down
130 changes: 130 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package main

import (
"os"
"path/filepath"
"reflect"
"testing"

"gopkg.in/yaml.v3"
)

const tagLatest = "latest"

// TestFilterTags pins the tag selection of the entries in images/: a release-tag
// filter with a prerelease floor keeps the -gs.N line and nothing else, a plain
// semver floor keeps releases and drops release candidates and non-semver tags.
func TestFilterTags(t *testing.T) {
tests := []struct {
name string
img RenamedImage
tags []string
want []string
}{
{
name: "release tags of a -gs.N line from a prerelease floor",
img: RenamedImage{
Filter: `^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$`,
Semver: ">= 0.11.0-gs.16",
},
tags: []string{
"0.11.0-gs.15",
"0.11.0-gs.16",
"0.11.0-gs.20",
"0.11.1-gs.1",
"0.12.0-gs.3",
"0.11.0-dev.poc.2026-09-10.21-15-57.hcc77fbb",
"0.11.1-dev.sync-20260914-main.h7b3d881",
"0.11.0",
"sha256-fc177422d843219b017cce398443b2458337ca5084abecfc0b7e4a622d7e6843.sbom",
tagLatest,
},
want: []string{"0.11.0-gs.16", "0.11.0-gs.20", "0.11.1-gs.1", "0.12.0-gs.3"},
},
{
name: "release tags of a -gs.N line, floor at a patch release",
img: RenamedImage{
Filter: `^([0-9]+\.[0-9]+\.[0-9]+-gs\.[0-9]+)$`,
Semver: ">= 0.0.30-gs.4",
},
tags: []string{"0.0.27-gs.9", "0.0.30-gs.3", "0.0.30-gs.4", "0.0.31-gs.1"},
want: []string{"0.0.30-gs.4", "0.0.31-gs.1"},
},
{
name: "v-prefixed releases from a floor, no release candidates or non-semver tags",
img: RenamedImage{Semver: ">= v1.9.0"},
tags: []string{"v1.8.5", "v1.9.0", "v1.9.5", "v1.10.0-rc.1", "v2.0.0", "v2beta2-dd92f466", tagLatest},
want: []string{"v1.9.0", "v1.9.5", "v2.0.0"},
},
{
name: "filter extracts the version to compare",
img: RenamedImage{
Filter: `^(\d+\.\d+\.\d+)-kafka-.*`,
Semver: ">= 0.51.0",
},
tags: []string{"0.50.0-kafka-3.9.0", "0.51.0-kafka-4.1.0", "0.51.0", tagLatest},
want: []string{"0.51.0-kafka-4.1.0"},
},
{
name: "tag pattern",
img: RenamedImage{TagOrPattern: `^[0-9]+\.[0-9]+\.[0-9]+-full$`},
tags: []string{"0.10.0", "0.10.0-full", "0.10.0-full-debug"},
want: []string{"0.10.0-full"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.img.Validate(); err != nil {
t.Fatalf("Validate: %v", err)
}
got, err := tt.img.FilterTags(tt.tags)
if err != nil {
t.Fatalf("FilterTags: %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("FilterTags = %v, want %v", got, tt.want)
}
})
}
}

// TestRenamedImagesFiles parses every renamed-images file the way `retagger run`
// does and validates each entry, its tag pattern, semver constraint and filter
// included, so a broken entry fails here and not in the nightly run.
func TestRenamedImagesFiles(t *testing.T) {
files, err := filepath.Glob("images/renamed-*.yaml")
if err != nil {
t.Fatal(err)
}
if len(files) == 0 {
t.Fatal("no images/renamed-*.yaml files found")
}
for _, file := range files {
t.Run(filepath.Base(file), func(t *testing.T) {
b, err := os.ReadFile(file) // #nosec G304 -- file names come from the glob over images/ above
if err != nil {
t.Fatal(err)
}
var images []RenamedImage
if err := yaml.Unmarshal(b, &images); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if len(images) == 0 {
t.Fatal("no entries")
}
for _, img := range images {
if img.Image == "" {
t.Errorf("entry without image: %+v", img)
continue
}
if err := img.Validate(); err != nil {
t.Errorf("%s: %v", img.Image, err)
continue
}
if _, err := img.FilterTags(nil); err != nil {
t.Errorf("%s: %v", img.Image, err)
}
}
})
}
}