Skip to content

Commit fac382c

Browse files
committed
Add resourceclaims controller
1 parent d9cca98 commit fac382c

19 files changed

Lines changed: 1007 additions & 240 deletions

backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ func (r *reconciler) validate(ctx context.Context, cl client.Client, req *kubebi
327327
// isClaimableAPI checks if a permission claim is for a claimable API.
328328
func isClaimableAPI(claim kubebindv1alpha2.PermissionClaim) bool {
329329
for _, api := range kubebindv1alpha2.ClaimableAPIs {
330-
if claim.Group == api.GroupVersion.Group && claim.Resource == api.Names.Plural {
330+
if claim.Group == api.GroupVersionResource.Group && claim.Resource == api.Names.Plural {
331331
return true
332332
}
333333
}

backend/controllers/servicenamespace/servicenamespace_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ func (r *APIServiceNamespaceReconciler) Reconcile(ctx context.Context, req mcrec
230230

231231
// Fetch the APIServiceNamespace instance
232232
apiServiceNamespace := &kubebindv1alpha2.APIServiceNamespace{}
233-
if err := client.Get(ctx, req.NamespacedName, apiServiceNamespace); err != nil {
233+
if err := cache.Get(ctx, req.NamespacedName, apiServiceNamespace); err != nil {
234234
if errors.IsNotFound(err) {
235235
// Request object not found, could have been deleted after reconcile request.
236236
// Handle deletion logic here

backend/controllers/servicenamespace/servicenamespace_reconcile.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
rbacv1 "k8s.io/api/rbac/v1"
2626
"k8s.io/apimachinery/pkg/api/errors"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/types"
29+
"k8s.io/klog/v2"
2830
"sigs.k8s.io/controller-runtime/pkg/cache"
2931
"sigs.k8s.io/controller-runtime/pkg/client"
3032

@@ -45,6 +47,7 @@ type reconciler struct {
4547
}
4648

4749
func (c *reconciler) reconcile(ctx context.Context, client client.Client, cache cache.Cache, sns *kubebindv1alpha2.APIServiceNamespace) error {
50+
logger := klog.FromContext(ctx)
4851
var ns *corev1.Namespace
4952
nsName := sns.Namespace + "-" + sns.Name
5053
if sns.Status.Namespace != "" {
@@ -75,6 +78,133 @@ func (c *reconciler) reconcile(ctx context.Context, client client.Client, cache
7578
sns.Status.Namespace = nsName
7679
}
7780

81+
// List APIServiceExports in the namespace
82+
apiServiceExports, err := c.listAPIServiceExports(ctx, cache, sns.Namespace)
83+
if err != nil {
84+
return fmt.Errorf("failed to list APIServiceExports: %w", err)
85+
}
86+
87+
for _, export := range apiServiceExports.Items {
88+
name := "kube-binder-export-" + export.Name // unique name for the service export related permissions
89+
permissions := []rbacv1.PolicyRule{}
90+
for _, claim := range export.Spec.PermissionClaims {
91+
permissions = append(permissions, rbacv1.PolicyRule{
92+
APIGroups: []string{claim.Group},
93+
Resources: []string{claim.Resource},
94+
// We need list and watch for informers to be able to start. And create to create initial object.
95+
Verbs: []string{"*"},
96+
})
97+
98+
}
99+
if c.scope == kubebindv1alpha2.ClusterScope {
100+
role, err := c.getPermissionClaimsClusterRole(ctx, cache, name)
101+
if err != nil && !errors.IsNotFound(err) {
102+
return fmt.Errorf("failed to get Role %s: %w", name, err)
103+
}
104+
if role == nil {
105+
role = &rbacv1.ClusterRole{
106+
ObjectMeta: metav1.ObjectMeta{
107+
Name: name,
108+
},
109+
Rules: permissions,
110+
}
111+
// Create new ClusterRole
112+
if err := client.Create(ctx, role); err != nil {
113+
return fmt.Errorf("failed to create ClusterRole %s: %w", name, err)
114+
}
115+
} else {
116+
role.Rules = permissions
117+
if err := client.Update(ctx, role); err != nil {
118+
return fmt.Errorf("failed to update ClusterRole %s: %w", name, err)
119+
}
120+
}
121+
122+
clusterBinding, err := c.getPermissionClaimsClusterRoleBinding(ctx, cache, name)
123+
if err != nil && !errors.IsNotFound(err) {
124+
return fmt.Errorf("failed to get ClusterRoleBinding %s: %w", name, err)
125+
}
126+
if clusterBinding == nil {
127+
clusterBinding = &rbacv1.ClusterRoleBinding{
128+
ObjectMeta: metav1.ObjectMeta{
129+
Name: name,
130+
},
131+
Subjects: []rbacv1.Subject{
132+
{
133+
Kind: "ServiceAccount",
134+
Namespace: sns.Namespace,
135+
Name: kuberesources.ServiceAccountName,
136+
},
137+
},
138+
RoleRef: rbacv1.RoleRef{
139+
Kind: "ClusterRole",
140+
Name: name,
141+
APIGroup: "rbac.authorization.k8s.io",
142+
},
143+
}
144+
if err := client.Create(ctx, clusterBinding); err != nil {
145+
return fmt.Errorf("failed to create ClusterRoleBinding %s: %w", name, err)
146+
}
147+
} else {
148+
logger.Info("ClusterRoleBinding already exists, update not implemented.", "name", name)
149+
}
150+
151+
} else {
152+
role, err := c.getPermissionClaimsRole(ctx, cache, sns.Status.Namespace, name)
153+
if err != nil && !errors.IsNotFound(err) {
154+
return fmt.Errorf("failed to get Role %s: %w", name, err)
155+
}
156+
if role == nil {
157+
role := &rbacv1.Role{
158+
ObjectMeta: metav1.ObjectMeta{
159+
Name: name,
160+
Namespace: sns.Status.Namespace,
161+
},
162+
Rules: permissions,
163+
}
164+
// Create new Role
165+
if err := client.Create(ctx, role); err != nil {
166+
return fmt.Errorf("failed to create Role %s: %w", name, err)
167+
}
168+
} else {
169+
role.Rules = permissions
170+
if err := client.Update(ctx, role); err != nil {
171+
return fmt.Errorf("failed to update Role %s: %w", name, err)
172+
}
173+
}
174+
175+
rolebinding, err := c.getPermissionClaimsRoleBinding(ctx, cache, sns.Status.Namespace, name)
176+
if err != nil && !errors.IsNotFound(err) {
177+
return fmt.Errorf("failed to get Role %s: %w", name, err)
178+
}
179+
180+
if rolebinding == nil {
181+
rolebinding := &rbacv1.RoleBinding{
182+
ObjectMeta: metav1.ObjectMeta{
183+
Name: name,
184+
Namespace: sns.Status.Namespace,
185+
},
186+
Subjects: []rbacv1.Subject{
187+
{
188+
Kind: "ServiceAccount",
189+
Namespace: sns.Namespace,
190+
Name: kuberesources.ServiceAccountName,
191+
},
192+
},
193+
RoleRef: rbacv1.RoleRef{
194+
Kind: "Role",
195+
Name: name,
196+
APIGroup: "rbac.authorization.k8s.io",
197+
},
198+
}
199+
if err := client.Create(ctx, rolebinding); err != nil {
200+
return fmt.Errorf("failed to create Role %s: %w", name, err)
201+
}
202+
} else {
203+
logger.Info("Role already exists, update not implemented.", "name", name)
204+
}
205+
}
206+
}
207+
78208
return nil
79209
}
80210

@@ -119,3 +249,47 @@ func (c *reconciler) ensureRBACRoleBinding(ctx context.Context, client client.Cl
119249

120250
return nil
121251
}
252+
253+
func (r *reconciler) getPermissionClaimsClusterRole(ctx context.Context, cache cache.Cache, name string) (*rbacv1.ClusterRole, error) {
254+
var role rbacv1.ClusterRole
255+
key := types.NamespacedName{Name: name}
256+
if err := cache.Get(ctx, key, &role); err != nil {
257+
return nil, err
258+
}
259+
return &role, nil
260+
}
261+
262+
func (r *reconciler) getPermissionClaimsRole(ctx context.Context, cache cache.Cache, namespace, name string) (*rbacv1.Role, error) {
263+
var role rbacv1.Role
264+
key := types.NamespacedName{Namespace: namespace, Name: name}
265+
if err := cache.Get(ctx, key, &role); err != nil {
266+
return nil, err
267+
}
268+
return &role, nil
269+
}
270+
271+
func (r *reconciler) getPermissionClaimsClusterRoleBinding(ctx context.Context, cache cache.Cache, name string) (*rbacv1.ClusterRoleBinding, error) {
272+
var roleBinding rbacv1.ClusterRoleBinding
273+
key := types.NamespacedName{Name: name}
274+
if err := cache.Get(ctx, key, &roleBinding); err != nil {
275+
return nil, err
276+
}
277+
return &roleBinding, nil
278+
}
279+
280+
func (r *reconciler) getPermissionClaimsRoleBinding(ctx context.Context, cache cache.Cache, namespace, name string) (*rbacv1.RoleBinding, error) {
281+
var roleBinding rbacv1.RoleBinding
282+
key := types.NamespacedName{Name: name, Namespace: namespace}
283+
if err := cache.Get(ctx, key, &roleBinding); err != nil {
284+
return nil, err
285+
}
286+
return &roleBinding, nil
287+
}
288+
289+
func (r *reconciler) listAPIServiceExports(ctx context.Context, cache cache.Cache, namespace string) (*kubebindv1alpha2.APIServiceExportList, error) {
290+
exports := &kubebindv1alpha2.APIServiceExportList{}
291+
if err := cache.List(ctx, exports, client.InNamespace(namespace)); err != nil {
292+
return nil, err
293+
}
294+
return exports, nil
295+
}

deploy/crd/kube-bind.io_apiservicebindings.yaml

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -306,35 +306,7 @@ spec:
306306
type: object
307307
type: object
308308
x-kubernetes-map-type: atomic
309-
resourceNames:
310-
description: resourceNames is a list of resource names to
311-
select.
312-
items:
313-
description: |-
314-
SelectorResourceName identifies a specific resource by name.
315-
If backend operates at the namespace level isolation, namespace will be included.
316-
properties:
317-
name:
318-
description: Name is the name of the resource.
319-
type: string
320-
required:
321-
- name
322-
type: object
323-
type: array
324309
type: object
325-
verbs:
326-
description: Verbs is a list of verbs that are required by the
327-
provider to operate.
328-
items:
329-
type: string
330-
type: array
331-
versions:
332-
description: |-
333-
versions is a list of versions that should be exported. If this is empty
334-
a sensible default is chosen by the service provider.
335-
items:
336-
type: string
337-
type: array
338310
required:
339311
- resource
340312
type: object

deploy/crd/kube-bind.io_apiserviceexportrequests.yaml

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -300,35 +300,7 @@ spec:
300300
type: object
301301
type: object
302302
x-kubernetes-map-type: atomic
303-
resourceNames:
304-
description: resourceNames is a list of resource names to
305-
select.
306-
items:
307-
description: |-
308-
SelectorResourceName identifies a specific resource by name.
309-
If backend operates at the namespace level isolation, namespace will be included.
310-
properties:
311-
name:
312-
description: Name is the name of the resource.
313-
type: string
314-
required:
315-
- name
316-
type: object
317-
type: array
318303
type: object
319-
verbs:
320-
description: Verbs is a list of verbs that are required by the
321-
provider to operate.
322-
items:
323-
type: string
324-
type: array
325-
versions:
326-
description: |-
327-
versions is a list of versions that should be exported. If this is empty
328-
a sensible default is chosen by the service provider.
329-
items:
330-
type: string
331-
type: array
332304
required:
333305
- resource
334306
type: object

deploy/crd/kube-bind.io_apiserviceexports.yaml

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -569,35 +569,7 @@ spec:
569569
type: object
570570
type: object
571571
x-kubernetes-map-type: atomic
572-
resourceNames:
573-
description: resourceNames is a list of resource names to
574-
select.
575-
items:
576-
description: |-
577-
SelectorResourceName identifies a specific resource by name.
578-
If backend operates at the namespace level isolation, namespace will be included.
579-
properties:
580-
name:
581-
description: Name is the name of the resource.
582-
type: string
583-
required:
584-
- name
585-
type: object
586-
type: array
587572
type: object
588-
verbs:
589-
description: Verbs is a list of verbs that are required by the
590-
provider to operate.
591-
items:
592-
type: string
593-
type: array
594-
versions:
595-
description: |-
596-
versions is a list of versions that should be exported. If this is empty
597-
a sensible default is chosen by the service provider.
598-
items:
599-
type: string
600-
type: array
601573
required:
602574
- resource
603575
type: object

kcp/README.md

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -99,34 +99,16 @@ kubectl ws create consumer --enter
9999
10. Bind the thing:
100100

101101
```bash
102-
./bin/kubectl-bind http://127.0.0.1:8080/clusters/fcaehwcimtlcvhss/exports --dry-run -o yaml > apiserviceexport.yaml
102+
./bin/kubectl-bind http://127.0.0.1:8080/clusters/36note1hch913ryv/exports --dry-run -o yaml > apiserviceexport.yaml
103103

104104
# Extract secret for binding process. Note that secret name is not the same as output from command above. Check secret
105105
# name by running `kubectl get secret -n kube-bind`
106-
kubectl get secret kubeconfig-4hhc6 -n kube-bind -o jsonpath='{.data.kubeconfig}' | base64 -d > remote.kubeconfig
106+
kubectl get secret kubeconfig-bpmvf -n kube-bind -o jsonpath='{.data.kubeconfig}' | base64 -d > remote.kubeconfig
107107

108-
./bin/kubectl-bind apiservice --remote-kubeconfig remote.kubeconfig -f apiserviceexport.yaml --skip-konnector --remote-namespace kube-bind-vlh79
108+
./bin/kubectl-bind apiservice --remote-kubeconfig remote.kubeconfig -f kcp/deploy/examples/apiserviceexport.yaml --skip-konnector --remote-namespace kube-bind-qsps8
109109

110110
export KUBECONFIG=.kcp/consumer.kubeconfig
111111
go run ./cmd/konnector/ --lease-namespace default
112-
113-
114-
11. (Optional) Add second consumer to test
115-
116-
```bash
117-
cp .kcp/admin.kubeconfig .kcp/consumer2.kubeconfig
118-
export KUBECONFIG=.kcp/consumer2.kubeconfig
119-
kubectl ws use :root
120-
kubectl ws create consumer2 --enter
121-
122-
./bin/kubectl-bind http://127.0.0.1:8080/clusters/2vgrh380y0cq38du/exports --dry-run -o yaml > apiserviceexport2.yaml
123-
kubectl get secret kubeconfig-wvvsb -n kube-bind -o jsonpath='{.data.kubeconfig}' | base64 -d > remote2.kubeconfig
124-
125-
./bin/kubectl-bind apiservice --remote-kubeconfig remote2.kubeconfig -f apiserviceexport.yaml --skip-konnector --remote-namespace kube-bind-m5zx4
126-
127-
128-
export KUBECONFIG=.kcp/consumer2.kubeconfig
129-
go run ./cmd/konnector/ --lease-namespace default --server-address :8091
130112
```
131113

132114
Create objects:
@@ -144,4 +126,9 @@ export KUBECONFIG=.kcp/debug.kubeconfig
144126
k ws use :root:kube-bind
145127

146128
k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath="{.status.endpoints[0].url}")/clusters/*" api-resources
147-
k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath="{.status.endpoints[0].url}")/clusters/*" get crd
129+
k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath="{.status.endpoints[0].url}")/clusters/*" get crd
130+
131+
# some claimed objects
132+
kubectl create cm provider -n kube-bind-qsps8-default
133+
kubectl label cm provider app=wildwest -n kube-bind-qsps8-default
134+
```

kcp/deploy/examples/apiserviceexport.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ spec:
1111
permissionClaims:
1212
- apiGroup: ""
1313
resource: configmaps
14-
verbs:
15-
- get
16-
- list
17-
- watch
18-
- create
1914
selector:
2015
labelSelector:
2116
matchLabels:

0 commit comments

Comments
 (0)