From c815311d636dd4b3ca0b2f70b08ec63f930ee744 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 6 Oct 2026 10:55:07 +0200 Subject: [PATCH 1/5] Assign copyright to The Cozystack Authors The repository now lives under the cozystack organization, so the project's copyright belongs to its authors collectively rather than to the original individual author. Update every source header and hack/boilerplate.go.txt, and regenerate zz_generated.deepcopy.go with controller-gen so its header follows the boilerplate. LICENSE carries the unmodified Apache-2.0 text with no named holder, so it needs no change. Assisted-By: LLM Signed-off-by: Timofei Larkin --- api/v1alpha1/groupversion_info.go | 2 +- api/v1alpha1/ipaddress_types.go | 2 +- api/v1alpha1/ipaddressclaim_types.go | 2 +- api/v1alpha1/ipaddressclass_types.go | 2 +- api/v1alpha1/well_known.go | 2 +- api/v1alpha1/zz_generated.deepcopy.go | 2 +- cmd/main.go | 2 +- hack/boilerplate.go.txt | 2 +- internal/controller/indexes.go | 2 +- internal/controller/ipaddress_controller.go | 2 +- internal/controller/ipaddress_controller_test.go | 2 +- internal/controller/ipaddressclaim_controller.go | 2 +- internal/controller/ipaddressclaim_controller_test.go | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go index 9ce34b5..9383fd4 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1alpha1/groupversion_info.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/api/v1alpha1/ipaddress_types.go b/api/v1alpha1/ipaddress_types.go index 72b0b53..260aaf4 100644 --- a/api/v1alpha1/ipaddress_types.go +++ b/api/v1alpha1/ipaddress_types.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/api/v1alpha1/ipaddressclaim_types.go b/api/v1alpha1/ipaddressclaim_types.go index 041a779..1b3a481 100644 --- a/api/v1alpha1/ipaddressclaim_types.go +++ b/api/v1alpha1/ipaddressclaim_types.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/api/v1alpha1/ipaddressclass_types.go b/api/v1alpha1/ipaddressclass_types.go index 88fa871..555f2ca 100644 --- a/api/v1alpha1/ipaddressclass_types.go +++ b/api/v1alpha1/ipaddressclass_types.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/api/v1alpha1/well_known.go b/api/v1alpha1/well_known.go index a2a2468..16124e6 100644 --- a/api/v1alpha1/well_known.go +++ b/api/v1alpha1/well_known.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index dfbbc60..76fd6c2 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,7 +1,7 @@ //go:build !ignore_autogenerated /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/cmd/main.go b/cmd/main.go index 35e899f..8ee53d8 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index c47ddf8..aa99ee3 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/indexes.go b/internal/controller/indexes.go index 5ed8a8c..0346b9d 100644 --- a/internal/controller/indexes.go +++ b/internal/controller/indexes.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/ipaddress_controller.go b/internal/controller/ipaddress_controller.go index 4584069..4dd0a25 100644 --- a/internal/controller/ipaddress_controller.go +++ b/internal/controller/ipaddress_controller.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/ipaddress_controller_test.go b/internal/controller/ipaddress_controller_test.go index 7780f61..7fc1960 100644 --- a/internal/controller/ipaddress_controller_test.go +++ b/internal/controller/ipaddress_controller_test.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/ipaddressclaim_controller.go b/internal/controller/ipaddressclaim_controller.go index 0429aec..fc145eb 100644 --- a/internal/controller/ipaddressclaim_controller.go +++ b/internal/controller/ipaddressclaim_controller.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/internal/controller/ipaddressclaim_controller_test.go b/internal/controller/ipaddressclaim_controller_test.go index 6527b29..f35e369 100644 --- a/internal/controller/ipaddressclaim_controller_test.go +++ b/internal/controller/ipaddressclaim_controller_test.go @@ -1,5 +1,5 @@ /* -Copyright 2026 Timofei Larkin. +Copyright 2026 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 0f2f5f71491b55822fab51b9c745064cdaf8bd10 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 6 Oct 2026 10:55:12 +0200 Subject: [PATCH 2/5] Move module path, image and links from lllamnyp to cozystack The repository was transferred from lllamnyp/address-controller to cozystack/address-controller. Point everything that named the old owner at the new one: - Go module path github.com/cozystack/address-controller (go mod edit -module, imports rewritten, go mod tidy) and the kubebuilder PROJECT file. - Image ghcr.io/cozystack/address-controller in the Makefile, the chart default and the README. The release workflow already derives its image from github.repository, so CI needs no change. - org.opencontainers.image.source label, so GHCR links the new package to this repository. - Chart home/sources and the metallb-iad links, which also moved to the cozystack organization. Images already published under ghcr.io/lllamnyp are left untouched. Assisted-By: LLM Signed-off-by: Timofei Larkin --- Dockerfile | 2 +- Makefile | 2 +- PROJECT | 8 ++++---- README.md | 6 +++--- chart/address-controller/Chart.yaml | 4 ++-- chart/address-controller/values.yaml | 2 +- cmd/main.go | 4 ++-- docs/design.md | 2 +- go.mod | 2 +- internal/controller/indexes.go | 2 +- internal/controller/ipaddress_controller.go | 2 +- internal/controller/ipaddress_controller_test.go | 2 +- internal/controller/ipaddressclaim_controller.go | 2 +- internal/controller/ipaddressclaim_controller_test.go | 2 +- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 268373b..79ec3bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ ARG TARGETARCH RUN CGO_ENABLED=0 GOARCH=${TARGETARCH} go build -trimpath -o /out/manager ./cmd FROM gcr.io/distroless/static:nonroot -LABEL org.opencontainers.image.source="https://github.com/lllamnyp/address-controller" +LABEL org.opencontainers.image.source="https://github.com/cozystack/address-controller" COPY --from=build /out/manager /manager USER 65532:65532 ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile index c5401a8..c1f97f6 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Image coordinates. TAG defaults to something unique per commit; override for # releases (e.g. make docker-push TAG=v0.1.0) or floating tags (TAG=main). -IMG_REPO ?= ghcr.io/lllamnyp/address-controller +IMG_REPO ?= ghcr.io/cozystack/address-controller TAG ?= $(shell git describe --tags --always --dirty) IMG ?= $(IMG_REPO):$(TAG) PLATFORMS ?= linux/amd64,linux/arm64 diff --git a/PROJECT b/PROJECT index b947642..455572f 100644 --- a/PROJECT +++ b/PROJECT @@ -6,14 +6,14 @@ domain: sdn.cozystack.io layout: - go.kubebuilder.io/v4 projectName: address-controller -repo: github.com/lllamnyp/address-controller +repo: github.com/cozystack/address-controller resources: - api: crdVersion: v1 domain: sdn.cozystack.io group: local kind: IPAddressClass - path: github.com/lllamnyp/address-controller/api/v1alpha1 + path: github.com/cozystack/address-controller/api/v1alpha1 version: v1alpha1 - api: crdVersion: v1 @@ -21,7 +21,7 @@ resources: domain: sdn.cozystack.io group: local kind: IPAddress - path: github.com/lllamnyp/address-controller/api/v1alpha1 + path: github.com/cozystack/address-controller/api/v1alpha1 version: v1alpha1 - api: crdVersion: v1 @@ -30,6 +30,6 @@ resources: domain: sdn.cozystack.io group: local kind: IPAddressClaim - path: github.com/lllamnyp/address-controller/api/v1alpha1 + path: github.com/cozystack/address-controller/api/v1alpha1 version: v1alpha1 version: "3" diff --git a/README.md b/README.md index 675a0b7..b747c3b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ analogue), which live in separate projects. **The full contract — state machines, reconciliation algorithms, field ownership, and driver obligations — is specified in [docs/design.md](docs/design.md).** The reference driver implementing it is -[metallb-iad](https://github.com/lllamnyp/metallb-iad). +[metallb-iad](https://github.com/cozystack/metallb-iad). ## Resource model @@ -96,7 +96,7 @@ cluster. `helm uninstall` leaves the CRDs (and every `IPAddress` ledger entry) behind by design. The controller image is published as -`ghcr.io/lllamnyp/address-controller:main` (plus `main-` and semver tags) +`ghcr.io/cozystack/address-controller:main` (plus `main-` and semver tags) by the release workflow on every push to main. ## Development @@ -106,6 +106,6 @@ make manifests generate # regenerate CRDs, ClusterRole, and deepcopy after API make build # build the manager go test ./internal/controller/ make run # run the controller locally -make docker-build docker-push # publish ghcr.io/lllamnyp/address-controller: +make docker-build docker-push # publish ghcr.io/cozystack/address-controller: make helm-package # lint and package the chart into dist/ ``` diff --git a/chart/address-controller/Chart.yaml b/chart/address-controller/Chart.yaml index c721da9..fb6d953 100644 --- a/chart/address-controller/Chart.yaml +++ b/chart/address-controller/Chart.yaml @@ -10,6 +10,6 @@ keywords: - ipam - networking - cozystack -home: https://github.com/lllamnyp/address-controller +home: https://github.com/cozystack/address-controller sources: - - https://github.com/lllamnyp/address-controller + - https://github.com/cozystack/address-controller diff --git a/chart/address-controller/values.yaml b/chart/address-controller/values.yaml index d9ba718..08b9f72 100644 --- a/chart/address-controller/values.yaml +++ b/chart/address-controller/values.yaml @@ -1,5 +1,5 @@ # Container image for the core controller. -image: ghcr.io/lllamnyp/address-controller:main +image: ghcr.io/cozystack/address-controller:main imagePullPolicy: IfNotPresent imagePullSecrets: [] diff --git a/cmd/main.go b/cmd/main.go index 8ee53d8..df44207 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -38,8 +38,8 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" - localv1alpha1 "github.com/lllamnyp/address-controller/api/v1alpha1" - "github.com/lllamnyp/address-controller/internal/controller" + localv1alpha1 "github.com/cozystack/address-controller/api/v1alpha1" + "github.com/cozystack/address-controller/internal/controller" // +kubebuilder:scaffold:imports ) diff --git a/docs/design.md b/docs/design.md index 839509d..cb449f0 100644 --- a/docs/design.md +++ b/docs/design.md @@ -9,7 +9,7 @@ This document records the contract: the resource model, the two state machines the core drives, the exact reconciliation algorithms, and the obligations a per-class driver must meet. It is the reference against which -drivers (such as [metallb-iad](https://github.com/lllamnyp/metallb-iad)) +drivers (such as [metallb-iad](https://github.com/cozystack/metallb-iad)) are written. ## 1. Scope diff --git a/go.mod b/go.mod index ef5a2d2..e89a7ce 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/lllamnyp/address-controller +module github.com/cozystack/address-controller go 1.23.0 diff --git a/internal/controller/indexes.go b/internal/controller/indexes.go index 0346b9d..2d52f88 100644 --- a/internal/controller/indexes.go +++ b/internal/controller/indexes.go @@ -23,7 +23,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - localv1alpha1 "github.com/lllamnyp/address-controller/api/v1alpha1" + localv1alpha1 "github.com/cozystack/address-controller/api/v1alpha1" ) // IPAddressClaimRefIndex indexes IPAddress objects by the diff --git a/internal/controller/ipaddress_controller.go b/internal/controller/ipaddress_controller.go index 4dd0a25..05fd45c 100644 --- a/internal/controller/ipaddress_controller.go +++ b/internal/controller/ipaddress_controller.go @@ -30,7 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" - localv1alpha1 "github.com/lllamnyp/address-controller/api/v1alpha1" + localv1alpha1 "github.com/cozystack/address-controller/api/v1alpha1" ) // IPAddressReconciler owns the class-agnostic address lifecycle: deletion diff --git a/internal/controller/ipaddress_controller_test.go b/internal/controller/ipaddress_controller_test.go index 7fc1960..ca3c1ec 100644 --- a/internal/controller/ipaddress_controller_test.go +++ b/internal/controller/ipaddress_controller_test.go @@ -27,7 +27,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - localv1alpha1 "github.com/lllamnyp/address-controller/api/v1alpha1" + localv1alpha1 "github.com/cozystack/address-controller/api/v1alpha1" ) func addressReconciler(c client.Client) *IPAddressReconciler { diff --git a/internal/controller/ipaddressclaim_controller.go b/internal/controller/ipaddressclaim_controller.go index fc145eb..a9cfd1e 100644 --- a/internal/controller/ipaddressclaim_controller.go +++ b/internal/controller/ipaddressclaim_controller.go @@ -34,7 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" - localv1alpha1 "github.com/lllamnyp/address-controller/api/v1alpha1" + localv1alpha1 "github.com/cozystack/address-controller/api/v1alpha1" ) // IPAddressClaimReconciler owns the class-agnostic claim lifecycle: resolving diff --git a/internal/controller/ipaddressclaim_controller_test.go b/internal/controller/ipaddressclaim_controller_test.go index f35e369..18c06de 100644 --- a/internal/controller/ipaddressclaim_controller_test.go +++ b/internal/controller/ipaddressclaim_controller_test.go @@ -31,7 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - localv1alpha1 "github.com/lllamnyp/address-controller/api/v1alpha1" + localv1alpha1 "github.com/cozystack/address-controller/api/v1alpha1" ) func testScheme(t *testing.T) *runtime.Scheme { From db24e1d2ad8ba25ea99308bdce225ecf37cd50b6 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 6 Oct 2026 11:39:17 +0200 Subject: [PATCH 3/5] Migrate to golangci-lint v2 and fix its findings The Lint workflow was failing on main. Move the linter to the current release, v2.14.0, in both the workflow (golangci-lint-action v9, which requires v2) and the Makefile, and convert .golangci.yml with golangci-lint migrate. Bump checkout and setup-go in the same workflow. Fix what v2 reports instead of silencing it: the claim tests now share constants for the class, provisioner, namespace, claim name and claim UID, the claim helpers drop the namespace and name parameters that always received the same values, and updateClaimStatus preallocates the reported address list. Assisted-By: LLM Signed-off-by: Timofei Larkin --- .github/workflows/lint.yml | 8 +- .golangci.yml | 56 ++++--- Makefile | 4 +- .../controller/ipaddress_controller_test.go | 22 +-- .../controller/ipaddressclaim_controller.go | 2 +- .../ipaddressclaim_controller_test.go | 148 ++++++++++-------- 6 files changed, 128 insertions(+), 112 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4951e33..72d2297 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,14 +10,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone the code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v7 with: go-version-file: go.mod - name: Run linter - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v9 with: - version: v1.63.4 + version: v2.14.0 diff --git a/.golangci.yml b/.golangci.yml index 6b29746..a7246fb 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,33 +1,15 @@ +version: "2" run: - timeout: 5m allow-parallel-runners: true - -issues: - # don't skip warning about doc comments - # don't exclude the default set of lint - exclude-use-default: false - # restore some of the defaults - # (fill in the rest as needed) - exclude-rules: - - path: "api/*" - linters: - - lll - - path: "internal/*" - linters: - - dupl - - lll linters: - disable-all: true + default: none enable: + - copyloopvar - dupl - errcheck - - copyloopvar - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - lll @@ -36,12 +18,34 @@ linters: - prealloc - revive - staticcheck - - typecheck - unconvert - unparam - unused - -linters-settings: - revive: + settings: + revive: + rules: + - name: comment-spacings + exclusions: + generated: lax rules: - - name: comment-spacings + - linters: + - lll + path: api/* + - linters: + - dupl + - lll + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index c1f97f6..551388b 100644 --- a/Makefile +++ b/Makefile @@ -131,7 +131,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.17.1 ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') #ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') -GOLANGCI_LINT_VERSION ?= v1.63.4 +GOLANGCI_LINT_VERSION ?= v2.14.0 .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. @@ -154,7 +154,7 @@ $(ENVTEST): $(LOCALBIN) .PHONY: golangci-lint golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) - $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary diff --git a/internal/controller/ipaddress_controller_test.go b/internal/controller/ipaddress_controller_test.go index ca3c1ec..ad5cece 100644 --- a/internal/controller/ipaddress_controller_test.go +++ b/internal/controller/ipaddress_controller_test.go @@ -52,7 +52,7 @@ func unboundAddress(name, ip string) *localv1alpha1.IPAddress { return &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: name}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: ip, Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, @@ -73,9 +73,9 @@ func TestUnboundAddressBecomesAvailable(t *testing.T) { } func TestBoundAddressWithLiveClaimBecomesBound(t *testing.T) { - claim := pendingClaim("public") + claim := pendingClaim(testClassName) addr := unboundAddress("ip-2", "203.0.113.2") - addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "web", UID: "claim-uid-1"} + addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: testClaimName, UID: testClaimUID} c := testClient(t, claim, addr) reconcileAddress(t, addressReconciler(c), "ip-2") @@ -85,9 +85,9 @@ func TestBoundAddressWithLiveClaimBecomesBound(t *testing.T) { } func TestPreBoundAddressWithoutUIDStaysUntouched(t *testing.T) { - claim := pendingClaim("public") + claim := pendingClaim(testClassName) addr := unboundAddress("ip-3", "203.0.113.3") - addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "web"} + addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: testClaimName} c := testClient(t, claim, addr) reconcileAddress(t, addressReconciler(c), "ip-3") @@ -99,7 +99,7 @@ func TestPreBoundAddressWithoutUIDStaysUntouched(t *testing.T) { func TestOrphanedAddressRetainIsReleased(t *testing.T) { addr := unboundAddress("ip-4", "203.0.113.4") addr.Spec.ReclaimPolicy = localv1alpha1.ReclaimRetain - addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "gone", UID: "old-uid"} + addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: "gone", UID: "old-uid"} c := testClient(t, addr) reconcileAddress(t, addressReconciler(c), "ip-4") @@ -115,7 +115,7 @@ func TestOrphanedAddressRetainIsReleased(t *testing.T) { func TestOrphanedAddressDeletePolicyIsDeleted(t *testing.T) { addr := unboundAddress("ip-5", "203.0.113.5") addr.Spec.ReclaimPolicy = localv1alpha1.ReclaimDelete - addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "gone", UID: "old-uid"} + addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: "gone", UID: "old-uid"} c := testClient(t, addr) r := addressReconciler(c) reconcileAddress(t, r, "ip-5") @@ -130,9 +130,9 @@ func TestOrphanedAddressDeletePolicyIsDeleted(t *testing.T) { } func TestUIDMismatchTriggersReclaim(t *testing.T) { - claim := pendingClaim("public") // UID claim-uid-1 + claim := pendingClaim(testClassName) // UID claim-uid-1 addr := unboundAddress("ip-6", "203.0.113.6") - addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "web", UID: "some-older-uid"} + addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: testClaimName, UID: "some-older-uid"} c := testClient(t, claim, addr) reconcileAddress(t, addressReconciler(c), "ip-6") @@ -156,10 +156,10 @@ func TestStickyPhasesAreNotOverwritten(t *testing.T) { } func TestDeletionBlockedWhileBoundToLiveClaim(t *testing.T) { - claim := pendingClaim("public") + claim := pendingClaim(testClassName) addr := unboundAddress("ip-7", "203.0.113.7") addr.Finalizers = []string{localv1alpha1.AddressProtectionFinalizer} - addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "web", UID: "claim-uid-1"} + addr.Spec.ClaimRef = &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: testClaimName, UID: testClaimUID} addr.Status.Phase = localv1alpha1.IPAddressBound c := testClient(t, claim, addr) if err := c.Delete(context.Background(), addr); err != nil { diff --git a/internal/controller/ipaddressclaim_controller.go b/internal/controller/ipaddressclaim_controller.go index a9cfd1e..d7975c4 100644 --- a/internal/controller/ipaddressclaim_controller.go +++ b/internal/controller/ipaddressclaim_controller.go @@ -371,7 +371,7 @@ func (r *IPAddressClaimReconciler) updateClaimStatus(ctx context.Context, claim previous := claim.Status.Phase sort.Slice(bound, func(i, j int) bool { return bound[i].Name < bound[j].Name }) - var reported []localv1alpha1.BoundAddress + reported := make([]localv1alpha1.BoundAddress, 0, len(bound)) for _, addr := range bound { reported = append(reported, localv1alpha1.BoundAddress{Name: addr.Name, Address: addr.Spec.Address}) } diff --git a/internal/controller/ipaddressclaim_controller_test.go b/internal/controller/ipaddressclaim_controller_test.go index 18c06de..ab4911b 100644 --- a/internal/controller/ipaddressclaim_controller_test.go +++ b/internal/controller/ipaddressclaim_controller_test.go @@ -34,6 +34,14 @@ import ( localv1alpha1 "github.com/cozystack/address-controller/api/v1alpha1" ) +const ( + testClassName = "public" + testProvisioner = "metallb.drivers.local.sdn.cozystack.io" + testNamespace = "tenant-a" + testClaimName = "web" + testClaimUID = "claim-uid-1" +) + func testScheme(t *testing.T) *runtime.Scheme { t.Helper() scheme := runtime.NewScheme() @@ -70,20 +78,20 @@ func claimReconciler(c client.Client) *IPAddressClaimReconciler { } } -func reconcileClaim(t *testing.T, r *IPAddressClaimReconciler, namespace, name string) { +func reconcileClaim(t *testing.T, r *IPAddressClaimReconciler) { t.Helper() _, err := r.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{Namespace: namespace, Name: name}, + NamespacedName: types.NamespacedName{Namespace: testNamespace, Name: testClaimName}, }) if err != nil { t.Fatalf("reconcile: %v", err) } } -func getClaim(t *testing.T, c client.Client, namespace, name string) *localv1alpha1.IPAddressClaim { +func getClaim(t *testing.T, c client.Client) *localv1alpha1.IPAddressClaim { t.Helper() claim := &localv1alpha1.IPAddressClaim{} - if err := c.Get(context.Background(), types.NamespacedName{Namespace: namespace, Name: name}, claim); err != nil { + if err := c.Get(context.Background(), types.NamespacedName{Namespace: testNamespace, Name: testClaimName}, claim); err != nil { t.Fatalf("get claim: %v", err) } return claim @@ -100,17 +108,21 @@ func getAddress(t *testing.T, c client.Client, name string) *localv1alpha1.IPAdd func publicClass(annotations map[string]string) *localv1alpha1.IPAddressClass { return &localv1alpha1.IPAddressClass{ - ObjectMeta: metav1.ObjectMeta{Name: "public", Annotations: annotations}, + ObjectMeta: metav1.ObjectMeta{Name: testClassName, Annotations: annotations}, Spec: localv1alpha1.IPAddressClassSpec{ - Provisioner: "metallb.drivers.local.sdn.cozystack.io", + Provisioner: testProvisioner, ReclaimPolicy: localv1alpha1.ReclaimRetain, }, } } +func defaultClass() *localv1alpha1.IPAddressClass { + return publicClass(map[string]string{localv1alpha1.IsDefaultClassAnnotation: "true"}) +} + func pendingClaim(className string) *localv1alpha1.IPAddressClaim { return &localv1alpha1.IPAddressClaim{ - ObjectMeta: metav1.ObjectMeta{Name: "web", Namespace: "tenant-a", UID: "claim-uid-1"}, + ObjectMeta: metav1.ObjectMeta{Name: testClaimName, Namespace: testNamespace, UID: testClaimUID}, Spec: localv1alpha1.IPAddressClaimSpec{ ClassName: className, Family: localv1alpha1.FamilyIPv4, @@ -119,17 +131,17 @@ func pendingClaim(className string) *localv1alpha1.IPAddressClaim { } func TestClaimResolvesClassAndWaitsForProvisioner(t *testing.T) { - c := testClient(t, publicClass(nil), pendingClaim("public")) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + c := testClient(t, publicClass(nil), pendingClaim(testClassName)) + reconcileClaim(t, claimReconciler(c)) - claim := getClaim(t, c, "tenant-a", "web") + claim := getClaim(t, c) if claim.Status.Phase != localv1alpha1.ClaimPending { t.Errorf("phase = %q, want Pending", claim.Status.Phase) } - if got := claim.Annotations[localv1alpha1.ProvisionerAnnotation]; got != "metallb.drivers.local.sdn.cozystack.io" { + if got := claim.Annotations[localv1alpha1.ProvisionerAnnotation]; got != testProvisioner { t.Errorf("provisioner annotation = %q", got) } - if claim.Status.ClassName != "public" { + if claim.Status.ClassName != testClassName { t.Errorf("status.className = %q", claim.Status.ClassName) } if !hasFinalizer(claim.Finalizers, localv1alpha1.ClaimProtectionFinalizer) { @@ -143,21 +155,21 @@ func TestClaimResolvesClassAndWaitsForProvisioner(t *testing.T) { func TestClaimUsesDefaultClass(t *testing.T) { c := testClient(t, - publicClass(map[string]string{localv1alpha1.IsDefaultClassAnnotation: "true"}), + defaultClass(), pendingClaim("")) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + reconcileClaim(t, claimReconciler(c)) - claim := getClaim(t, c, "tenant-a", "web") - if claim.Status.ClassName != "public" { + claim := getClaim(t, c) + if claim.Status.ClassName != testClassName { t.Errorf("status.className = %q, want public (the default class)", claim.Status.ClassName) } } func TestClaimWithoutAnyDefaultClassStaysPending(t *testing.T) { c := testClient(t, publicClass(nil), pendingClaim("")) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + reconcileClaim(t, claimReconciler(c)) - claim := getClaim(t, c, "tenant-a", "web") + claim := getClaim(t, c) cond := meta.FindStatusCondition(claim.Status.Conditions, localv1alpha1.ConditionClassResolved) if cond == nil || cond.Reason != localv1alpha1.ReasonNoDefaultClass { t.Errorf("ClassResolved condition = %+v, want reason NoDefaultClass", cond) @@ -165,15 +177,15 @@ func TestClaimWithoutAnyDefaultClassStaysPending(t *testing.T) { } func TestClaimWithMultipleDefaultClassesStaysPending(t *testing.T) { - second := publicClass(map[string]string{localv1alpha1.IsDefaultClassAnnotation: "true"}) + second := defaultClass() second.Name = "public-2" c := testClient(t, - publicClass(map[string]string{localv1alpha1.IsDefaultClassAnnotation: "true"}), + defaultClass(), second, pendingClaim("")) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + reconcileClaim(t, claimReconciler(c)) - claim := getClaim(t, c, "tenant-a", "web") + claim := getClaim(t, c) cond := meta.FindStatusCondition(claim.Status.Conditions, localv1alpha1.ConditionClassResolved) if cond == nil || cond.Reason != localv1alpha1.ReasonMultipleDefaultClasses { t.Errorf("ClassResolved condition = %+v, want reason MultipleDefaultClasses", cond) @@ -184,24 +196,24 @@ func TestClaimAcceptsDriverPreBoundAddress(t *testing.T) { addr := &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: "ip-203-0-113-7"}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: "203.0.113.7", // Driver pre-binds without a UID; the core controller completes it. - ClaimRef: &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "web"}, + ClaimRef: &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: testClaimName}, Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, } - c := testClient(t, publicClass(nil), pendingClaim("public"), addr) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + c := testClient(t, publicClass(nil), pendingClaim(testClassName), addr) + reconcileClaim(t, claimReconciler(c)) - claim := getClaim(t, c, "tenant-a", "web") + claim := getClaim(t, c) if claim.Status.Phase != localv1alpha1.ClaimBound { t.Fatalf("phase = %q, want Bound", claim.Status.Phase) } if len(claim.Status.Addresses) != 1 || claim.Status.Addresses[0].Address != "203.0.113.7" { t.Errorf("status.addresses = %+v", claim.Status.Addresses) } - if got := getAddress(t, c, "ip-203-0-113-7").Spec.ClaimRef.UID; got != "claim-uid-1" { + if got := getAddress(t, c, "ip-203-0-113-7").Spec.ClaimRef.UID; got != testClaimUID { t.Errorf("claimRef.uid = %q, want completed to claim-uid-1", got) } } @@ -210,21 +222,21 @@ func TestClaimMatchesAvailableAddress(t *testing.T) { addr := &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: "ip-203-0-113-8"}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: "203.0.113.8", Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, Status: localv1alpha1.IPAddressStatus{Phase: localv1alpha1.IPAddressAvailable}, } - c := testClient(t, publicClass(nil), pendingClaim("public"), addr) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + c := testClient(t, publicClass(nil), pendingClaim(testClassName), addr) + reconcileClaim(t, claimReconciler(c)) - claim := getClaim(t, c, "tenant-a", "web") + claim := getClaim(t, c) if claim.Status.Phase != localv1alpha1.ClaimBound { t.Fatalf("phase = %q, want Bound", claim.Status.Phase) } ref := getAddress(t, c, "ip-203-0-113-8").Spec.ClaimRef - if ref == nil || ref.Namespace != "tenant-a" || ref.Name != "web" || ref.UID != "claim-uid-1" { + if ref == nil || ref.Namespace != testNamespace || ref.Name != testClaimName || ref.UID != testClaimUID { t.Errorf("claimRef = %+v", ref) } } @@ -242,27 +254,27 @@ func TestClaimIgnoresAvailableAddressOfWrongClassOrFamily(t *testing.T) { wrongFamily := &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: "ip-wrong-family"}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: "2001:db8::9", Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, Status: localv1alpha1.IPAddressStatus{Phase: localv1alpha1.IPAddressAvailable}, } - c := testClient(t, publicClass(nil), pendingClaim("public"), wrongClass, wrongFamily) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + c := testClient(t, publicClass(nil), pendingClaim(testClassName), wrongClass, wrongFamily) + reconcileClaim(t, claimReconciler(c)) - if claim := getClaim(t, c, "tenant-a", "web"); claim.Status.Phase != localv1alpha1.ClaimPending { + if claim := getClaim(t, c); claim.Status.Phase != localv1alpha1.ClaimPending { t.Errorf("phase = %q, want Pending", claim.Status.Phase) } } func TestDualClaimNeedsBothFamilies(t *testing.T) { - claim := pendingClaim("public") + claim := pendingClaim(testClassName) claim.Spec.Family = localv1alpha1.FamilyDual v4 := &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: "ip-v4"}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: "203.0.113.10", Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, @@ -270,9 +282,9 @@ func TestDualClaimNeedsBothFamilies(t *testing.T) { } c := testClient(t, publicClass(nil), claim, v4) r := claimReconciler(c) - reconcileClaim(t, r, "tenant-a", "web") + reconcileClaim(t, r) - got := getClaim(t, c, "tenant-a", "web") + got := getClaim(t, c) if got.Status.Phase != localv1alpha1.ClaimPending { t.Fatalf("phase = %q, want Pending (v6 still missing)", got.Status.Phase) } @@ -283,7 +295,7 @@ func TestDualClaimNeedsBothFamilies(t *testing.T) { v6 := &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: "ip-v6"}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: "2001:db8::10", Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, @@ -292,9 +304,9 @@ func TestDualClaimNeedsBothFamilies(t *testing.T) { if err := c.Create(context.Background(), v6); err != nil { t.Fatal(err) } - reconcileClaim(t, r, "tenant-a", "web") + reconcileClaim(t, r) - got = getClaim(t, c, "tenant-a", "web") + got = getClaim(t, c) if got.Status.Phase != localv1alpha1.ClaimBound { t.Fatalf("phase = %q, want Bound", got.Status.Phase) } @@ -304,15 +316,15 @@ func TestDualClaimNeedsBothFamilies(t *testing.T) { } func TestClaimDeletionRetainReleasesAddress(t *testing.T) { - claim := pendingClaim("public") + claim := pendingClaim(testClassName) claim.Finalizers = []string{localv1alpha1.ClaimProtectionFinalizer} addr := &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: "ip-retained"}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: "203.0.113.11", ReclaimPolicy: localv1alpha1.ReclaimRetain, - ClaimRef: &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "web", UID: "claim-uid-1"}, + ClaimRef: &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: testClaimName, UID: testClaimUID}, Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, Status: localv1alpha1.IPAddressStatus{Phase: localv1alpha1.IPAddressBound}, @@ -321,9 +333,9 @@ func TestClaimDeletionRetainReleasesAddress(t *testing.T) { if err := c.Delete(context.Background(), claim); err != nil { t.Fatal(err) } - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + reconcileClaim(t, claimReconciler(c)) - err := c.Get(context.Background(), types.NamespacedName{Namespace: "tenant-a", Name: "web"}, &localv1alpha1.IPAddressClaim{}) + err := c.Get(context.Background(), types.NamespacedName{Namespace: testNamespace, Name: testClaimName}, &localv1alpha1.IPAddressClaim{}) if !apierrors.IsNotFound(err) { t.Errorf("claim still present after finalization: %v", err) } @@ -337,15 +349,15 @@ func TestClaimDeletionRetainReleasesAddress(t *testing.T) { } func TestClaimDeletionDeletePolicyDeletesAddress(t *testing.T) { - claim := pendingClaim("public") + claim := pendingClaim(testClassName) claim.Finalizers = []string{localv1alpha1.ClaimProtectionFinalizer} addr := &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: "ip-deleted"}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: "203.0.113.12", ReclaimPolicy: localv1alpha1.ReclaimDelete, - ClaimRef: &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "web", UID: "claim-uid-1"}, + ClaimRef: &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: testClaimName, UID: testClaimUID}, Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, Status: localv1alpha1.IPAddressStatus{Phase: localv1alpha1.IPAddressBound}, @@ -354,7 +366,7 @@ func TestClaimDeletionDeletePolicyDeletesAddress(t *testing.T) { if err := c.Delete(context.Background(), claim); err != nil { t.Fatal(err) } - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + reconcileClaim(t, claimReconciler(c)) err := c.Get(context.Background(), types.NamespacedName{Name: "ip-deleted"}, &localv1alpha1.IPAddress{}) if !apierrors.IsNotFound(err) { @@ -365,32 +377,32 @@ func TestClaimDeletionDeletePolicyDeletesAddress(t *testing.T) { func TestBoundClaimSurvivesClassDeletion(t *testing.T) { // A fully bound claim must reconcile without touching its class: // deleting the class breaks neither the binding nor status upkeep. - claim := pendingClaim("public") + claim := pendingClaim(testClassName) claim.Finalizers = []string{localv1alpha1.ClaimProtectionFinalizer} - claim.Annotations = map[string]string{localv1alpha1.ProvisionerAnnotation: "metallb.drivers.local.sdn.cozystack.io"} + claim.Annotations = map[string]string{localv1alpha1.ProvisionerAnnotation: testProvisioner} claim.Status = localv1alpha1.IPAddressClaimStatus{ Phase: localv1alpha1.ClaimBound, - ClassName: "public", + ClassName: testClassName, } addr := &localv1alpha1.IPAddress{ ObjectMeta: metav1.ObjectMeta{Name: "ip-203-0-113-14"}, Spec: localv1alpha1.IPAddressSpec{ - ClassName: "public", + ClassName: testClassName, Address: "203.0.113.14", - ClaimRef: &localv1alpha1.ClaimReference{Namespace: "tenant-a", Name: "web", UID: "claim-uid-1"}, + ClaimRef: &localv1alpha1.ClaimReference{Namespace: testNamespace, Name: testClaimName, UID: testClaimUID}, Source: localv1alpha1.IPAddressSource{FromClass: &localv1alpha1.FromClassSource{}}, }, Status: localv1alpha1.IPAddressStatus{Phase: localv1alpha1.IPAddressBound}, } // Note: no IPAddressClass object at all. c := testClient(t, claim, addr) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + reconcileClaim(t, claimReconciler(c)) - got := getClaim(t, c, "tenant-a", "web") + got := getClaim(t, c) if got.Status.Phase != localv1alpha1.ClaimBound { t.Errorf("phase = %q, want Bound to survive class deletion", got.Status.Phase) } - if got.Status.ClassName != "public" { + if got.Status.ClassName != testClassName { t.Errorf("status.className = %q, want the sticky record untouched", got.Status.ClassName) } if len(got.Status.Addresses) != 1 || got.Status.Addresses[0].Address != "203.0.113.14" { @@ -402,9 +414,9 @@ func TestPendingClaimWithMissingClassRecordsClassNotFound(t *testing.T) { // An unbound claim naming a class that does not exist stays Pending // with the reason recorded; only provisioning is blocked. c := testClient(t, pendingClaim("nonexistent")) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + reconcileClaim(t, claimReconciler(c)) - claim := getClaim(t, c, "tenant-a", "web") + claim := getClaim(t, c) if claim.Status.Phase != localv1alpha1.ClaimPending { t.Errorf("phase = %q, want Pending", claim.Status.Phase) } @@ -415,18 +427,18 @@ func TestPendingClaimWithMissingClassRecordsClassNotFound(t *testing.T) { } func TestBoundClaimGoesLostWhenAddressDisappears(t *testing.T) { - claim := pendingClaim("public") + claim := pendingClaim(testClassName) claim.Finalizers = []string{localv1alpha1.ClaimProtectionFinalizer} - claim.Annotations = map[string]string{localv1alpha1.ProvisionerAnnotation: "metallb.drivers.local.sdn.cozystack.io"} + claim.Annotations = map[string]string{localv1alpha1.ProvisionerAnnotation: testProvisioner} claim.Status = localv1alpha1.IPAddressClaimStatus{ Phase: localv1alpha1.ClaimBound, - ClassName: "public", + ClassName: testClassName, Addresses: []localv1alpha1.BoundAddress{{Name: "ip-gone", Address: "203.0.113.13"}}, } c := testClient(t, publicClass(nil), claim) - reconcileClaim(t, claimReconciler(c), "tenant-a", "web") + reconcileClaim(t, claimReconciler(c)) - got := getClaim(t, c, "tenant-a", "web") + got := getClaim(t, c) if got.Status.Phase != localv1alpha1.ClaimLost { t.Errorf("phase = %q, want Lost", got.Status.Phase) } From 1fba66f51722f9d8b2bfc7bedddd61efe1c16a37 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 6 Oct 2026 11:39:17 +0200 Subject: [PATCH 4/5] Bump Go to 1.26 and the GitHub Actions to current majors CI took its Go version from go.mod, which still said 1.23.0 (end of life), while the image is built with golang:1.26. Set go 1.26 with go mod edit and keep godebug default=go1.23 so runtime defaults don't change, matching metallb-iad. Move the test and release workflows to the current major versions of the checkout, setup-go and docker actions, which run on Node 24 instead of the deprecated Node 20. Assisted-By: LLM Signed-off-by: Timofei Larkin --- .github/workflows/release.yml | 12 ++++++------ .github/workflows/test.yml | 4 ++-- go.mod | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb95a6e..e791cc4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,19 +16,19 @@ jobs: image: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: docker/setup-qemu-action@v3 - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-qemu-action@v4 + - uses: docker/setup-buildx-action@v4 - - uses: docker/login-action@v3 + - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.IMAGE }} tags: | @@ -38,7 +38,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} - id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc2e80d..9bb6941 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone the code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v7 with: go-version-file: go.mod diff --git a/go.mod b/go.mod index e89a7ce..62aaa52 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cozystack/address-controller -go 1.23.0 +go 1.26 godebug default=go1.23 From c2a22072fad34a6ee7727d2ddab4ebae01cb0597 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 6 Oct 2026 11:39:27 +0200 Subject: [PATCH 5/5] Annotate the image index so GHCR links the package to the repo The release builds a multi-arch image, and GHCR reads org.opencontainers.image.source from the index manifest; the Dockerfile LABEL only lands in the per-arch image configs. Have metadata-action emit its labels and annotations at the manifest and index levels and pass both to build-push-action, as metallb-iad does. Assisted-By: LLM Signed-off-by: Timofei Larkin --- .github/workflows/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e791cc4..0b6e3db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,10 @@ jobs: - id: meta uses: docker/metadata-action@v6 + env: + # GHCR links a multi-arch package to its repo via the index + # annotation; the Dockerfile LABEL only reaches the per-arch configs. + DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index with: images: ${{ env.IMAGE }} tags: | @@ -43,6 +47,8 @@ jobs: context: . platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} push: true cache-from: type=gha cache-to: type=gha,mode=max