Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,10 @@ func mapAPIResourceSchema(clusterName string, cl cluster.Cluster) handler.TypedE
return []mcreconcile.Request{
{
Request: reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(serviceExport),
NamespacedName: types.NamespacedName{
Namespace: serviceExport.GetNamespace(),
Name: "cluster",
Comment thread
mjudeikis marked this conversation as resolved.
},
},
ClusterName: clusterName,
},
Expand Down
11 changes: 2 additions & 9 deletions backend/controllers/serviceexport/serviceexport_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (

"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
Expand Down Expand Up @@ -56,6 +55,7 @@ type APIServiceExportReconciler struct {
func NewAPIServiceExportReconciler(
ctx context.Context,
mgr mcmanager.Manager,
scope kubebindv1alpha2.InformerScope,
opts controller.TypedOptions[mcreconcile.Request],
) (*APIServiceExportReconciler, error) {
if err := mgr.GetFieldIndexer().IndexField(ctx, &kubebindv1alpha2.APIServiceExport{}, indexers.ServiceExportByBoundSchema,
Expand All @@ -67,6 +67,7 @@ func NewAPIServiceExportReconciler(
manager: mgr,
opts: opts,
reconciler: reconciler{
scope: scope,
getBoundSchema: func(ctx context.Context, cache cache.Cache, namespace, name string) (*kubebindv1alpha2.BoundSchema, error) {
var schema kubebindv1alpha2.BoundSchema
key := types.NamespacedName{Namespace: namespace, Name: name}
Expand All @@ -75,14 +76,6 @@ func NewAPIServiceExportReconciler(
}
return &schema, nil
},
deleteServiceExport: func(ctx context.Context, cl client.Client, ns, name string) error {
return cl.Delete(ctx, &kubebindv1alpha2.APIServiceExport{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
})
},
},
}

Expand Down
20 changes: 17 additions & 3 deletions backend/controllers/serviceexport/serviceexport_reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"

kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
"github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2/helpers"
Expand All @@ -32,8 +31,8 @@ import (
)

type reconciler struct {
getBoundSchema func(ctx context.Context, cache cache.Cache, namespace, name string) (*kubebindv1alpha2.BoundSchema, error)
deleteServiceExport func(ctx context.Context, client client.Client, namespace, name string) error
scope kubebindv1alpha2.InformerScope
getBoundSchema func(ctx context.Context, cache cache.Cache, namespace, name string) (*kubebindv1alpha2.BoundSchema, error)
}

func (r *reconciler) reconcile(ctx context.Context, cache cache.Cache, export *kubebindv1alpha2.APIServiceExport) error {
Expand All @@ -43,6 +42,12 @@ func (r *reconciler) reconcile(ctx context.Context, cache cache.Cache, export *k
errs = append(errs, err)
}

if r.scope == kubebindv1alpha2.ClusterScope {
if err := r.ensureClusterPermissions(ctx, cache, export); err != nil {
errs = append(errs, err)
}
}

return utilerrors.NewAggregate(errs)
}

Expand Down Expand Up @@ -88,3 +93,12 @@ func (r *reconciler) ensureSchema(ctx context.Context, cache cache.Cache, export

return nil
}

// ensureClusterPermissions ensures that the necessary cluster-wide permissions are in place for the exported APIs.
// This runs only when backend is operating in ClusterScope. In this scenario, there might not yet be a namespace so
// APIServiceNamespace reconciliation cannot be used.
func (r *reconciler) ensureClusterPermissions(ctx context.Context, cache cache.Cache, export *kubebindv1alpha2.APIServiceExport) error {
// TODO(mjudeikis): implement this for cluster scoped resources.
// https://github.com/kube-bind/kube-bind/issues/344
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func (r *reconciler) reconcile(ctx context.Context, cl client.Client, cache cach
return fmt.Errorf("failed to ensure exports: %w", err)
}

if err := r.ensureAPIServiceNamespaces(ctx, cl, cache, req); err != nil {
conditions.SetSummary(req)
return fmt.Errorf("failed to ensure APIServiceNamespaces: %w", err)
}

// TODO(mjudeikis): we could potentially add finallizer to APIServiceExport above or "adopt" boundschemas
// with owner references once export is created.
// https://github.com/kube-bind/kube-bind/issues/297
Expand Down Expand Up @@ -367,3 +372,34 @@ func isClaimableAPI(claim kubebindv1alpha2.PermissionClaim) bool {
}
return false
}

func (r *reconciler) ensureAPIServiceNamespaces(ctx context.Context, cl client.Client, cache cache.Cache, req *kubebindv1alpha2.APIServiceExportRequest) error {
logger := klog.FromContext(ctx)

// TODO(mjudeikis): We have this object above already, pass it down to avoid extra get.
export := &kubebindv1alpha2.APIServiceExport{}
if err := cl.Get(ctx, client.ObjectKeyFromObject(req), export); err != nil {
return fmt.Errorf("failed to get APIServiceExport %s/%s: %w", req.Namespace, req.Name, err)
}

for _, ns := range req.Spec.Namespaces {
apiServiceNamespace := helpers.APIServiceNamespaceFromExport(export, ns.Name)
currentAPIServiceNamespace := &kubebindv1alpha2.APIServiceNamespace{}
err := cache.Get(ctx, client.ObjectKeyFromObject(apiServiceNamespace), currentAPIServiceNamespace)
if err != nil {
if apierrors.IsNotFound(err) {
logger.V(1).Info("Creating APIServiceNamespace", "name", apiServiceNamespace.Name, "namespace", apiServiceNamespace.Namespace)
if err := cl.Create(ctx, apiServiceNamespace); err != nil {
if apierrors.IsAlreadyExists(err) {
continue
}
return err
}
} else {
return err
}
}
}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (c *reconciler) reconcile(ctx context.Context, client client.Client, cache
}

for _, export := range apiServiceExports.Items {
name := fmt.Sprintf("kube-binder-%s-export-%s", sns.Name, export.Name) // per-sns unique name
name := fmt.Sprintf("kube-binder-export-%s-%s", sns.Name, export.Name) // per-sns unique name
permissions := []rbacv1.PolicyRule{}
for _, claim := range export.Spec.PermissionClaims {
permissions = append(permissions, rbacv1.PolicyRule{
Expand All @@ -106,7 +106,7 @@ func (c *reconciler) reconcile(ctx context.Context, client client.Client, cache
Rules: permissions,
}
// Create new ClusterRole
if err := client.Create(ctx, role); err != nil {
if err := client.Create(ctx, role); err != nil && !errors.IsAlreadyExists(err) {
return fmt.Errorf("failed to create ClusterRole %s: %w", name, err)
}
} else {
Expand Down
1 change: 1 addition & 0 deletions backend/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) {
s.ServiceExport, err = serviceexport.NewAPIServiceExportReconciler(
ctx,
s.Config.Manager,
kubebindv1alpha2.InformerScope(c.Options.ConsumerScope),
opts,
)
if err != nil {
Expand Down
5 changes: 2 additions & 3 deletions contrib/kcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ kubectl kcp bind apiexport root:provider:cowboys-stable
```bash
kubectl get logicalcluster
# NAME PHASE URL AGE
# cluster Ready https://192.168.2.166:6443/clusters/1d5vpxvdpy0opbj1
# cluster Ready https://192.168.2.166:6443/clusters/1f4roigyt6meiaf8
```

## Consumer
Expand All @@ -120,7 +120,7 @@ kubectl ws create consumer --enter
10. Bind the thing:

```bash
./bin/kubectl-bind http://127.0.0.1:8080/clusters/1d5vpxvdpy0opbj1/exports --dry-run -o yaml > apiserviceexport.yaml
./bin/kubectl-bind http://127.0.0.1:8080/clusters/1f4roigyt6meiaf8/exports --dry-run -o yaml > apiserviceexport.yaml

# Extract secret for binding process. Note that secret name is not the same as output from command above. Check secret
# name by running `kubectl get secret -n kube-bind`
Expand Down Expand Up @@ -169,7 +169,6 @@ kubectl apply -f contrib/kcp/deploy/examples/cowboy.yaml
kubectl apply -f contrib/kcp/deploy/examples/sheriff.yaml
```


## Debug

```bash
Expand Down
2 changes: 1 addition & 1 deletion contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ spec:
crd: {}
- group: kube-bind.io
name: apiserviceexportrequests
schema: v250929-62fc678.apiserviceexportrequests.kube-bind.io
schema: v251020-7892cc6.apiserviceexportrequests.kube-bind.io
storage:
crd: {}
- group: kube-bind.io
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: apis.kcp.io/v1alpha1
kind: APIResourceSchema
metadata:
creationTimestamp: null
name: v250929-62fc678.apiserviceexportrequests.kube-bind.io
name: v251020-7892cc6.apiserviceexportrequests.kube-bind.io
spec:
conversion:
strategy: None
Expand Down Expand Up @@ -208,6 +208,25 @@ spec:
spec specifies how an API service from a service provider should be bound in the
local consumer cluster.
properties:
namespaces:
description: |-
namespaces specifies the namespaces to bootstrap as part of this request.
When objects originate from provider side, the consumer does not always know the necessary details.
This field allows provider to pre-heat the necessary namespaces on provider side by creating
APIServiceNamespace objects attached to the APIServiceExport. More namespaces can be created later by the consumer.
items:
properties:
name:
description: name is the name of the namespace to create on provider
side.
type: string
required:
- name
type: object
type: array
x-kubernetes-validations:
- message: namespaces are immutable
rule: self == oldSelf
Comment thread
coderabbitai[bot] marked this conversation as resolved.
parameters:
description: |-
parameters holds service provider specific parameters for this binding
Expand Down
19 changes: 19 additions & 0 deletions deploy/crd/kube-bind.io_apiserviceexportrequests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,25 @@ spec:
spec specifies how an API service from a service provider should be bound in the
local consumer cluster.
properties:
namespaces:
description: |-
namespaces specifies the namespaces to bootstrap as part of this request.
When objects originate from provider side, the consumer does not always know the necessary details.
This field allows provider to pre-heat the necessary namespaces on provider side by creating
APIServiceNamespace objects attached to the APIServiceExport. More namespaces can be created later by the consumer.
items:
properties:
name:
description: name is the name of the namespace to create on
provider side.
type: string
required:
- name
type: object
type: array
x-kubernetes-validations:
- message: namespaces are immutable
rule: self == oldSelf
Comment thread
mjudeikis marked this conversation as resolved.
parameters:
description: |-
parameters holds service provider specific parameters for this binding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ import (
kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
)

const label = "kube-bind.io/owner"

type readReconciler struct {
getServiceNamespace func(upstreamNamespace string) (*kubebindv1alpha2.APIServiceNamespace, error)
getProviderObject func(ns, name string) (*unstructured.Unstructured, error)
Expand Down Expand Up @@ -185,7 +183,7 @@ func (r readReconciler) makeConsumerOwner(obj *unstructured.Unstructured) {
if a == nil {
a = map[string]string{}
}
a[label] = string(kubebindv1alpha2.OwnerConsumer)
a[kubebindv1alpha2.ObjectOwnerLabel] = string(kubebindv1alpha2.OwnerConsumer)
obj.SetLabels(a)
}

Expand All @@ -194,7 +192,7 @@ func (r readReconciler) makeProviderOwner(obj *unstructured.Unstructured) {
if a == nil {
a = map[string]string{}
}
a[label] = string(kubebindv1alpha2.OwnerProvider)
a[kubebindv1alpha2.ObjectOwnerLabel] = string(kubebindv1alpha2.OwnerProvider)
obj.SetLabels(a)
}

Expand Down Expand Up @@ -237,7 +235,7 @@ func candidateFromOwnerObj(downstreamNS string, obj *unstructured.Unstructured)
// consumer or provider side.
func determineOwner(providerObj, consumerObj *unstructured.Unstructured) (kubebindv1alpha2.Owner, error) {
if providerObj != nil {
ownerLabel := providerObj.GetLabels()[label]
ownerLabel := providerObj.GetLabels()[kubebindv1alpha2.ObjectOwnerLabel]
switch ownerLabel {
case kubebindv1alpha2.OwnerProvider.String():
return kubebindv1alpha2.OwnerProvider, nil
Expand All @@ -250,7 +248,7 @@ func determineOwner(providerObj, consumerObj *unstructured.Unstructured) (kubebi
}

if consumerObj != nil {
ownerLabel := consumerObj.GetLabels()[label]
ownerLabel := consumerObj.GetLabels()[kubebindv1alpha2.ObjectOwnerLabel]
switch ownerLabel {
case kubebindv1alpha2.OwnerProvider.String():
return kubebindv1alpha2.OwnerProvider, nil
Expand Down
11 changes: 6 additions & 5 deletions pkg/konnector/controllers/cluster/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (

"github.com/kube-bind/kube-bind/pkg/indexers"
"github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/clusterbinding"
"github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/namespacedeletion"
"github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/namespacelifecycle"
"github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/servicebinding"
"github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/serviceexport"
"github.com/kube-bind/kube-bind/pkg/konnector/controllers/dynamic"
Expand Down Expand Up @@ -115,8 +115,9 @@ func NewController(
if err != nil {
return nil, err
}
namespacedeletionCtrl, err := namespacedeletion.NewController(
namespacelifecycleCtrl, err := namespacelifecycle.NewController(
providerConfig,
consumerConfig,
providerNamespace,
providerBindInformers.KubeBind().V1alpha2().APIServiceNamespaces(),
namespaceInformer,
Expand Down Expand Up @@ -167,7 +168,7 @@ func NewController(
serviceBindingIndexer: serviceBindingInformer.Informer().GetIndexer(),

clusterbindingCtrl: clusterbindingCtrl,
namespacedeletionCtrl: namespacedeletionCtrl,
namespacelifecycleCtrl: namespacelifecycleCtrl,
servicebindingCtrl: servicebindingCtrl,
serviceresourcebindingCtrl: serviceexportCtrl,
}, nil
Expand All @@ -194,7 +195,7 @@ type controller struct {
factories []SharedInformerFactory

clusterbindingCtrl GenericController
namespacedeletionCtrl GenericController
namespacelifecycleCtrl GenericController
servicebindingCtrl GenericController
serviceresourcebindingCtrl GenericController
}
Expand Down Expand Up @@ -248,7 +249,7 @@ func (c *controller) Start(ctx context.Context) {
})

go c.clusterbindingCtrl.Start(ctx, 2)
go c.namespacedeletionCtrl.Start(ctx, 2)
go c.namespacelifecycleCtrl.Start(ctx, 2)
go c.servicebindingCtrl.Start(ctx, 2)
go c.serviceresourcebindingCtrl.Start(ctx, 2)

Expand Down
Loading