Skip to content

Commit 997725a

Browse files
authored
Merge pull request #15 from kcp-dev/add-cluster-paths
✨ Add workspace paths as annotations to synced objects
2 parents 9edf625 + 4b4d9d7 commit 997725a

File tree

16 files changed

+183
-79
lines changed

16 files changed

+183
-79
lines changed

deploy/crd/kcp.io/syncagent.kcp.io_publishedresources.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ spec:
4747
PublishedResourceSpec describes the desired resource publication from a service
4848
cluster to kcp.
4949
properties:
50+
enableWorkspacePaths:
51+
description: |-
52+
EnableWorkspacePaths toggles whether the Sync Agent will not just store the kcp
53+
cluster name as a label on each locally synced object, but also the full workspace
54+
path. This is optional because it requires additional requests to kcp and
55+
should only be used if the workspace path is of interest on the
56+
service cluster side.
57+
type: boolean
5058
filter:
5159
description: |-
5260
If specified, the filter will be applied to the resources in a workspace

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ require (
7070
github.com/mattn/go-isatty v0.0.20 // indirect
7171
github.com/mitchellh/copystructure v1.2.0 // indirect
7272
github.com/mitchellh/go-homedir v1.1.0 // indirect
73-
github.com/mitchellh/mapstructure v1.3.3 // indirect
73+
github.com/mitchellh/mapstructure v1.4.1 // indirect
7474
github.com/mitchellh/reflectwalk v1.0.2 // indirect
7575
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
7676
github.com/modern-go/reflect2 v1.0.2 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
229229
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
230230
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
231231
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
232-
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
233-
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
232+
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
233+
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
234234
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
235235
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
236236
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

internal/controller/sync/controller.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ import (
3030
"github.com/kcp-dev/api-syncagent/internal/sync"
3131
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
3232

33+
kcpcore "github.com/kcp-dev/kcp/sdk/apis/core"
34+
kcpdevcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
35+
3336
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
37+
"k8s.io/apimachinery/pkg/types"
3438
"k8s.io/utils/ptr"
3539
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
3640
"sigs.k8s.io/controller-runtime/pkg/cluster"
@@ -52,6 +56,7 @@ type Reconciler struct {
5256
log *zap.SugaredLogger
5357
syncer *sync.ResourceSyncer
5458
remoteDummy *unstructured.Unstructured
59+
pubRes *syncagentv1alpha1.PublishedResource
5560
}
5661

5762
// Create creates a new controller and importantly does *not* add it to the manager,
@@ -99,6 +104,7 @@ func Create(
99104
log: log,
100105
remoteDummy: remoteDummy,
101106
syncer: syncer,
107+
pubRes: pubRes,
102108
}
103109

104110
ctrlOptions := controller.Options{
@@ -152,8 +158,22 @@ func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (
152158
return reconcile.Result{}, nil
153159
}
154160

161+
syncContext := sync.NewContext(ctx, wsCtx)
162+
163+
// if desired, fetch the cluster path as well (some downstream service providers might make use of it,
164+
// but since it requires an additional permission claim, it's optional)
165+
if r.pubRes.Spec.EnableWorkspacePaths {
166+
lc := &kcpdevcorev1alpha1.LogicalCluster{}
167+
if err := r.vwClient.Get(wsCtx, types.NamespacedName{Name: kcpdevcorev1alpha1.LogicalClusterName}, lc); err != nil {
168+
return reconcile.Result{}, fmt.Errorf("failed to retrieve remote logicalcluster: %w", err)
169+
}
170+
171+
path := lc.Annotations[kcpcore.LogicalClusterPathAnnotationKey]
172+
syncContext = syncContext.WithWorkspacePath(logicalcluster.NewPath(path))
173+
}
174+
155175
// sync main object
156-
requeue, err := r.syncer.Process(sync.NewContext(ctx, wsCtx), remoteObj)
176+
requeue, err := r.syncer.Process(syncContext, remoteObj)
157177
if err != nil {
158178
return reconcile.Result{}, err
159179
}

internal/controller/syncmanager/lifecycle/cluster.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ import (
2727
"github.com/kcp-dev/logicalcluster/v3"
2828
"go.uber.org/zap"
2929

30+
kcpdevcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
31+
3032
"k8s.io/apimachinery/pkg/api/meta"
33+
"k8s.io/apimachinery/pkg/runtime"
3134
"k8s.io/client-go/rest"
3235
"sigs.k8s.io/controller-runtime/pkg/cache"
3336
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -100,18 +103,18 @@ func (c clusterRoundTripper) RoundTrip(req *http.Request) (*http.Response, error
100103
var apiRegex = regexp.MustCompile(`(/api/|/apis/)`)
101104

102105
// generatePath formats the request path to target the specified cluster.
103-
func generatePath(originalPath string, clusterPath logicalcluster.Path) string {
106+
func generatePath(originalPath string, workspacePath logicalcluster.Path) string {
104107
// If the originalPath already has cluster.Path() then the path was already modifed and no change needed
105-
if strings.Contains(originalPath, clusterPath.RequestPath()) {
108+
if strings.Contains(originalPath, workspacePath.RequestPath()) {
106109
return originalPath
107110
}
108111
// If the originalPath has /api/ or /apis/ in it, it might be anywhere in the path, so we use a regex to find and
109112
// replaces /api/ or /apis/ with $cluster/api/ or $cluster/apis/
110113
if apiRegex.MatchString(originalPath) {
111-
return apiRegex.ReplaceAllString(originalPath, fmt.Sprintf("%s$1", clusterPath.RequestPath()))
114+
return apiRegex.ReplaceAllString(originalPath, fmt.Sprintf("%s$1", workspacePath.RequestPath()))
112115
}
113116
// Otherwise, we're just prepending /clusters/$name
114-
path := clusterPath.RequestPath()
117+
path := workspacePath.RequestPath()
115118
// if the original path is relative, add a / separator
116119
if len(originalPath) > 0 && originalPath[0] != '/' {
117120
path += "/"
@@ -130,7 +133,14 @@ func NewCluster(address string, baseRestConfig *rest.Config) (*Cluster, error) {
130133
return newClusterAwareRoundTripper(rt)
131134
})
132135

136+
scheme := runtime.NewScheme()
137+
138+
if err := kcpdevcorev1alpha1.AddToScheme(scheme); err != nil {
139+
return nil, fmt.Errorf("failed to register scheme %s: %w", kcpdevcorev1alpha1.SchemeGroupVersion, err)
140+
}
141+
133142
clusterObj, err := cluster.New(config, func(o *cluster.Options) {
143+
o.Scheme = scheme
134144
o.NewCache = kcp.NewClusterAwareCache
135145
o.NewAPIReader = kcp.NewClusterAwareAPIReader
136146
o.NewClient = kcp.NewClusterAwareClient

internal/sync/context.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ package sync
1919
import (
2020
"context"
2121

22+
"github.com/kcp-dev/logicalcluster/v3"
23+
2224
"sigs.k8s.io/controller-runtime/pkg/kontext"
2325
)
2426

2527
type Context struct {
26-
clusterName string
27-
local context.Context
28-
remote context.Context
28+
clusterName logicalcluster.Name
29+
workspacePath logicalcluster.Path
30+
local context.Context
31+
remote context.Context
2932
}
3033

3134
func NewContext(local, remote context.Context) Context {
@@ -35,8 +38,17 @@ func NewContext(local, remote context.Context) Context {
3538
}
3639

3740
return Context{
38-
clusterName: string(clusterName),
41+
clusterName: clusterName,
3942
local: local,
4043
remote: remote,
4144
}
4245
}
46+
47+
func (c *Context) WithWorkspacePath(path logicalcluster.Path) Context {
48+
return Context{
49+
clusterName: c.clusterName,
50+
workspacePath: path,
51+
local: c.local,
52+
remote: c.remote,
53+
}
54+
}

internal/sync/context_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestNewContext(t *testing.T) {
3131

3232
combinedCtx := NewContext(context.Background(), ctx)
3333

34-
if combinedCtx.clusterName != clusterName.String() {
34+
if combinedCtx.clusterName != clusterName {
3535
t.Fatalf("Expected function to recognize the cluster name in the context, but got %q", combinedCtx.clusterName)
3636
}
3737
}

internal/sync/meta.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package sync
1919
import (
2020
"context"
2121

22+
"github.com/kcp-dev/logicalcluster/v3"
2223
"go.uber.org/zap"
2324

2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -65,7 +66,7 @@ func ensureFinalizer(ctx context.Context, log *zap.SugaredLogger, client ctrlrun
6566
finalizers.Insert(deletionFinalizer)
6667
obj.SetFinalizers(sets.List(finalizers))
6768

68-
log.Debugw("Adding finalizer…", "on", newObjectKey(obj, ""), "finalizer", finalizer)
69+
log.Debugw("Adding finalizer…", "on", newObjectKey(obj, "", logicalcluster.None), "finalizer", finalizer)
6970
if err := client.Patch(ctx, obj, ctrlruntimeclient.MergeFrom(original)); err != nil {
7071
return false, err
7172
}
@@ -84,7 +85,7 @@ func removeFinalizer(ctx context.Context, log *zap.SugaredLogger, client ctrlrun
8485
finalizers.Delete(deletionFinalizer)
8586
obj.SetFinalizers(sets.List(finalizers))
8687

87-
log.Debugw("Removing finalizer…", "on", newObjectKey(obj, ""), "finalizer", finalizer)
88+
log.Debugw("Removing finalizer…", "on", newObjectKey(obj, "", logicalcluster.None), "finalizer", finalizer)
8889
if err := client.Patch(ctx, obj, ctrlruntimeclient.MergeFrom(original)); err != nil {
8990
return false, err
9091
}
@@ -93,16 +94,18 @@ func removeFinalizer(ctx context.Context, log *zap.SugaredLogger, client ctrlrun
9394
}
9495

9596
type objectKey struct {
96-
Cluster string
97-
Namespace string
98-
Name string
97+
ClusterName logicalcluster.Name
98+
WorkspacePath logicalcluster.Path
99+
Namespace string
100+
Name string
99101
}
100102

101-
func newObjectKey(obj metav1.Object, clusterName string) objectKey {
103+
func newObjectKey(obj metav1.Object, clusterName logicalcluster.Name, workspacePath logicalcluster.Path) objectKey {
102104
return objectKey{
103-
Cluster: clusterName,
104-
Namespace: obj.GetNamespace(),
105-
Name: obj.GetName(),
105+
ClusterName: clusterName,
106+
WorkspacePath: workspacePath,
107+
Namespace: obj.GetNamespace(),
108+
Name: obj.GetName(),
106109
}
107110
}
108111

@@ -111,8 +114,8 @@ func (k objectKey) String() string {
111114
if k.Namespace != "" {
112115
result = k.Namespace + "/" + result
113116
}
114-
if k.Cluster != "" {
115-
result = k.Cluster + "|" + result
117+
if k.ClusterName != "" {
118+
result = string(k.ClusterName) + "|" + result
116119
}
117120

118121
return result
@@ -123,17 +126,27 @@ func (k objectKey) Key() string {
123126
if k.Namespace != "" {
124127
result = k.Namespace + "_" + result
125128
}
126-
if k.Cluster != "" {
127-
result = k.Cluster + "_" + result
129+
if k.ClusterName != "" {
130+
result = string(k.ClusterName) + "_" + result
128131
}
129132

130133
return result
131134
}
132135

133136
func (k objectKey) Labels() labels.Set {
134137
return labels.Set{
135-
remoteObjectClusterLabel: k.Cluster,
138+
remoteObjectClusterLabel: string(k.ClusterName),
136139
remoteObjectNamespaceLabel: k.Namespace,
137140
remoteObjectNameLabel: k.Name,
138141
}
139142
}
143+
144+
func (k objectKey) Annotations() labels.Set {
145+
s := labels.Set{}
146+
147+
if !k.WorkspacePath.Empty() {
148+
s[remoteObjectWorkspacePathAnnotation] = k.WorkspacePath.String()
149+
}
150+
151+
return s
152+
}

internal/sync/meta_test.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package sync
1919
import (
2020
"testing"
2121

22+
"github.com/kcp-dev/logicalcluster/v3"
23+
2224
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2325
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2426
)
@@ -33,9 +35,10 @@ func createNewObject(name, namespace string) metav1.Object {
3335

3436
func TestObjectKey(t *testing.T) {
3537
testcases := []struct {
36-
object metav1.Object
37-
clusterName string
38-
expected string
38+
object metav1.Object
39+
clusterName logicalcluster.Name
40+
workspacePath logicalcluster.Path
41+
expected string
3942
}{
4043
{
4144
object: createNewObject("test", ""),
@@ -57,11 +60,17 @@ func TestObjectKey(t *testing.T) {
5760
clusterName: "abc123",
5861
expected: "abc123|namespace/test",
5962
},
63+
{
64+
object: createNewObject("test", "namespace"),
65+
clusterName: "abc123",
66+
workspacePath: logicalcluster.NewPath("this:should:not:appear:in:the:key"),
67+
expected: "abc123|namespace/test",
68+
},
6069
}
6170

6271
for _, testcase := range testcases {
6372
t.Run("", func(t *testing.T) {
64-
key := newObjectKey(testcase.object, testcase.clusterName)
73+
key := newObjectKey(testcase.object, testcase.clusterName, testcase.workspacePath)
6574

6675
if stringified := key.String(); stringified != testcase.expected {
6776
t.Fatalf("Expected %q but got %q.", testcase.expected, stringified)

0 commit comments

Comments
 (0)