Skip to content

Commit dc93a02

Browse files
committed
Add CatalogAPI and Template
Signed-off-by: Mangirdas Judeikis <mangirdas@judeikis.lt> On-behalf-of: @SAP mangirdas.judeikis@sap.com
1 parent a998472 commit dc93a02

61 files changed

Lines changed: 2864 additions & 303 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.coderabbit.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ reviews:
3030
# Specify file patterns to include or exclude in a review using glob patterns (e.g., !dist/**, src/**). These patterns also apply to 'git sparse-checkout', including specified patterns and ignoring excluded ones (starting with '!') when cloning the repository.
3131
# Default: []
3232
path_filters: [
33-
sdk/client/**,
34-
'**/*.yaml',
33+
'!sdk/client/**',
34+
'!**/*.yaml',
3535
]
3636

3737
# Configuration for auto review

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ coverage.*
1414
/dex
1515
/bin
1616
docs/generators/cli-doc/cli-doc
17-
dex/
17+
dex/
18+
apiserviceexport.yaml

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,28 @@ All the actions shown between the clusters are done by the konnector, except: th
6262

6363
To get familiar with setting up the environment, please check out docs at [kube-bind.io](https://docs.kube-bind.io/main/setup).
6464

65+
## API Changes in v0.6.0 release
66+
67+
### Catalog API
68+
Introduction of new `Collection` and `APIServiceExportTemplate` CRDs for better service organization:
69+
- **Collections**: Function as folders in the UI, grouping related modules
70+
- **APIServiceExportTemplate**: Group multiple CRDs with their related resources and permission claims and creates `APIServiceExportRequest`
71+
72+
### Enhanced Permission Claims
73+
Major improvements to `PermissionClaims` in APIServiceExportSpec:
74+
- **NamedResources**: Specify exact resources by name and namespace
75+
- **Combined Selectors**: Use both label selectors AND named resources (both must match)
76+
- **Granular Control**: More precise access control for service resources
77+
78+
### Provider-side Namespace Management
79+
Enhanced namespace management on the provider side:
80+
- **APIServiceNamespace Controller**: Automatically creates Roles and RoleBindings
81+
- **Namespace Isolation**: Each consumer gets isolated provider-side namespaces
82+
- **RBAC Automation**: Proper permissions created based on scope (namespaced vs cluster-scoped)
83+
- **Namespace Pre-provisioning**: Providers can pre-create namespaces for better UX
84+
85+
**Important**: When `ClusterScope` mode is used, cluster-wide permissions are created instead of namespaced ones.
86+
6587
## API Changes in v0.5.0 release
6688

6789
Version v0.5.0 includes significant architectural improvements to the API structure:

backend/controllers/serviceexportrequest/serviceexportrequest_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ func getBoundSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedE
182182
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexportrequests/finalizers,verbs=update
183183
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete
184184
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiresourceschemas,verbs=get;list;watch;create;update;patch;delete
185+
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=resources=apiservicenamespaces,verbs=get;list;watch;create
185186

186187
// Reconcile is part of the main kubernetes reconciliation loop which aims to
187188
// move the current state of the cluster closer to the desired state.

backend/http/handler.go

Lines changed: 47 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,15 @@ import (
3333
"github.com/gorilla/securecookie"
3434
"golang.org/x/oauth2"
3535
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36-
"k8s.io/apimachinery/pkg/labels"
3736
"k8s.io/apimachinery/pkg/runtime"
38-
"k8s.io/apimachinery/pkg/runtime/schema"
3937
componentbaseversion "k8s.io/component-base/version"
4038
"k8s.io/klog/v2"
4139

4240
"github.com/kube-bind/kube-bind/backend/kubernetes"
43-
"github.com/kube-bind/kube-bind/backend/kubernetes/resources"
4441
"github.com/kube-bind/kube-bind/backend/session"
4542
"github.com/kube-bind/kube-bind/backend/template"
4643
bindversion "github.com/kube-bind/kube-bind/pkg/version"
4744
kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
48-
"github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2/helpers"
4945
)
5046

5147
var (
@@ -346,12 +342,14 @@ func createSessionState(authCode *AuthCode, token *oauth2.Token) (*session.State
346342
}
347343

348344
type UISchema struct {
349-
Name string
350-
Version string
351-
Group string
352-
Kind string
353-
Scope string // "Namespaced" or "Cluster"
354-
Resource string
345+
Scope string // "Namespaced" or "Cluster"
346+
347+
Name string
348+
Description string
349+
350+
Resources []kubebindv1alpha2.APIServiceExportResource
351+
PermissionClaims []kubebindv1alpha2.PermissionClaim
352+
Namespaces []kubebindv1alpha2.Namespaces
355353

356354
// SessionID
357355
SessionID string
@@ -376,44 +374,26 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
376374
return
377375
}
378376

379-
exportedSchemas, err := h.getBackendDynamicResource(r.Context(), providerCluster)
377+
templates, err := h.listCollectionTemplates(r.Context(), providerCluster)
380378
if err != nil {
381-
logger.Error(err, "failed to get dynamic resources")
379+
logger.Error(err, "failed to get template resources")
382380
http.Error(w, "internal error", http.StatusInternalServerError)
383381
return
384382
}
385383

386-
result := make([]UISchema, 0, len(exportedSchemas))
387-
for _, item := range exportedSchemas {
384+
result := make([]UISchema, 0, len(templates.Items))
385+
for _, item := range templates.Items {
388386
if !strings.EqualFold(h.scope.String(), string(item.Spec.Scope)) && h.scope != kubebindv1alpha2.ClusterScope {
389387
continue
390388
}
391389

392-
if len(item.Spec.Versions) == 0 {
393-
logger.Error(fmt.Errorf("no versions found"), "skipping schema", "name", item.Name)
394-
continue
395-
}
396-
// pick first served version
397-
ver := ""
398-
for _, v := range item.Spec.Versions {
399-
if v.Served {
400-
ver = v.Name
401-
break
402-
}
403-
}
404-
if ver == "" {
405-
logger.Error(fmt.Errorf("no served versions found"), "skipping schema", "name", item.Name)
406-
continue
407-
}
408390
result = append(result, UISchema{
409-
Name: item.GetName(),
410-
Kind: item.Spec.Names.Kind,
411-
Scope: string(item.Spec.Scope),
412-
Version: ver,
413-
Group: item.Spec.Group,
414-
// Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it.
415-
Resource: item.Spec.Names.Plural,
416-
SessionID: sessionID,
391+
Name: item.GetName(),
392+
Scope: string(item.Spec.Scope),
393+
PermissionClaims: item.Spec.PermissionClaims,
394+
Resources: item.Spec.Resources,
395+
Namespaces: item.Spec.Namespaces,
396+
SessionID: sessionID,
417397
})
418398
}
419399

@@ -436,9 +416,7 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
436416

437417
func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
438418
logger := getLogger(r)
439-
group := r.URL.Query().Get("group")
440-
resource := r.URL.Query().Get("resource")
441-
version := r.URL.Query().Get("version")
419+
templateName := r.URL.Query().Get("template")
442420
providerCluster := mux.Vars(r)["cluster"]
443421

444422
prepareNoCache(w)
@@ -466,24 +444,26 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
466444
return
467445
}
468446

447+
// Module consist of many resources and permissionClaims. Read it and translate to
448+
template, err := h.kubeManager.GetTemplates(r.Context(), providerCluster, templateName)
449+
if err != nil {
450+
logger.Error(err, "failed to get template")
451+
http.Error(w, "internal error", http.StatusInternalServerError)
452+
return
453+
}
454+
469455
request := kubebindv1alpha2.APIServiceExportRequestResponse{
470456
TypeMeta: metav1.TypeMeta{
471457
APIVersion: kubebindv1alpha2.SchemeGroupVersion.String(),
472458
Kind: "APIServiceExportRequest",
473459
},
474460
ObjectMeta: kubebindv1alpha2.NameObjectMeta{
475-
// this is good for one resource. If there are more (in the future),
476-
// we need a better name heuristic. Note: it does not have to be unique.
477-
// But pretty is better.
478-
Name: resource + "." + group,
461+
Name: templateName,
479462
},
480463
Spec: kubebindv1alpha2.APIServiceExportRequestSpec{
481-
Resources: []kubebindv1alpha2.APIServiceExportRequestResource{
482-
{
483-
GroupResource: kubebindv1alpha2.GroupResource{Group: group, Resource: resource},
484-
Versions: []string{version},
485-
},
486-
},
464+
Resources: template.Spec.Resources,
465+
PermissionClaims: template.Spec.PermissionClaims,
466+
Namespaces: template.Spec.Namespaces,
487467
},
488468
}
489469

@@ -543,34 +523,26 @@ func mustRead(f func(name string) ([]byte, error), name string) string {
543523
return string(bs)
544524
}
545525

546-
func (h *handler) getBackendDynamicResource(ctx context.Context, cluster string) (kubebindv1alpha2.ExportedSchemas, error) {
547-
labelSelector := labels.Set{
548-
resources.ExportedCRDsLabel: "true",
549-
}
550-
551-
parts := strings.SplitN(h.schemaSource, ".", 3)
552-
if len(parts) != 3 { // We check this in validation, but just in case.
553-
return nil, fmt.Errorf("invalid schema source: %q", h.schemaSource)
554-
}
555-
556-
gvk := schema.GroupVersionKind{
557-
Kind: parts[0],
558-
Version: parts[1],
559-
Group: parts[2],
560-
}
561-
list, err := h.kubeManager.ListDynamicResources(ctx, cluster, gvk, labelSelector.AsSelector())
526+
// listCollectionTemplates fetches the list of Collections from the backend cluster.
527+
// Flow is:
528+
// 1. List Collection and check what modules we are targeting
529+
// 2. Get templates from the backend cluster and construct shallow-bound schemas (no crd content).
530+
func (h *handler) listCollectionTemplates(ctx context.Context, cluster string) (*kubebindv1alpha2.APIServiceExportTemplateList, error) {
531+
collections, err := h.kubeManager.ListCollections(ctx, cluster)
562532
if err != nil {
563-
return nil, fmt.Errorf("failed to list resources: %w", err)
533+
return nil, fmt.Errorf("failed to list collections: %w", err)
564534
}
565535

566-
boundSchemas := make(kubebindv1alpha2.ExportedSchemas, len(list.Items))
567-
for _, item := range list.Items {
568-
boundSchema, err := helpers.UnstructuredToBoundSchema(item)
569-
if err != nil {
570-
return nil, err
536+
templates := &kubebindv1alpha2.APIServiceExportTemplateList{}
537+
for _, collection := range collections.Items {
538+
for _, t := range collection.Spec.Templates {
539+
template, err := h.kubeManager.GetTemplates(ctx, cluster, t.Name)
540+
if err != nil {
541+
return nil, fmt.Errorf("failed to get template %q: %w", t.Name, err)
542+
}
543+
templates.Items = append(templates.Items, *template)
571544
}
572-
boundSchemas[boundSchema.ResourceGroupName()] = boundSchema
573545
}
574546

575-
return boundSchemas, nil
547+
return templates, nil
576548
}

backend/kubernetes/manager.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,38 @@ func (m *Manager) ListCustomResourceDefinitions(ctx context.Context, cluster str
165165
return &crds, nil
166166
}
167167

168+
func (m *Manager) ListCollections(ctx context.Context, cluster string) (*kubebindv1alpha2.CollectionList, error) {
169+
cl, err := m.manager.GetCluster(ctx, cluster)
170+
if err != nil {
171+
return nil, err
172+
}
173+
c := cl.GetClient()
174+
175+
var collections kubebindv1alpha2.CollectionList
176+
err = c.List(ctx, &collections)
177+
if err != nil {
178+
return nil, err
179+
}
180+
181+
return &collections, nil
182+
}
183+
184+
func (m *Manager) GetTemplates(ctx context.Context, cluster, name string) (*kubebindv1alpha2.APIServiceExportTemplate, error) {
185+
cl, err := m.manager.GetCluster(ctx, cluster)
186+
if err != nil {
187+
return nil, err
188+
}
189+
c := cl.GetClient()
190+
191+
var template kubebindv1alpha2.APIServiceExportTemplate
192+
err = c.Get(ctx, types.NamespacedName{Name: name}, &template)
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
return &template, nil
198+
}
199+
168200
func (m *Manager) ListDynamicResources(ctx context.Context, cluster string, gvk schema.GroupVersionKind, selector labels.Selector) (*unstructured.UnstructuredList, error) {
169201
cl, err := m.manager.GetCluster(ctx, cluster)
170202
if err != nil {

backend/kubernetes/resources/resources.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@ const (
2525
KubeconfigSecretName = "kubeconfig"
2626
ClusterBindingName = "cluster"
2727

28-
// TODO(MQ): maybe think of a better label name.
2928
ExportedCRDsLabel = "kube-bind.io/exported"
3029
)

backend/template/login.html

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)