@@ -137,20 +137,7 @@ Since the Sync Agent ingests resources from many different Kubernetes clusters (
137137them onto a single cluster, resources have to be renamed to prevent collisions and also follow the
138138conventions of whatever tooling ultimately processes the resources locally.
139139
140- The renaming is configured in `spec.naming`. In there, renaming patterns are configured, where
141- pre-defined placeholders can be used, for example `foo-$placeholder`. The following placeholders
142- are available :
143-
144- * `$remoteClusterName` – the workspace's cluster name (e.g. "1084s8ceexsehjm2")
145- * `$remoteNamespace` – the original namespace used by the consumer inside the workspace
146- * `$remoteNamespaceHash` – first 20 hex characters of the SHA-1 hash of `$remoteNamespace`
147- * `$remoteName` – the original name of the object inside the workspace (rarely used to construct
148- local namespace names)
149- * `$remoteNameHash` – first 20 hex characters of the SHA-1 hash of `$remoteName`
150-
151- If nothing is configured, the default ensures that no collisions will happen : Each workspace in
152- kcp will create a namespace on the local cluster, with a combination of namespace and name hashes
153- used for the actual resource names.
140+ This snippet shows the implicit default configuration :
154141
155142` ` ` yaml
156143apiVersion: syncagent.kcp.io/v1alpha1
@@ -160,11 +147,60 @@ metadata:
160147spec:
161148 resource: ...
162149 naming:
163- # This is the implicit default configuration.
164- namespace: "$remoteClusterName"
165- name: "cert-$remoteNamespaceHash-$remoteNameHash"
150+ namespace: '{{ .ClusterName }}'
151+ name: '{{ .Object.metadata.namespace | sha3short }}-{{ .Object.metadata.name | sha3short }}'
152+ ` ` `
153+
154+ This configuration ensures that no collisions will happen : Each workspace in
155+ kcp will create a namespace on the local cluster, with a combination of namespace and name hashes
156+ used for the actual resource names.
157+
158+ You can override the name or namespaces rules, or both. It is your responsibility to ensure no
159+ naming conflicts can happen on the service cluster, as the agent cannot determine this automatically.
160+
161+ # ### Templating
162+
163+ In `spec.naming`, [Go template expressions](https://pkg.go.dev/text/template) are used to construct
164+ the desired name of the object's copy. In the templates used here, the following data is injected by
165+ the agent :
166+
167+ ` ` ` go
168+ type localObjectNamingContext struct {
169+ // Object is the full remote object found in a kcp workspace.
170+ Object map[string]any
171+ // ClusterName is the internal cluster identifier (e.g. "34hg2j4gh24jdfgf").
172+ ClusterName logicalcluster.Name
173+ // ClusterPath is the workspace path (e.g. "root:customer:projectx").
174+ ClusterPath logicalcluster.Path
175+ }
166176` ` `
167177
178+ For more details about the templating, see the [Templating](templating.md) documentation.
179+
180+ # ### Legacy Naming Rules
181+
182+ Go templates for naming rules have been added in v0.3 of the agent. Previous versions used a
183+ ` $variable` -based approach, which since has been deprecated. You are encouraged to migrate your
184+ PublishedResources over to Go templates.
185+
186+ The following table shows the available variables and their modern replacements :
187+
188+ | Deprecated Variable | Go Template | Description |
189+ | ---------------------- | ----------------------------------------------- | ----------- |
190+ | `$remoteClusterName` | `{{ .ClusterName }}` | the workspace's cluster name (e.g. "1084s8ceexsehjm2") |
191+ | `$remoteNamespace` | `{{ .Object.metadata.namespace }}` | the original namespace used by the consumer inside the workspace |
192+ | `$remoteNamespaceHash` | `{{ .Object.metadata.namespace \| shortHash }}` | first 20 hex characters of the SHA-1 hash of `$remoteNamespace` |
193+ | `$remoteName` | `{{ .Object.metadata.name }}` | the original name of the object inside the workspace (rarely used to construct local namespace names) |
194+ | `$remoteNameHash` | `{{ .Object.metadata.name \| shortHash }}` | first 20 hex characters of the SHA-1 hash of `$remoteName` |
195+
196+ Note that `ClusterPath` was never available in `$variable` form.
197+
198+ Note also that the `shortHash` function exists only for backwards compatibility with the old
199+ ` $variable` syntax. The new default is to use SHA-3 instead (via the `sha3short` function). When
200+ migrating from the old syntax, you can use the `shortHash` function to ensure new objects are placed
201+ in the old locations. New setups should however use explicitly named functions for hashing, like
202+ ` sha3sum` or `sha3short`. `sha3short` takes an optional length parameter that defaults to 20.
203+
168204# ## Mutation
169205
170206Besides projecting the type meta, changes to object contents are also nearly always required.
@@ -216,7 +252,7 @@ usual path, without a leading dot.
216252` ` ` yaml
217253template:
218254 path: "json.path[expression]"
219- template: "{{ .LocalObject.ObjectMeta.Namespace }}"
255+ template: "{{ .LocalObject.metadata.namespace }}"
220256` ` `
221257{% endraw %}
222258
@@ -275,7 +311,7 @@ PublishedResource) and the path will yield 2 ready to use values (`my-secret` an
275311The value selected by the path expression must be a string (or number, but it will be coalesced into
276312a string) and can then be further adjusted by applying a regular expression to it.
277313
278- References can only ever select 1 related object. Their upside is that they are simple to understand
314+ References can only ever select one related object. Their upside is that they are simple to understand
279315and easy to use, but require a "link" in the primary object that would point to the related object.
280316
281317Here's an example on how to use references to locate the related object.
@@ -295,7 +331,7 @@ spec:
295331 # this is where our CA and Issuer live in this example
296332 namespace: kube-system
297333 # need to adjust it to prevent collions (normally clustername is the namespace)
298- name: "$remoteClusterName-$remoteNamespaceHash-$remoteNameHash "
334+ name: "{{ .ClusterName }}-{{ .Object.metadata.namespace | sha3short }}-{{ .Object.metadata.name | sha3short }} "
299335
300336 related:
301337 - # unique name for this related resource. The name must be unique within
@@ -313,7 +349,7 @@ spec:
313349
314350 # configure where in the parent object we can find the child object
315351 object:
316- # Object can use either reference, labelSelector or expressions . In this
352+ # Object can use either reference, labelSelector or template . In this
317353 # example we use references.
318354 reference:
319355 # This path is evaluated in both the local and remote objects, to figure out
@@ -332,6 +368,59 @@ spec:
332368 # replacement: '...'
333369` ` `
334370
371+ # ### Templates
372+
373+ Similar to references, [Go templates](https://pkg.go.dev/text/template) can also be used to determine
374+ the names of related objects on both sides of the sync. In fact, templates can be thought of as more
375+ powerful references since they allow for minimal logic to be embedded in them. Templates also do not
376+ necessarily have to select a value from the object (like a reference does), but can use any kind of
377+ logic to determine the names.
378+
379+ Like references, templates can also only be used to select a single object per related resource.
380+
381+ A template gets the following data injected into it :
382+
383+ ` ` ` go
384+ type localObjectNamingContext struct {
385+ // Side is set to either one of the possible origin values to indicate for
386+ // which cluster the template is currently being evaluated for.
387+ Side syncagentv1alpha1.RelatedResourceOrigin
388+ // Object is the primary object belonging to the related object. Since related
389+ // object templates are evaluated twice (once for the origin side and once
390+ // for the destination side), object is the primary object on the side the
391+ // template is evaluated for.
392+ Object map[string]any
393+ // ClusterName is the internal cluster identifier (e.g. "34hg2j4gh24jdfgf")
394+ // of the kcp workspace that the synchronization is currently processing. This
395+ // value is set for both evaluations, regardless of side.
396+ ClusterName logicalcluster.Name
397+ // ClusterPath is the workspace path (e.g. "root:customer:projectx"). This
398+ // value is set for both evaluations, regardless of side.
399+ ClusterPath logicalcluster.Path
400+ }
401+ ` ` `
402+
403+ In the simplest form, a template can replace a reference :
404+
405+ * reference: `.spec.secretName`
406+ * Go template: `{{ .Object.spec.secretName }}`
407+
408+ Just like with references, the configured template is evaluated twice, once for each side of the
409+ synchronization. You can use the `Side` variable to allow for fully customized names on each side :
410+
411+ ` ` ` yaml
412+ spec:
413+ ...
414+ related:
415+ - identifier: tls-secret
416+ # ..omitting other fields..
417+ object:
418+ template:
419+ template: ` {{ if eq .Side "kcp" }}name-in-kcp{{ else }}name-on-service-cluster{{ end }}`
420+ ```
421+
422+ See [ Templating] ( templating.md ) for more information on how to use templates in PublishedResources.
423+
335424#### Label Selectors
336425
337426In some cases, the primary object does not have a link to its child/children objects. In these cases,
@@ -362,7 +451,7 @@ is assumed. However you can actually also use label selectors to find the origin
362451dynamically. So you can configure two label selectors, and then agent will first use the namespace
363452selector to find all applicable namespaces, and then use the other label selector _ in each of the
364453applicable namespaces_ to finally locate the related objects. How useful this is depends a lot on
365- how crazy the underlying operators on the service clusters are.
454+ how peculiar the underlying operators on the service clusters are.
366455
367456Here is an example on how to use label selectors:
368457
@@ -379,7 +468,7 @@ spec:
379468
380469 naming :
381470 namespace : kube-system
382- name: "$remoteClusterName-$remoteNamespaceHash-$remoteNameHash "
471+ name : " {{ .ClusterName }}-{{ .Object.metadata.namespace | sha3short }}-{{ .Object.metadata.name | sha3short }} "
383472
384473 related :
385474 - identifier : tls-secrets
@@ -399,9 +488,18 @@ spec:
399488 matchLabels :
400489 my-key : my-value
401490 another : pair
491+ # Within matchLabels, keys and values are treated as Go templates.
492+ # In this example, since the Secret originates on the service cluster
493+ # (see "origin" above), we use LocalObject to determine the value
494+ # for the selector. In case the object was heavily mutated during the
495+ # sync, this will give access to the mutated values on the service
496+ # cluster side.
497+ ' {{ shasum "test" }} ' : ' {{ .LocalObject.spec.username }}'
402498
403499 # You also need to provide rules on how objects found by this selector
404- # should be named on the destination side of the sync.
500+ # should be named on the destination side of the sync. You can choose
501+ # to define a rewrite rule that keeps the original name from the origin
502+ # side, but this may leak undesirable internals to the users.
405503 # Rewrites are either using regular expressions or templated strings,
406504 # never both.
407505 # The rewrite config is applied to each individual found object.
@@ -427,13 +525,84 @@ spec:
427525 # replacement: '...'
428526```
429527
430- # ### Templates
528+ There are two possible usages of Go templates when using label selectors. See [ Templating] ( templating.md )
529+ for more information on how to use templates in PublishedResources in general.
530+
531+ ##### Selector Templates
532+
533+ Each template rendered as part of a ` matchLabels ` selector gets the following data injected:
534+
535+ ``` go
536+ type relatedObjectLabelContext struct {
537+ // LocalObject is the primary object copy on the local side of the sync
538+ // (i.e. on the service cluster).
539+ LocalObject map [string ]any
540+ // RemoteObject is the primary object original, in kcp.
541+ RemoteObject map [string ]any
542+ // ClusterName is the internal cluster identifier (e.g. "34hg2j4gh24jdfgf")
543+ // of the kcp workspace that the synchronization is currently processing
544+ // (where the remote object exists).
545+ ClusterName logicalcluster.Name
546+ // ClusterPath is the workspace path (e.g. "root:customer:projectx").
547+ ClusterPath logicalcluster.Path
548+ }
549+ ```
550+
551+ Note that in contrast to the ` template ` way of selecting objects, the templates here in the label
552+ selector are only evaluated once, on the origin side of the sync. The names of the destination side
553+ are determined using the rewrite mechanism (which might also be a Go template, see next section).
554+
555+ ##### Rewrite Rules
556+
557+ Each found related object on the origin side needs to have its own name on the destination side. To
558+ map from the origin to the destination side, regular expressions (see example snippet) or Go
559+ templatess can be used.
560+
561+ If a template is configured, it is evaluated once for every found related object. The template gets
562+ the following data injected into it:
563+
564+ ``` go
565+ type relatedObjectLabelRewriteContext struct {
566+ // Value is either the a found namespace name (when a label selector was
567+ // used to select the source namespaces for related objects) or the name of
568+ // a found object (when a label selector was used to find objects). In the
569+ // former case, the template should return the new namespace to use on the
570+ // destination side, in the latter case it should return the new object name
571+ // to use on the destination side.
572+ Value string
573+ // When a rewrite is used to rewrite object names, RelatedObject is the
574+ // original related object (found on the origin side). This enables you to
575+ // ignore the given Value entirely and just select anything from the object
576+ // itself.
577+ // RelatedObject is nil when the rewrite is performed for a namespace.
578+ RelatedObject map [string ]any
579+ // LocalObject is the primary object copy on the local side of the sync
580+ // (i.e. on the service cluster).
581+ LocalObject map [string ]any
582+ // RemoteObject is the primary object original, in kcp.
583+ RemoteObject map [string ]any
584+ // ClusterName is the internal cluster identifier (e.g. "34hg2j4gh24jdfgf")
585+ // of the kcp workspace that the synchronization is currently processing
586+ // (where the remote object exists).
587+ ClusterName logicalcluster.Name
588+ // ClusterPath is the workspace path (e.g. "root:customer:projectx").
589+ ClusterPath logicalcluster.Path
590+ }
591+ ```
431592
432- The third option to configure how to find/create related objects are templates. These are simple
433- Go template strings (like `{% raw %}{{ .Variable }}{% endraw %}`) that allow to easily configure static values with a
434- sprinkling of dynamic values .
593+ Regarding ` Value ` : The agent allows to individually configure rules for finding object _ names _ and
594+ object _ namespaces _ . Often the namespace is not configured because the related objects live in the
595+ same namespace as their owning, primary object .
435596
436- This feature has not been fully implemented yet.
597+ When a label selector is configured to find namespaces, the rewrite template will be evaluated once
598+ for each found namespace. In this case the ` .Value ` is the name of the found namespace. Remember, the
599+ template's job is to map the found namespace to the new namespace on the destination side of the sync.
600+
601+ Once the namespaces have been determined, the agent will look for matching objects in each namespace
602+ individually. For each namespace it will again follow the configured source, may it be a selector,
603+ template or reference. If again a label selector is used, it will be applied in each namespace and
604+ the configured rewrite rule will be evaluated once per found object. In this case, ` .Value ` is the
605+ name of found object.
437606
438607## Examples
439608
@@ -466,28 +635,20 @@ spec:
466635 # this is where our CA and Issuer live in this example
467636 namespace : kube-system
468637 # need to adjust it to prevent collions (normally clustername is the namespace)
469- name: "$remoteClusterName-$remoteNamespaceHash-$remoteNameHash "
638+ name : " {{ .ClusterName }}-{{ .Object.metadata.namespace | sha3short }}-{{ .Object.metadata.name | sha3short }} "
470639
471640 related :
472641 - origin : service # service or kcp
473- kind: Secret # for now, only "Secret" and "ConfigMap" are supported;
474- # there is no GVK projection for related resources
642+ kind : Secret # for now, only "Secret" and "ConfigMap" are supported;
643+ # there is no GVK projection for related resources
475644
476645 # configure where in the parent object we can find
477646 # the name/namespace of the related resource (the child)
478- reference:
479- name:
480- # This path is evaluated in both the local and remote objects, to figure out
481- # the local and remote names for the related object. This saves us from having
482- # to remember mutated fields before their mutation (similar to the last-known
483- # annotation).
484- path: spec.secretName
485- # namespace part is optional; if not configured,
486- # Sync Agent assumes the same namespace as the owning resource
487- # namespace:
488- # path: spec.secretName
489- # regex:
490- # pattern: '...'
491- # replacement: '...'
647+ object :
648+ # This template is evaluated in both the local and remote objects, to figure out
649+ # the local and remote names for the related object. This saves us from having
650+ # to remember mutated fields before their mutation (similar to the last-known
651+ # annotation).
652+ template :
653+ template : ' {{ .Object.spec.secretName }}'
492654` ` `
493-
0 commit comments