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
4 changes: 2 additions & 2 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ reviews:
# 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.
# Default: []
path_filters: [
sdk/client/**,
'**/*.yaml',
'!sdk/client/**',
'!**/*.yaml',
]
Comment thread
mjudeikis marked this conversation as resolved.

# Configuration for auto review
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ coverage.*
/dex
/bin
docs/generators/cli-doc/cli-doc
dex/
dex/
apiserviceexport.yaml
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# CHANGELOG

Change log is high level summary of notable changes for each release.

## API Changes in v0.6.0 release

### Helm Chart Updates

Introduction of helm chart, `kube-bind/backend`, to deploy the backend components with configurable options.

### Catalog API
Introduction of new `Collection` and `APIServiceExportTemplate` CRDs for better service organization:
- **Collections**: Function as folders in the UI, grouping bindable resources such as templates
- **APIServiceExportTemplate**: Group multiple CRDs with their related resources and permission claims that can be used to create a `APIServiceExportRequest`

### Enhanced Permission Claims
Major improvements to `PermissionClaims` in APIServiceExportSpec:
- **NamedResources**: Specify exact resources by name and namespace
- **Combined Selectors**: Use both label selectors AND named resources (both must match)
- **Granular Control**: More precise access control for service resources

### Provider-side Namespace Management
Enhanced namespace management on the provider side:
- **APIServiceNamespace Controller**: Automatically creates Roles and RoleBindings
- **Namespace Isolation**: Each consumer gets isolated provider-side namespaces
- **RBAC Automation**: Proper permissions created based on scope (namespaced vs cluster-scoped)
- **Namespace Pre-provisioning**: Providers can pre-create namespaces for better UX

**Important**: When `ClusterScope` mode is used, cluster-wide permissions are created instead of namespaced ones.

## API Changes in v0.5.0 release

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

### Major Changes

- **API Version Upgrade**: Introduced `v1alpha2` API version alongside existing `v1alpha1`
- **Service Exposure Refactoring**: Refactored the service exposure mechanism from embedded CRD specifications to a resource-based model:
- `APIServiceExportSpec` now uses `Resources []APIServiceExportResource` instead of embedded CRD specs
- `BoundSchema`: New resource type in `v1alpha2` that represents bound schemas in consumer clusters and tracks the status of synced resources
- This allows one APIServiceExport to reference multiple CRDs more efficiently

### Backend Architecture Improvements

- **MultiCluster Runtime Integration**: The backend now leverages `sigs.k8s.io/multicluster-runtime` for enhanced cluster management capabilities
- **Provider Support**: Built-in support for multiple backend providers including KCP through `github.com/kcp-dev/multicluster-provider`
- **Enhanced Cluster Operations**: Improved cluster-aware resource management with dedicated manager architecture for handling multi-cluster scenarios
18 changes: 0 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,6 @@ All the actions shown between the clusters are done by the konnector, except: th

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

## API Changes in v0.5.0 release

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

### Major Changes

- **API Version Upgrade**: Introduced `v1alpha2` API version alongside existing `v1alpha1`
- **Service Exposure Refactoring**: Refactored the service exposure mechanism from embedded CRD specifications to a resource-based model:
- `APIServiceExportSpec` now uses `Resources []APIServiceExportResource` instead of embedded CRD specs
- `BoundSchema`: New resource type in `v1alpha2` that represents bound schemas in consumer clusters and tracks the status of synced resources
- This allows one APIServiceExport to reference multiple CRDs more efficiently

### Backend Architecture Improvements

- **MultiCluster Runtime Integration**: The backend now leverages `sigs.k8s.io/multicluster-runtime` for enhanced cluster management capabilities
- **Provider Support**: Built-in support for multiple backend providers including KCP through `github.com/kcp-dev/multicluster-provider`
- **Enhanced Cluster Operations**: Improved cluster-aware resource management with dedicated manager architecture for handling multi-cluster scenarios

### Limitations

These limitations are part of the roadmap and will be addressed in the future.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ func getBoundSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedE
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexportrequests/finalizers,verbs=update
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiresourceschemas,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=resources=apiservicenamespaces,verbs=get;list;watch;create

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down
122 changes: 47 additions & 75 deletions backend/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,15 @@ import (
"github.com/gorilla/securecookie"
"golang.org/x/oauth2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
componentbaseversion "k8s.io/component-base/version"
"k8s.io/klog/v2"

"github.com/kube-bind/kube-bind/backend/kubernetes"
"github.com/kube-bind/kube-bind/backend/kubernetes/resources"
"github.com/kube-bind/kube-bind/backend/session"
"github.com/kube-bind/kube-bind/backend/template"
bindversion "github.com/kube-bind/kube-bind/pkg/version"
kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
"github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2/helpers"
)

var (
Expand Down Expand Up @@ -346,12 +342,14 @@ func createSessionState(authCode *AuthCode, token *oauth2.Token) (*session.State
}

type UISchema struct {
Name string
Version string
Group string
Kind string
Scope string // "Namespaced" or "Cluster"
Resource string
Scope string // "Namespaced" or "Cluster"

Name string
Description string

Resources []kubebindv1alpha2.APIServiceExportResource
PermissionClaims []kubebindv1alpha2.PermissionClaim
Namespaces []kubebindv1alpha2.Namespaces

// SessionID
SessionID string
Expand All @@ -376,44 +374,26 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) {
return
}

exportedSchemas, err := h.getBackendDynamicResource(r.Context(), providerCluster)
templates, err := h.listCollectionTemplates(r.Context(), providerCluster)
if err != nil {
logger.Error(err, "failed to get dynamic resources")
logger.Error(err, "failed to get template resources")
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
Comment thread
mjudeikis marked this conversation as resolved.

result := make([]UISchema, 0, len(exportedSchemas))
for _, item := range exportedSchemas {
result := make([]UISchema, 0, len(templates.Items))
for _, item := range templates.Items {
if !strings.EqualFold(h.scope.String(), string(item.Spec.Scope)) && h.scope != kubebindv1alpha2.ClusterScope {
continue
}

if len(item.Spec.Versions) == 0 {
logger.Error(fmt.Errorf("no versions found"), "skipping schema", "name", item.Name)
continue
}
// pick first served version
ver := ""
for _, v := range item.Spec.Versions {
if v.Served {
ver = v.Name
break
}
}
if ver == "" {
logger.Error(fmt.Errorf("no served versions found"), "skipping schema", "name", item.Name)
continue
}
result = append(result, UISchema{
Name: item.GetName(),
Kind: item.Spec.Names.Kind,
Scope: string(item.Spec.Scope),
Version: ver,
Group: item.Spec.Group,
// Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it.
Resource: item.Spec.Names.Plural,
SessionID: sessionID,
Name: item.GetName(),
Scope: string(item.Spec.Scope),
PermissionClaims: item.Spec.PermissionClaims,
Resources: item.Spec.Resources,
Namespaces: item.Spec.Namespaces,
SessionID: sessionID,
})
}

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

func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
logger := getLogger(r)
group := r.URL.Query().Get("group")
resource := r.URL.Query().Get("resource")
version := r.URL.Query().Get("version")
templateName := r.URL.Query().Get("template")
providerCluster := mux.Vars(r)["cluster"]

prepareNoCache(w)
Expand Down Expand Up @@ -466,24 +444,26 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) {
return
}

// Module consist of many resources and permissionClaims. Read it and translate to
template, err := h.kubeManager.GetTemplates(r.Context(), providerCluster, templateName)
if err != nil {
logger.Error(err, "failed to get template")
http.Error(w, "internal error", http.StatusInternalServerError)
return
}

request := kubebindv1alpha2.APIServiceExportRequestResponse{
TypeMeta: metav1.TypeMeta{
APIVersion: kubebindv1alpha2.SchemeGroupVersion.String(),
Kind: "APIServiceExportRequest",
},
ObjectMeta: kubebindv1alpha2.NameObjectMeta{
// this is good for one resource. If there are more (in the future),
// we need a better name heuristic. Note: it does not have to be unique.
// But pretty is better.
Name: resource + "." + group,
Name: templateName,
},
Spec: kubebindv1alpha2.APIServiceExportRequestSpec{
Resources: []kubebindv1alpha2.APIServiceExportRequestResource{
{
GroupResource: kubebindv1alpha2.GroupResource{Group: group, Resource: resource},
Versions: []string{version},
},
},
Resources: template.Spec.Resources,
PermissionClaims: template.Spec.PermissionClaims,
Namespaces: template.Spec.Namespaces,
},
}

Expand Down Expand Up @@ -543,34 +523,26 @@ func mustRead(f func(name string) ([]byte, error), name string) string {
return string(bs)
}

func (h *handler) getBackendDynamicResource(ctx context.Context, cluster string) (kubebindv1alpha2.ExportedSchemas, error) {
labelSelector := labels.Set{
resources.ExportedCRDsLabel: "true",
}

parts := strings.SplitN(h.schemaSource, ".", 3)
if len(parts) != 3 { // We check this in validation, but just in case.
return nil, fmt.Errorf("invalid schema source: %q", h.schemaSource)
}

gvk := schema.GroupVersionKind{
Kind: parts[0],
Version: parts[1],
Group: parts[2],
}
list, err := h.kubeManager.ListDynamicResources(ctx, cluster, gvk, labelSelector.AsSelector())
// listCollectionTemplates fetches the list of Collections from the backend cluster.
// Flow is:
// 1. List Collection and check what modules we are targeting
// 2. Get templates from the backend cluster and construct shallow-bound schemas (no crd content).
func (h *handler) listCollectionTemplates(ctx context.Context, cluster string) (*kubebindv1alpha2.APIServiceExportTemplateList, error) {
collections, err := h.kubeManager.ListCollections(ctx, cluster)
if err != nil {
return nil, fmt.Errorf("failed to list resources: %w", err)
return nil, fmt.Errorf("failed to list collections: %w", err)
}
Comment thread
mjudeikis marked this conversation as resolved.

boundSchemas := make(kubebindv1alpha2.ExportedSchemas, len(list.Items))
for _, item := range list.Items {
boundSchema, err := helpers.UnstructuredToBoundSchema(item)
if err != nil {
return nil, err
templates := &kubebindv1alpha2.APIServiceExportTemplateList{}
for _, collection := range collections.Items {
for _, t := range collection.Spec.Templates {
template, err := h.kubeManager.GetTemplates(ctx, cluster, t.Name)
if err != nil {
return nil, fmt.Errorf("failed to get template %q: %w", t.Name, err)
}
templates.Items = append(templates.Items, *template)
}
boundSchemas[boundSchema.ResourceGroupName()] = boundSchema
}

return boundSchemas, nil
return templates, nil
}
32 changes: 32 additions & 0 deletions backend/kubernetes/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,38 @@ func (m *Manager) ListCustomResourceDefinitions(ctx context.Context, cluster str
return &crds, nil
}

func (m *Manager) ListCollections(ctx context.Context, cluster string) (*kubebindv1alpha2.CollectionList, error) {
cl, err := m.manager.GetCluster(ctx, cluster)
if err != nil {
return nil, err
}
c := cl.GetClient()

var collections kubebindv1alpha2.CollectionList
err = c.List(ctx, &collections)
if err != nil {
return nil, err
}

return &collections, nil
}

func (m *Manager) GetTemplates(ctx context.Context, cluster, name string) (*kubebindv1alpha2.APIServiceExportTemplate, error) {
cl, err := m.manager.GetCluster(ctx, cluster)
if err != nil {
return nil, err
}
c := cl.GetClient()

var template kubebindv1alpha2.APIServiceExportTemplate
err = c.Get(ctx, types.NamespacedName{Name: name}, &template)
if err != nil {
return nil, err
}

return &template, nil
}

func (m *Manager) ListDynamicResources(ctx context.Context, cluster string, gvk schema.GroupVersionKind, selector labels.Selector) (*unstructured.UnstructuredList, error) {
cl, err := m.manager.GetCluster(ctx, cluster)
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion backend/kubernetes/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@ const (
KubeconfigSecretName = "kubeconfig"
ClusterBindingName = "cluster"

// TODO(MQ): maybe think of a better label name.
ExportedCRDsLabel = "kube-bind.io/exported"
)
49 changes: 0 additions & 49 deletions backend/template/login.html

This file was deleted.

Loading