Skip to content

Commit 02bcbdc

Browse files
authored
Merge pull request #541 from mjudeikis/permissions.controller.kcp
Add RBAC permissions controller for kcp
2 parents 57884df + 5449b9d commit 02bcbdc

6 files changed

Lines changed: 346 additions & 24 deletions

File tree

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,8 @@ image-local: export PLATFORMS ?= linux/$(shell go env GOARCH)
435435
image-local: ## Build images locally (single platform, --load) for the current commit
436436
@LDFLAGS="$(LDFLAGS)" hack/build-image.sh
437437

438+
# Dev example:
439+
# make IMAGE_REPO=ghcr.io/<user> IMAGE_TAGS=v$(date -u +%Y%m%d) image-push
438440
.PHONY: image-push
439441
image-push: export PLATFORMS = $(IMAGE_PUSH_PLATFORMS)
440442
image-push: export PUSH = true

backend/provider/kcp/controllers/apibindingtemplate/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func New(
7373
opts: opts,
7474
ignorePrefixes: ignorePrefixes,
7575
scheme: scheme,
76-
vwCache: shared.NewVWClientCache(baseConfig, scheme),
76+
vwCache: shared.NewVWClientCache(baseConfig),
7777
}
7878

7979
return r, nil

backend/provider/kcp/controllers/apiresourceschema/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func New(
6363
manager: mgr,
6464
opts: opts,
6565
scheme: scheme,
66-
vwCache: shared.NewVWClientCache(baseConfig, scheme),
66+
vwCache: shared.NewVWClientCache(baseConfig),
6767
}, nil
6868
}
6969

backend/provider/kcp/controllers/shared/vw.go

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import (
2525
"sync"
2626

2727
apisv1alpha1 "github.com/kcp-dev/sdk/apis/apis/v1alpha1"
28+
apisv1alpha1client "github.com/kcp-dev/sdk/client/clientset/versioned/typed/apis/v1alpha1"
2829
"k8s.io/apimachinery/pkg/api/errors"
29-
"k8s.io/apimachinery/pkg/runtime"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3031
"k8s.io/client-go/rest"
3132
"sigs.k8s.io/controller-runtime/pkg/client"
3233
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -57,27 +58,27 @@ func ExtractClusterID(clusterConfig *rest.Config) (string, error) {
5758
return pathParts[5], nil
5859
}
5960

60-
// VWClientCache caches controller-runtime clients keyed by cluster ID to avoid
61-
// creating a new client on every schema lookup.
61+
// VWClientCache caches typed apis.kcp.io clients keyed by cluster ID. The
62+
// clients target the apiresourceschema virtual workspace and skip discovery
63+
// (which the VW does not fully serve), so we use the typed kcp REST client
64+
// rather than controller-runtime's client.Client.
6265
type VWClientCache struct {
6366
mu sync.RWMutex
64-
clients map[string]client.Client
67+
clients map[string]apisv1alpha1client.ApisV1alpha1Interface
6568
baseConfig *rest.Config
66-
scheme *runtime.Scheme
6769
}
6870

6971
// NewVWClientCache creates a new VWClientCache.
70-
func NewVWClientCache(baseConfig *rest.Config, scheme *runtime.Scheme) *VWClientCache {
72+
func NewVWClientCache(baseConfig *rest.Config) *VWClientCache {
7173
return &VWClientCache{
72-
clients: make(map[string]client.Client),
74+
clients: make(map[string]apisv1alpha1client.ApisV1alpha1Interface),
7375
baseConfig: baseConfig,
74-
scheme: scheme,
7576
}
7677
}
7778

7879
// GetClient returns a cached client for the given cluster ID, creating one if
7980
// necessary.
80-
func (c *VWClientCache) GetClient(clusterID string) (client.Client, error) {
81+
func (c *VWClientCache) GetClient(clusterID string) (apisv1alpha1client.ApisV1alpha1Interface, error) {
8182
c.mu.RLock()
8283
if cl, ok := c.clients[clusterID]; ok {
8384
c.mu.RUnlock()
@@ -93,28 +94,40 @@ func (c *VWClientCache) GetClient(clusterID string) (client.Client, error) {
9394
return cl, nil
9495
}
9596

96-
cl, err := newVWClient(c.baseConfig, c.scheme, clusterID)
97+
cl, err := newVWClient(c.baseConfig, clusterID)
9798
if err != nil {
9899
return nil, err
99100
}
100101
c.clients[clusterID] = cl
101102
return cl, nil
102103
}
103104

104-
// newVWClient creates a client pointing at the apiresourceschema virtual workspace
105-
// for the given cluster ID:
106-
// https://host:port/services/apiresourceschema/{clusterID}/clusters/*/
107-
func newVWClient(baseConfig *rest.Config, scheme *runtime.Scheme, clusterID string) (client.Client, error) {
105+
// newVWClient creates a typed apis.kcp.io client pointing at the
106+
// apiresourceschema virtual workspace for the given cluster ID:
107+
//
108+
// https://host:port/services/apiresourceschema/{clusterID}/clusters/{clusterID}
109+
//
110+
// Two important details:
111+
//
112+
// 1. We replace the path on baseConfig.Host (which typically points at a
113+
// workspace such as .../clusters/<provider>) rather than appending — otherwise
114+
// the URL ends up with two /clusters/ segments and never matches the VW root.
115+
//
116+
// 2. The kcp VW resolver accepts either "*" or the literal consumer cluster
117+
// name in the /clusters/<x>/ segment (see kcp pkg/virtual/apiresourceschema/
118+
// builder/build.go digestURL). We use the literal name because client-go's
119+
// REST request builder percent-encodes "*" to "%2A" when constructing the
120+
// final URL, and the resolver compares the path segment against the literal
121+
// string "*" — so a wildcard never matches in practice.
122+
func newVWClient(baseConfig *rest.Config, clusterID string) (apisv1alpha1client.ApisV1alpha1Interface, error) {
108123
cfg := rest.CopyConfig(baseConfig)
109-
u, err := url.Parse(cfg.Host)
124+
u, err := url.Parse(baseConfig.Host)
110125
if err != nil {
111126
return nil, fmt.Errorf("failed to parse base config host: %w", err)
112127
}
128+
cfg.Host = fmt.Sprintf("%s://%s/services/apiresourceschema/%s/clusters/%s", u.Scheme, u.Host, clusterID, clusterID)
113129

114-
u.Path = fmt.Sprintf("/services/apiresourceschema/%s/clusters/*", clusterID)
115-
cfg.Host = u.String()
116-
117-
return client.New(cfg, client.Options{Scheme: scheme})
130+
return apisv1alpha1client.NewForConfig(cfg)
118131
}
119132

120133
// SchemaGetterWithFallback returns a function that first tries to get an
@@ -151,11 +164,11 @@ func SchemaGetterWithFallback(
151164
return nil, fmt.Errorf("failed to get VW client for cluster %q: %w", clusterID, err)
152165
}
153166

154-
var vwSchema apisv1alpha1.APIResourceSchema
155-
if err := vwClient.Get(ctx, client.ObjectKey{Name: name}, &vwSchema); err != nil {
167+
vwSchema, err := vwClient.APIResourceSchemas().Get(ctx, name, metav1.GetOptions{})
168+
if err != nil {
156169
return nil, fmt.Errorf("APIResourceSchema %q not found in workspace or VW: %w", name, err)
157170
}
158171

159-
return &vwSchema, nil
172+
return vwSchema, nil
160173
}
161174
}

0 commit comments

Comments
 (0)