From 1eb9708e81088a7d5895ef8ecc04ce40a3c2f70c Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 18 Jun 2026 09:57:06 -0500 Subject: [PATCH] fix(gateway,iroh-dns): tolerate legacy slash-encoded cluster names in remaining decoders Follow-up to #199, which routed two of the downstream-resource label decoders through UpstreamClusterNameFromLabel. The same legacy slash-encoded meta.datumapis.com/upstream-cluster-name labels are decoded in four more places, which still fail the cluster lookup on every replica for resources written before #196: - gateway resource replicator - HTTPProxy controller (downstream Gateway watch) - TrafficProtectionPolicy controller (certificate-ready enqueue) - iroh-dns controller (decodeIrohClusterLabel) Route the first three through the shared helper, and strip the legacy leading slash in decodeIrohClusterLabel. Updates the iroh-dns round-trip test for the slash-less canonical form. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../gateway_resource_replicator_controller.go | 2 +- internal/controller/httpproxy_controller.go | 2 +- internal/controller/iroh_dns_controller.go | 6 +++- .../controller/iroh_dns_controller_test.go | 28 +++++++++++-------- .../trafficprotectionpolicy_controller.go | 2 +- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/internal/controller/gateway_resource_replicator_controller.go b/internal/controller/gateway_resource_replicator_controller.go index 78b9ac09..d6c4e471 100644 --- a/internal/controller/gateway_resource_replicator_controller.go +++ b/internal/controller/gateway_resource_replicator_controller.go @@ -800,7 +800,7 @@ func typedEnqueueDownstreamGVKRequest(gvk schema.GroupVersionKind) mchandler.Typ return nil } - clusterName := multicluster.ClusterName(strings.TrimPrefix(strings.ReplaceAll(clusterLabel, "_", "/"), "cluster-")) + clusterName := multicluster.ClusterName(downstreamclient.UpstreamClusterNameFromLabel(clusterLabel)) request := GVKRequest{ GVK: gvk, diff --git a/internal/controller/httpproxy_controller.go b/internal/controller/httpproxy_controller.go index 3bbe9d65..93bcfc2b 100644 --- a/internal/controller/httpproxy_controller.go +++ b/internal/controller/httpproxy_controller.go @@ -632,7 +632,7 @@ func (r *HTTPProxyReconciler) enqueueHTTPProxyForDownstreamCertificate() func(cl if upstreamNs == "" || upstreamName == "" || upstreamCluster == "" { return nil } - clusterName := multicluster.ClusterName(strings.TrimPrefix(strings.ReplaceAll(upstreamCluster, "_", "/"), "cluster-")) + clusterName := multicluster.ClusterName(downstreamclient.UpstreamClusterNameFromLabel(upstreamCluster)) return []mcreconcile.Request{{ ClusterName: clusterName, Request: ctrl.Request{NamespacedName: types.NamespacedName{Namespace: upstreamNs, Name: upstreamName}}, diff --git a/internal/controller/iroh_dns_controller.go b/internal/controller/iroh_dns_controller.go index 8d30078c..adc37565 100644 --- a/internal/controller/iroh_dns_controller.go +++ b/internal/controller/iroh_dns_controller.go @@ -264,7 +264,11 @@ func encodeIrohClusterLabel(clusterName string) string { } func decodeIrohClusterLabel(label string) string { - return strings.TrimPrefix(strings.ReplaceAll(label, "_", "/"), "cluster-") + // Tolerate labels written before #196, when cluster names carried a leading + // slash (encoded as "_"): strip it so the result matches the slash-less name + // the provider now engages. Mirrors downstreamclient.UpstreamClusterNameFromLabel. + name := strings.TrimPrefix(strings.ReplaceAll(label, "_", "/"), "cluster-") + return strings.TrimPrefix(name, "/") } func connectorEndpointZ32(connector *networkingv1alpha1.Connector) (string, error) { diff --git a/internal/controller/iroh_dns_controller_test.go b/internal/controller/iroh_dns_controller_test.go index faded244..1f4b28dd 100644 --- a/internal/controller/iroh_dns_controller_test.go +++ b/internal/controller/iroh_dns_controller_test.go @@ -202,21 +202,27 @@ func TestBuildDesiredRecordSet_RecordContents(t *testing.T) { } func TestEncodeDecodeIrohClusterLabel(t *testing.T) { - tests := []string{ - "", - "/test-project-staging", - "/zachs-project-z5pegw", - "plain-no-slashes", - "/with/multiple/slashes", - } - for _, want := range tests { - t.Run(want, func(t *testing.T) { - got := decodeIrohClusterLabel(encodeIrohClusterLabel(want)) - if got != want { + // Modern (slash-less) cluster names round-trip exactly. + for _, want := range []string{"", "test-project-staging", "zachs-project-z5pegw", "plain-no-slashes"} { + t.Run("roundtrip/"+want, func(t *testing.T) { + if got := decodeIrohClusterLabel(encodeIrohClusterLabel(want)); got != want { t.Errorf("round-trip mismatch: encode(%q) -> decode = %q", want, got) } }) } + + // Labels written before #196 carried a leading slash; decode strips it so + // they resolve to the slash-less name the provider now engages. + for label, want := range map[string]string{ + "cluster-_test-project-staging": "test-project-staging", + "cluster-_zachs-project-z5pegw": "zachs-project-z5pegw", + } { + t.Run("legacy/"+label, func(t *testing.T) { + if got := decodeIrohClusterLabel(label); got != want { + t.Errorf("decodeIrohClusterLabel(%q) = %q, want %q", label, got, want) + } + }) + } } func TestBuildDesiredRecordSet_RelayOnlyOmitsAddrEntry(t *testing.T) { diff --git a/internal/controller/trafficprotectionpolicy_controller.go b/internal/controller/trafficprotectionpolicy_controller.go index e5e8e808..1bfb4587 100644 --- a/internal/controller/trafficprotectionpolicy_controller.go +++ b/internal/controller/trafficprotectionpolicy_controller.go @@ -1337,7 +1337,7 @@ func (r *TrafficProtectionPolicyReconciler) enqueuePoliciesForCertificate() hand // cluster via mcsingle.Get (which requires clusterName == "single"). // The label value is "cluster-" with "/" replaced by "_". clusterLabel := downstreamNamespace.Labels[downstreamclient.UpstreamOwnerClusterNameLabel] - upstreamClusterName := multicluster.ClusterName(strings.TrimPrefix(strings.ReplaceAll(clusterLabel, "_", "/"), "cluster-")) + upstreamClusterName := multicluster.ClusterName(downstreamclient.UpstreamClusterNameFromLabel(clusterLabel)) logger.Info("certificate became ready, enqueueing reconcile", "certificate", cert.GetName(), "upstreamNamespace", upstreamNamespace)