From f92eb073dd1385a0c311fcf6c344aa254c868994 Mon Sep 17 00:00:00 2001 From: Sarthak Date: Fri, 16 Oct 2026 00:48:32 +0530 Subject: [PATCH] Use a default local name that works for cluster-scoped objects The default name template hashes .Object.metadata.namespace, which is missing on cluster-scoped objects, so sha3short failed and the local object was never created. For objects without a namespace the default name now hashes the cluster name instead, which also keeps names unique when the local copy is cluster-scoped and the namespace is dropped. Signed-off-by: Sarthak --- .../syncagent.kcp.io_publishedresources.yaml | 4 ++++ docs/content/publish-resources/index.md | 4 ++++ internal/sync/templating/naming.go | 8 ++++++++ internal/sync/templating/naming_test.go | 14 ++++++++++++++ sdk/apis/syncagent/v1alpha1/published_resource.go | 4 ++++ 5 files changed, 34 insertions(+) diff --git a/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml b/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml index 63c39acc..738586ca 100644 --- a/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml +++ b/deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml @@ -266,6 +266,10 @@ spec: This guarantees unique names as long as the cluster name is used for the local namespace (the default unless configured otherwise). + For cluster-scoped objects, which have no namespace, the default value is: + + "{{ .ClusterName | sha3short }}-{{ .Object.metadata.name | sha3short }}" + This value is a Go template, see the documentation for the available variables and functions. Alternatively (but deprecated), this value can be a simple string using one of the following diff --git a/docs/content/publish-resources/index.md b/docs/content/publish-resources/index.md index cb730ac9..55e38a3a 100644 --- a/docs/content/publish-resources/index.md +++ b/docs/content/publish-resources/index.md @@ -157,6 +157,10 @@ This configuration ensures that no collisions will happen: Each workspace in kcp will create a namespace on the local cluster, with a combination of namespace and name hashes used for the actual resource names. +Cluster-scoped objects have no namespace, so for them the default name is +{% raw %}`{{ .ClusterName | sha3short }}-{{ .Object.metadata.name | sha3short }}`{% endraw %} instead. This keeps +names unique even when the local copy is cluster-scoped as well. + You can override the name or namespaces rules, or both. It is your responsibility to ensure no naming conflicts can happen on the service cluster, as the agent cannot determine this automatically. diff --git a/internal/sync/templating/naming.go b/internal/sync/templating/naming.go index 32058b3e..3a1b09fc 100644 --- a/internal/sync/templating/naming.go +++ b/internal/sync/templating/naming.go @@ -53,6 +53,11 @@ var defaultNamingScheme = syncagentv1alpha1.ResourceNaming{ Name: "{{ .Object.metadata.namespace | sha3short }}-{{ .Object.metadata.name | sha3short }}", } +// defaultClusterScopedName is the default name for cluster-scoped remote objects. They have +// no namespace to hash, and their local copy can be cluster-scoped too (so the cluster name +// namespace is dropped), so the cluster name is part of the name instead. +const defaultClusterScopedName = "{{ .ClusterName | sha3short }}-{{ .Object.metadata.name | sha3short }}" + func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object *unstructured.Unstructured, clusterName logicalcluster.Name, workspacePath logicalcluster.Path) (types.NamespacedName, error) { naming := pr.Spec.Naming if naming == nil { @@ -75,6 +80,9 @@ func GenerateLocalObjectName(pr *syncagentv1alpha1.PublishedResource, object *un pattern = naming.Name if pattern == "" { pattern = defaultNamingScheme.Name + if object.GetNamespace() == "" { + pattern = defaultClusterScopedName + } } rendered, err = generateLocalObjectIdentifier(pattern, object, clusterName, workspacePath) if err != nil { diff --git a/internal/sync/templating/naming_test.go b/internal/sync/templating/naming_test.go index 471ae634..6cc1ec9e 100644 --- a/internal/sync/templating/naming_test.go +++ b/internal/sync/templating/naming_test.go @@ -51,6 +51,20 @@ func TestGenerateLocalObjectName(t *testing.T) { namingConfig: nil, expected: types.NamespacedName{Namespace: "testcluster", Name: "2928c2aa0e510f017f07-73d08a1ba188f1340dae"}, }, + { + name: "follow default naming rules for cluster-scoped objects", + clusterName: "testcluster", + remoteObject: createNewObject("objname", ""), + namingConfig: nil, + expected: types.NamespacedName{Namespace: "testcluster", Name: "4f96fcff7885055b00ad-73d08a1ba188f1340dae"}, + }, + { + name: "custom namespace pattern for cluster-scoped objects keeps the default name", + clusterName: "testcluster", + remoteObject: createNewObject("objname", ""), + namingConfig: &syncagentv1alpha1.ResourceNaming{Namespace: "foobar"}, + expected: types.NamespacedName{Namespace: "foobar", Name: "4f96fcff7885055b00ad-73d08a1ba188f1340dae"}, + }, { name: "custom static namespace pattern", clusterName: "testcluster", diff --git a/sdk/apis/syncagent/v1alpha1/published_resource.go b/sdk/apis/syncagent/v1alpha1/published_resource.go index 0e6fdde9..5dc4e5be 100644 --- a/sdk/apis/syncagent/v1alpha1/published_resource.go +++ b/sdk/apis/syncagent/v1alpha1/published_resource.go @@ -115,6 +115,10 @@ type ResourceNaming struct { // This guarantees unique names as long as the cluster name is used for the local namespace // (the default unless configured otherwise). // + // For cluster-scoped objects, which have no namespace, the default value is: + // + // "{{ .ClusterName | sha3short }}-{{ .Object.metadata.name | sha3short }}" + // // This value is a Go template, see the documentation for the available variables and functions. // // Alternatively (but deprecated), this value can be a simple string using one of the following