Skip to content

Commit 94d2d45

Browse files
committed
implement templating for label-selector based rewrites
On-behalf-of: @SAP christoph.mewes@sap.com
1 parent 73b9b10 commit 94d2d45

File tree

2 files changed

+69
-22
lines changed

2 files changed

+69
-22
lines changed

internal/sync/syncer_related.go

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"slices"
2525
"strings"
2626

27-
"github.com/kcp-dev/logicalcluster/v3"
2827
"github.com/tidwall/gjson"
2928
"go.uber.org/zap"
3029

@@ -296,7 +295,7 @@ func resolveRelatedResourceOriginNamespaces(relatedOrigin, relatedDest syncSide,
296295
for _, namespace := range namespaces.Items {
297296
name := namespace.Name
298297

299-
destinationName, err := applySelectorRewrites(relatedOrigin, relatedDest, name, spec.Selector.Rewrite)
298+
destinationName, err := applySelectorRewrites(relatedOrigin, relatedDest, origin, name, nil, spec.Selector.Rewrite)
300299
if err != nil {
301300
return nil, fmt.Errorf("failed to rewrite origin namespace: %w", err)
302301
}
@@ -416,7 +415,7 @@ func resolveRelatedResourceObjectsInNamespace(relatedOrigin, relatedDest syncSid
416415
for _, originObject := range originObjects.Items {
417416
name := originObject.GetName()
418417

419-
destinationName, err := applySelectorRewrites(relatedOrigin, relatedDest, name, spec.Selector.Rewrite)
418+
destinationName, err := applySelectorRewrites(relatedOrigin, relatedDest, relRes.Origin, name, &originObject, spec.Selector.Rewrite)
420419
if err != nil {
421420
return nil, fmt.Errorf("failed to rewrite origin name: %w", err)
422421
}
@@ -475,12 +474,18 @@ func resolveReference(jsonData []byte, ref syncagentv1alpha1.RelatedResourceObje
475474
return strVal, nil
476475
}
477476

478-
func applySelectorRewrites(relatedOrigin, relatedDest syncSide, value string, rewrite syncagentv1alpha1.RelatedResourceSelectorRewrite) (string, error) {
477+
// applyTemplate is used after a label selector has been applied and a list of namespaces or objects
478+
// has been selected. To map these to the destination side, rewrites can be applied, and these are
479+
// first applied to all found namespaces (in which case, the value parameter here is the namespace
480+
// name and originRelatedObject is nil) and then again to all found objects (in which case the value
481+
// parameter is the object's name and originRelatedObject is set). In both cases the rewrite is supposed
482+
// to return a string.
483+
func applySelectorRewrites(relatedOrigin, relatedDest syncSide, origin syncagentv1alpha1.RelatedResourceOrigin, value string, originRelatedObject *unstructured.Unstructured, rewrite syncagentv1alpha1.RelatedResourceSelectorRewrite) (string, error) {
479484
switch {
480485
case rewrite.Regex != nil:
481486
return applyRegularExpression(value, *rewrite.Regex)
482487
case rewrite.Template != nil:
483-
return applyTemplate(relatedOrigin, relatedDest, *rewrite.Template, value)
488+
return applyTemplate(relatedOrigin, relatedDest, origin, *rewrite.Template, value, originRelatedObject)
484489
default:
485490
return "", errors.New("invalid rewrite: no mechanism configured")
486491
}
@@ -499,22 +504,25 @@ func applyRegularExpression(value string, re syncagentv1alpha1.RegularExpression
499504
return expr.ReplaceAllString(value, re.Replacement), nil
500505
}
501506

502-
func applyTemplate(relatedOrigin, relatedDest syncSide, tpl syncagentv1alpha1.TemplateExpression, value string) (string, error) {
503-
return "", errors.New("not yet implemented")
507+
func applyTemplate(relatedOrigin, relatedDest syncSide, origin syncagentv1alpha1.RelatedResourceOrigin, tpl syncagentv1alpha1.TemplateExpression, value string, originRelatedObject *unstructured.Unstructured) (string, error) {
508+
localSide, remoteSide := remapSyncSides(relatedOrigin, relatedDest, origin)
509+
ctx := templating.NewRelatedObjectLabelRewriteContext(value, localSide.object, remoteSide.object, originRelatedObject, remoteSide.clusterName, remoteSide.workspacePath)
510+
511+
return templating.Render(tpl.Template, ctx)
504512
}
505513

506514
func applyTemplateBothSides(relatedOrigin, relatedDest syncSide, origin syncagentv1alpha1.RelatedResourceOrigin, tpl syncagentv1alpha1.TemplateExpression) (originValue, destValue string, err error) {
507-
clusterName, workspacePath := clusterIdent(relatedOrigin, relatedDest, origin)
515+
_, remoteSide := remapSyncSides(relatedOrigin, relatedDest, origin)
508516

509517
// evaluate the template for the origin object side
510-
ctx := templating.NewRelatedObjectContext(relatedOrigin.object, clusterName, workspacePath)
518+
ctx := templating.NewRelatedObjectContext(relatedOrigin.object, remoteSide.clusterName, remoteSide.workspacePath)
511519
originValue, err = templating.Render(tpl.Template, ctx)
512520
if err != nil {
513521
return "", "", fmt.Errorf("failed to evaluate template on origin side: %w", err)
514522
}
515523

516524
// and once more on the other side
517-
ctx = templating.NewRelatedObjectContext(relatedDest.object, clusterName, workspacePath)
525+
ctx = templating.NewRelatedObjectContext(relatedDest.object, remoteSide.clusterName, remoteSide.workspacePath)
518526
destValue, err = templating.Render(tpl.Template, ctx)
519527
if err != nil {
520528
return "", "", fmt.Errorf("failed to evaluate template on destination side: %w", err)
@@ -526,16 +534,9 @@ func applyTemplateBothSides(relatedOrigin, relatedDest syncSide, origin syncagen
526534
// templateLabelSelector applies Go templating logic to all keys and values in the MatchLabels of
527535
// a label selector.
528536
func templateLabelSelector(relatedOrigin, relatedDest syncSide, origin syncagentv1alpha1.RelatedResourceOrigin, selector *metav1.LabelSelector) (*metav1.LabelSelector, error) {
529-
clusterName, workspacePath := clusterIdent(relatedOrigin, relatedDest, origin)
530-
531-
localObject := relatedOrigin.object
532-
remoteObject := relatedDest.object
533-
if origin == syncagentv1alpha1.RelatedResourceOriginKcp {
534-
localObject = relatedDest.object
535-
remoteObject = relatedOrigin.object
536-
}
537+
localSide, remoteSide := remapSyncSides(relatedOrigin, relatedDest, origin)
537538

538-
ctx := templating.NewRelatedObjectLabelContext(localObject, remoteObject, clusterName, workspacePath)
539+
ctx := templating.NewRelatedObjectLabelContext(localSide.object, remoteSide.object, remoteSide.clusterName, remoteSide.workspacePath)
539540

540541
newMatchLabels := map[string]string{}
541542
for key, value := range selector.MatchLabels {
@@ -565,10 +566,10 @@ func templateLabelSelector(relatedOrigin, relatedDest syncSide, origin syncagent
565566
return selector, nil
566567
}
567568

568-
func clusterIdent(relatedOrigin, relatedDest syncSide, origin syncagentv1alpha1.RelatedResourceOrigin) (logicalcluster.Name, logicalcluster.Path) {
569+
func remapSyncSides(relatedOrigin, relatedDest syncSide, origin syncagentv1alpha1.RelatedResourceOrigin) (localSide, remoteSide syncSide) {
569570
if origin == syncagentv1alpha1.RelatedResourceOriginKcp {
570-
return relatedOrigin.clusterName, relatedOrigin.workspacePath
571+
return relatedOrigin, relatedDest
571572
}
572573

573-
return relatedDest.clusterName, relatedDest.workspacePath
574+
return relatedDest, relatedOrigin
574575
}

internal/sync/templating/related.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,49 @@ func NewRelatedObjectLabelContext(localObject, remoteObject *unstructured.Unstru
7171
ClusterPath: clusterPath,
7272
}
7373
}
74+
75+
// relatedObjectLabelRewriteContext is the data available to Go templates when
76+
// mapping the found namespace names and objects names from having evaluated a
77+
// label selector previously.
78+
type relatedObjectLabelRewriteContext struct {
79+
// Value is either the a found namespace name (when a label selector was
80+
// used to select the source namespaces for related objects) or the name of
81+
// a found object (when a label selector was used to find objects). In the
82+
// former case, the template should return the new namespace to use on the
83+
// destination side, in the latter case it should return the new object name
84+
// to use on the destination side.
85+
Value string
86+
// When a rewrite is used to rewrite object names, RelatedObject is the
87+
// original related object (found on the origin side). This enables you to
88+
// ignore the given Value entirely and just select anything from the object
89+
// itself.
90+
// RelatedObject is nil when the rewrite is performed for a namespace.
91+
RelatedObject map[string]any
92+
// LocalObject ist the primary object copy on the local side of the sync
93+
// (i.e. on the service cluster).
94+
LocalObject map[string]any
95+
// RemoteObject is the primary object original, in kcp.
96+
RemoteObject map[string]any
97+
// ClusterName is the internal cluster identifier (e.g. "34hg2j4gh24jdfgf")
98+
// of the kcp workspace that the synchronization is currently processing
99+
// (where the remote object exists).
100+
ClusterName logicalcluster.Name
101+
// ClusterPath is the workspace path (e.g. "root:customer:projectx").
102+
ClusterPath logicalcluster.Path
103+
}
104+
105+
func NewRelatedObjectLabelRewriteContext(value string, localObject, remoteObject, relatedObject *unstructured.Unstructured, clusterName logicalcluster.Name, clusterPath logicalcluster.Path) relatedObjectLabelRewriteContext {
106+
ctx := relatedObjectLabelRewriteContext{
107+
Value: value,
108+
LocalObject: localObject.Object,
109+
RemoteObject: remoteObject.Object,
110+
ClusterName: clusterName,
111+
ClusterPath: clusterPath,
112+
}
113+
114+
if relatedObject != nil {
115+
ctx.RelatedObject = relatedObject.Object
116+
}
117+
118+
return ctx
119+
}

0 commit comments

Comments
 (0)