diff --git a/backend/controllers/clusterbinding/clusterbinding_controller.go b/backend/controllers/clusterbinding/clusterbinding_controller.go index c7e380b6e..029ff22de 100644 --- a/backend/controllers/clusterbinding/clusterbinding_controller.go +++ b/backend/controllers/clusterbinding/clusterbinding_controller.go @@ -77,11 +77,11 @@ func NewClusterBindingReconciler( } return exports, nil }, - getAPIResourceSchema: func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - result := &kubebindv1alpha2.APIResourceSchema{} - err := cache.Get(ctx, types.NamespacedName{Name: name}, result) + getBoundSchema: func(ctx context.Context, cache cache.Cache, namespace, name string) (*kubebindv1alpha2.BoundSchema, error) { + result := &kubebindv1alpha2.BoundSchema{} + err := cache.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, result) if err != nil { - return nil, fmt.Errorf("failed to get APIResourceSchema %q: %w", name, err) + return nil, fmt.Errorf("failed to get BoundSchema %q: %w", name, err) } return result, nil }, diff --git a/backend/controllers/clusterbinding/clusterbinding_reconcile.go b/backend/controllers/clusterbinding/clusterbinding_reconcile.go index ebf235d53..d72603e84 100644 --- a/backend/controllers/clusterbinding/clusterbinding_reconcile.go +++ b/backend/controllers/clusterbinding/clusterbinding_reconcile.go @@ -19,7 +19,9 @@ package clusterbinding import ( "context" "fmt" + "maps" "reflect" + "slices" "time" corev1 "k8s.io/api/core/v1" @@ -40,11 +42,11 @@ import ( type reconciler struct { scope kubebindv1alpha2.InformerScope - listServiceExports func(ctx context.Context, cache cache.Cache, ns string) ([]*kubebindv1alpha2.APIServiceExport, error) - getAPIResourceSchema func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) - getClusterRole func(ctx context.Context, cache cache.Cache, name string) (*rbacv1.ClusterRole, error) - createClusterRole func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRole) error - updateClusterRole func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRole) error + listServiceExports func(ctx context.Context, cache cache.Cache, ns string) ([]*kubebindv1alpha2.APIServiceExport, error) + getBoundSchema func(ctx context.Context, cache cache.Cache, namespace, name string) (*kubebindv1alpha2.BoundSchema, error) + getClusterRole func(ctx context.Context, cache cache.Cache, name string) (*rbacv1.ClusterRole, error) + createClusterRole func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRole) error + updateClusterRole func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRole) error getClusterRoleBinding func(ctx context.Context, cache cache.Cache, name string) (*rbacv1.ClusterRoleBinding, error) createClusterRoleBinding func(ctx context.Context, client client.Client, binding *rbacv1.ClusterRoleBinding) error @@ -148,24 +150,35 @@ func (r *reconciler) ensureRBACClusterRole(ctx context.Context, client client.Cl }, }, }, - } + Rules: []rbacv1.PolicyRule{ + // Always need to be able to get/list/watch the BoundSchemas + // to be able to figure out what to bind. + { + APIGroups: []string{kubebindv1alpha2.GroupName}, + Resources: []string{"boundschemas"}, + Verbs: []string{"get", "list", "watch"}, + }, + }} for _, export := range exports { + // Collect unique GroupResources and sort for stable rule ordering. + grSet := map[string]kubebindv1alpha2.GroupResource{} for _, res := range export.Spec.Resources { - schema, err := r.getAPIResourceSchema(ctx, cache, res.Name) + key := res.ResourceGroupName() + grSet[key] = kubebindv1alpha2.GroupResource{Group: res.Group, Resource: res.Resource} + } + keys := slices.Collect(maps.Keys(grSet)) + slices.Sort(keys) + for _, k := range keys { + // k is already normalized (e.g., "pods.core" for empty group). + schema, err := r.getBoundSchema(ctx, cache, clusterBinding.Namespace, k) if err != nil { - return fmt.Errorf("failed to get APIResourceSchema %w", err) + return fmt.Errorf("failed to get BoundSchema %q: %w", k, err) } - expected.Rules = append(expected.Rules, rbacv1.PolicyRule{ - APIGroups: []string{schema.Spec.APIResourceSchemaCRDSpec.Group}, - Resources: []string{schema.Spec.APIResourceSchemaCRDSpec.Names.Plural}, - Verbs: []string{"get", "list", "watch", "update", "patch", "delete", "create"}, - }, - rbacv1.PolicyRule{ - APIGroups: []string{kubebindv1alpha2.GroupName}, - Resources: []string{"apiresourceschemas"}, - Verbs: []string{"get", "list", "watch"}, + APIGroups: []string{schema.Spec.Group}, + Resources: []string{schema.Spec.Names.Plural}, + Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}, }, ) } @@ -192,7 +205,6 @@ func (r *reconciler) ensureRBACClusterRoleBinding(ctx context.Context, client cl if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("failed to get ClusterRoleBinding %s: %w", name, err) } - if r.scope != kubebindv1alpha2.ClusterScope { if err := r.deleteClusterRoleBinding(ctx, client, name); err != nil && !errors.IsNotFound(err) { return fmt.Errorf("failed to delete ClusterRoleBinding %s: %w", name, err) diff --git a/backend/controllers/serviceexport/serviceexport_controller.go b/backend/controllers/serviceexport/serviceexport_controller.go index f3a455267..7d2cdd2e7 100644 --- a/backend/controllers/serviceexport/serviceexport_controller.go +++ b/backend/controllers/serviceexport/serviceexport_controller.go @@ -58,18 +58,18 @@ func NewAPIServiceExportReconciler( mgr mcmanager.Manager, opts controller.TypedOptions[mcreconcile.Request], ) (*APIServiceExportReconciler, error) { - if err := mgr.GetFieldIndexer().IndexField(ctx, &kubebindv1alpha2.APIServiceExport{}, indexers.ServiceExportByAPIResourceSchema, - indexers.IndexServiceExportByAPIResourceSchema); err != nil { - return nil, fmt.Errorf("failed to setup ServiceExportByAPIResourceSchema indexer: %w", err) + if err := mgr.GetFieldIndexer().IndexField(ctx, &kubebindv1alpha2.APIServiceExport{}, indexers.ServiceExportByBoundSchema, + indexers.IndexServiceExportByBoundSchema); err != nil { + return nil, fmt.Errorf("failed to setup ServiceExportByBoundSchema indexer: %w", err) } r := &APIServiceExportReconciler{ manager: mgr, opts: opts, reconciler: reconciler{ - getAPIResourceSchema: func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - var schema kubebindv1alpha2.APIResourceSchema - key := types.NamespacedName{Name: name} + 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} if err := cache.Get(ctx, key, &schema); err != nil { return nil, err } @@ -92,6 +92,8 @@ func NewAPIServiceExportReconciler( //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports/status,verbs=get;update;patch //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports/finalizers,verbs=update +//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=boundschemas,verbs=get;list;watch +//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=boundschemas/status,verbs=get;update;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -128,10 +130,18 @@ func (r *APIServiceExportReconciler) Reconcile(ctx context.Context, req mcreconc return ctrl.Result{}, err } - // Update status if it has changed - if !equality.Semantic.DeepEqual(original, apiServiceExport) { - err := client.Status().Update(ctx, apiServiceExport) - if err != nil { + // Update annotations changed (hash), we need to propagate it and requeue for status changes. + // This is why we compare annotations only as we don't expect any changes to spec. + // Status changes are handled below. + if !equality.Semantic.DeepEqual(original.Annotations, apiServiceExport.Annotations) { + if err := client.Update(ctx, apiServiceExport); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update APIServiceExport: %w", err) + } + logger.Info("APIServiceExport hash updated", "namespace", apiServiceExport.Namespace, "name", apiServiceExport.Name) + return ctrl.Result{Requeue: true}, nil + } + if !equality.Semantic.DeepEqual(original.Status, apiServiceExport.Status) { + if err := client.Status().Update(ctx, apiServiceExport); err != nil { return ctrl.Result{}, fmt.Errorf("failed to update APIServiceExport status: %w", err) } logger.Info("APIServiceExport status updated", "namespace", apiServiceExport.Namespace, "name", apiServiceExport.Name) @@ -141,14 +151,14 @@ func (r *APIServiceExportReconciler) Reconcile(ctx context.Context, req mcreconc } // getAPIResourceSchemaMapper returns a mapper function that uses the manager to find related APIServiceExports. -func getAPIResourceSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { +func getBoundSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { return handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []mcreconcile.Request { - apiResourceSchema := obj.(*kubebindv1alpha2.APIResourceSchema) - apiResourceSchemaKey := apiResourceSchema.Name + boundSchema := obj.(*kubebindv1alpha2.BoundSchema) + boundSchemaKey := boundSchema.Spec.Names.Plural + "." + boundSchema.Spec.Group c := cl.GetClient() var exports kubebindv1alpha2.APIServiceExportList - if err := c.List(ctx, &exports, client.MatchingFields{indexers.ServiceExportByAPIResourceSchema: apiResourceSchemaKey}); err != nil { + if err := c.List(ctx, &exports, client.MatchingFields{indexers.ServiceExportByBoundSchema: boundSchemaKey}); err != nil { return []mcreconcile.Request{} } @@ -171,8 +181,8 @@ func (r *APIServiceExportReconciler) SetupWithManager(mgr mcmanager.Manager) err return mcbuilder.ControllerManagedBy(mgr). For(&kubebindv1alpha2.APIServiceExport{}). Watches( - &kubebindv1alpha2.APIResourceSchema{}, - getAPIResourceSchemaMapper, + &kubebindv1alpha2.BoundSchema{}, + getBoundSchemaMapper, ). WithOptions(r.opts). Named(controllerName). diff --git a/backend/controllers/serviceexport/serviceexport_reconcile.go b/backend/controllers/serviceexport/serviceexport_reconcile.go index 061fbb967..00ffe55bb 100644 --- a/backend/controllers/serviceexport/serviceexport_reconcile.go +++ b/backend/controllers/serviceexport/serviceexport_reconcile.go @@ -18,10 +18,6 @@ package serviceexport import ( "context" - "crypto/sha256" - "encoding/hex" - "slices" - "sort" "k8s.io/apimachinery/pkg/api/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors" @@ -30,75 +26,65 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" - kubebindhelpers "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2/helpers" + "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2/helpers" + conditionsapi "github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" "github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/util/conditions" ) type reconciler struct { - getAPIResourceSchema func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) - deleteServiceExport func(ctx context.Context, client client.Client, namespace, name string) error + 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 } func (r *reconciler) reconcile(ctx context.Context, cache cache.Cache, export *kubebindv1alpha2.APIServiceExport) error { var errs []error - if specChanged, err := r.ensureSchema(ctx, cache, export); err != nil { + if err := r.ensureSchema(ctx, cache, export); err != nil { errs = append(errs, err) - } else if specChanged { - // TODO: This should be separate controller for apiresourceschemas. - // This is wrong place now. - // r.requeue(export) - return nil } return utilerrors.NewAggregate(errs) } -func (r *reconciler) ensureSchema(ctx context.Context, cache cache.Cache, export *kubebindv1alpha2.APIServiceExport) (specChanged bool, err error) { +func (r *reconciler) ensureSchema(ctx context.Context, cache cache.Cache, export *kubebindv1alpha2.APIServiceExport) error { logger := klog.FromContext(ctx) - leafHashes := make([]string, 0, len(export.Spec.Resources)) - for _, resourceRef := range export.Spec.Resources { - if resourceRef.Type != "APIResourceSchema" { - logger.V(1).Info("Skipping unsupported resource type", "type", resourceRef.Type) - continue - } + schemas := make([]*kubebindv1alpha2.BoundSchema, 0, len(export.Spec.Resources)) - schema, err := r.getAPIResourceSchema(ctx, cache, resourceRef.Name) + for _, res := range export.Spec.Resources { + name := res.ResourceGroupName() + schema, err := r.getBoundSchema(ctx, cache, export.Namespace, name) if err != nil { if errors.IsNotFound(err) { - continue + conditions.MarkFalse( + export, + kubebindv1alpha2.APIServiceExportConditionProviderInSync, + "BoundSchemaMissing", + conditionsapi.ConditionSeverityError, + "BoundSchema %q is not available: %v", name, err) + return nil } - return false, err + return err } - hash := kubebindhelpers.APIResourceSchemaCRDSpecHash(&schema.Spec.APIResourceSchemaCRDSpec) - leafHashes = append(leafHashes, hash) + schemas = append(schemas, schema) } - hashOfHashes := hashOfHashes(leafHashes) + hash, err := helpers.BoundSchemasSpecHash(schemas) + if err != nil { + return err + } - if export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey] != hashOfHashes { + if export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey] != hash { // both exist, update APIServiceExport - logger.V(1).Info("Updating APIServiceExport. Hash mismatch", "hash", hashOfHashes, "expected", export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey]) + logger.V(1).Info("Updating APIServiceExport. Hash mismatch", "hash", hash, "expected", export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey]) if export.Annotations == nil { export.Annotations = map[string]string{} } - export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey] = hashOfHashes - return true, nil + export.Annotations[kubebindv1alpha2.SourceSpecHashAnnotationKey] = hash + return nil } conditions.MarkTrue(export, kubebindv1alpha2.APIServiceExportConditionProviderInSync) - return false, nil -} - -func hashOfHashes(hashes []string) string { - hexHashes := slices.Clone(hashes) - sort.Strings(hexHashes) - - rootHasher := sha256.New() - for _, h := range hexHashes { - rootHasher.Write([]byte(h)) - } - return hex.EncodeToString(rootHasher.Sum(nil)) + return nil } diff --git a/backend/controllers/serviceexportrequest/serviceexportrequest_controller.go b/backend/controllers/serviceexportrequest/serviceexportrequest_controller.go index f21a71bc6..d34985ab3 100644 --- a/backend/controllers/serviceexportrequest/serviceexportrequest_controller.go +++ b/backend/controllers/serviceexportrequest/serviceexportrequest_controller.go @@ -61,6 +61,7 @@ func NewAPIServiceExportRequestReconciler( opts controller.TypedOptions[mcreconcile.Request], scope kubebindv1alpha2.InformerScope, isolation kubebindv1alpha2.Isolation, + schemaSource string, ) (*APIServiceExportRequestReconciler, error) { // Set up field indexers for APIServiceExportRequests if err := mgr.GetFieldIndexer().IndexField(ctx, &kubebindv1alpha2.APIServiceExportRequest{}, indexers.ServiceExportRequestByServiceExport, @@ -81,9 +82,10 @@ func NewAPIServiceExportRequestReconciler( reconciler: reconciler{ informerScope: scope, clusterScopedIsolation: isolation, - getAPIResourceSchema: func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - var schema kubebindv1alpha2.APIResourceSchema - key := types.NamespacedName{Name: name} + schemaSource: schemaSource, + 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} if err := cache.Get(ctx, key, &schema); err != nil { return nil, err } @@ -100,11 +102,8 @@ func NewAPIServiceExportRequestReconciler( createServiceExport: func(ctx context.Context, cl client.Client, resource *kubebindv1alpha2.APIServiceExport) error { return cl.Create(ctx, resource) }, - createAPIResourceSchema: func(ctx context.Context, cl client.Client, schema *kubebindv1alpha2.APIResourceSchema) (*kubebindv1alpha2.APIResourceSchema, error) { - if err := cl.Create(ctx, schema); err != nil { - return nil, err - } - return schema, nil + createBoundSchema: func(ctx context.Context, cl client.Client, schema *kubebindv1alpha2.BoundSchema) error { + return cl.Create(ctx, schema) }, deleteServiceExportRequest: func(ctx context.Context, cl client.Client, ns, name string) error { return cl.Delete(ctx, &kubebindv1alpha2.APIServiceExportRequest{ @@ -150,15 +149,15 @@ func getServiceExportRequestMapper(clusterName string, cl cluster.Cluster) handl }) } -// getAPIResourceSchemaMapper creates a mapping function for APIResourceSchema changes. -func getAPIResourceSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { +// getBoundSchemaMapper creates a mapping function for BoundSchema changes. +func getBoundSchemaMapper(clusterName string, cl cluster.Cluster) handler.TypedEventHandler[client.Object, mcreconcile.Request] { return handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []mcreconcile.Request { - apiResourceSchema := obj.(*kubebindv1alpha2.APIResourceSchema) - apiResourceSchemaKey := apiResourceSchema.Name + boundSchema := obj.(*kubebindv1alpha2.BoundSchema) + boundSchemaKey := boundSchema.Spec.Names.Plural + "." + boundSchema.Spec.Group c := cl.GetClient() var requests kubebindv1alpha2.APIServiceExportRequestList - if err := c.List(ctx, &requests, client.MatchingFields{indexers.ServiceExportRequestByGroupResource: apiResourceSchemaKey}); err != nil { + if err := c.List(ctx, &requests, client.MatchingFields{indexers.ServiceExportRequestByGroupResource: boundSchemaKey}); err != nil { return []mcreconcile.Request{} } @@ -216,13 +215,20 @@ func (r *APIServiceExportRequestReconciler) Reconcile(ctx context.Context, req m // Run the reconciliation logic if err := r.reconciler.reconcile(ctx, client, cache, apiServiceExportRequest); err != nil { logger.Error(err, "Failed to reconcile APIServiceExportRequest") + if !reflect.DeepEqual(original.Status.Phase, apiServiceExportRequest.Status.Phase) { + if err := client.Status().Update(ctx, apiServiceExportRequest); err != nil { + logger.Error(err, "Failed to update APIServiceExportRequest status") + return ctrl.Result{}, fmt.Errorf("failed to update APIServiceExportRequest status: %w", err) + } + logger.Info("APIServiceExportRequest status updated", "namespace", apiServiceExportRequest.Namespace, "name", apiServiceExportRequest.Name) + } + return ctrl.Result{}, err } // Update status if it has changed if !reflect.DeepEqual(original.Status, apiServiceExportRequest.Status) { - err := client.Status().Update(ctx, apiServiceExportRequest) - if err != nil { + if err := client.Status().Update(ctx, apiServiceExportRequest); err != nil { logger.Error(err, "Failed to update APIServiceExportRequest status") return ctrl.Result{}, fmt.Errorf("failed to update APIServiceExportRequest status: %w", err) } @@ -241,8 +247,8 @@ func (r *APIServiceExportRequestReconciler) SetupWithManager(mgr mcmanager.Manag getServiceExportRequestMapper, ). Watches( - &kubebindv1alpha2.APIResourceSchema{}, - getAPIResourceSchemaMapper, + &kubebindv1alpha2.BoundSchema{}, + getBoundSchemaMapper, ). WithOptions(r.opts). Named(controllerName). diff --git a/backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go b/backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go index 9b80f31aa..0bcf4cf9c 100644 --- a/backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go +++ b/backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go @@ -18,16 +18,21 @@ package serviceexportrequest import ( "context" + "fmt" + "strings" "time" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/kube-bind/kube-bind/backend/kubernetes/resources" kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2/helpers" conditionsapi "github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" @@ -37,41 +42,169 @@ import ( type reconciler struct { informerScope kubebindv1alpha2.InformerScope clusterScopedIsolation kubebindv1alpha2.Isolation + schemaSource string + + getBoundSchema func(ctx context.Context, cache cache.Cache, namespace, name string) (*kubebindv1alpha2.BoundSchema, error) + createBoundSchema func(ctx context.Context, cl client.Client, schema *kubebindv1alpha2.BoundSchema) error - getAPIResourceSchema func(ctx context.Context, cache cache.Cache, name string) (*kubebindv1alpha2.APIResourceSchema, error) getServiceExport func(ctx context.Context, cache cache.Cache, ns, name string) (*kubebindv1alpha2.APIServiceExport, error) createServiceExport func(ctx context.Context, cl client.Client, resource *kubebindv1alpha2.APIServiceExport) error - createAPIResourceSchema func(ctx context.Context, cl client.Client, schema *kubebindv1alpha2.APIResourceSchema) (*kubebindv1alpha2.APIResourceSchema, error) deleteServiceExportRequest func(ctx context.Context, cl client.Client, namespace, name string) error } func (r *reconciler) reconcile(ctx context.Context, cl client.Client, cache cache.Cache, req *kubebindv1alpha2.APIServiceExportRequest) error { - var errs []error + if err := r.ensureBoundSchemas(ctx, cl, cache, req); err != nil { + conditions.SetSummary(req) + return err + } if err := r.ensureExports(ctx, cl, cache, req); err != nil { - errs = append(errs, err) + conditions.SetSummary(req) + return 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 + conditions.SetSummary(req) - return utilerrors.NewAggregate(errs) + return nil +} + +func (r *reconciler) ensureBoundSchemas(ctx context.Context, cl client.Client, cache cache.Cache, req *kubebindv1alpha2.APIServiceExportRequest) error { + // Ensure all bound schemas exist + for _, res := range req.Spec.Resources { + parts := strings.SplitN(r.schemaSource, ".", 3) + if len(parts) != 3 { // We check this in validation, but just in case. + return fmt.Errorf("malformed schema source: %q", r.schemaSource) + } + + gvk := schema.GroupVersionKind{ + Kind: parts[0], + Version: parts[1], + Group: parts[2], + } + + // Ensure we have the List kind + listGVK := gvk + if !strings.HasSuffix(listGVK.Kind, "List") { + listGVK.Kind += "List" + } + + list := &unstructured.UnstructuredList{} + list.SetGroupVersionKind(listGVK) + + // TODO(mjudeikis): This is hardcoded here and in handlers.go for now. + labelSelector := labels.Set{ + resources.ExportedCRDsLabel: "true", + } + + listOpts := []client.ListOption{} + listOpts = append(listOpts, client.MatchingLabelsSelector{Selector: labelSelector.AsSelector()}) + + if err := cl.List(ctx, list, listOpts...); err != nil { + return err + } + + for _, item := range list.Items { + var schemaFailed bool + obj := item.UnstructuredContent() + group, ok, err := unstructured.NestedString(obj, "spec", "group") + if !ok || err != nil || group == "" { + klog.FromContext(ctx).Error(err, "Skipping invalid schema: missing group", "ns", item.GetNamespace(), "name", item.GetName()) + schemaFailed = true + } + plural, ok, err := unstructured.NestedString(obj, "spec", "names", "plural") + if !ok || err != nil || plural == "" { + klog.FromContext(ctx).Error(err, "Skipping invalid schema: missing names.plural", "ns", item.GetNamespace(), "name", item.GetName()) + schemaFailed = true + } + + scope, ok, err := unstructured.NestedString(obj, "spec", "scope") + if !ok || err != nil || scope == "" { + klog.FromContext(ctx).Error(err, "Skipping invalid schema: missing scope", "ns", item.GetNamespace(), "name", item.GetName()) + schemaFailed = true + } + + if schemaFailed { + conditions.MarkFalse( + req, + kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, + "APIServiceExportRequestInvalid", + conditionsapi.ConditionSeverityError, + "APIServiceExportRequest %s is invalid: resource %s/%s has invalid schema", + req.Name, group, plural, + ) + req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseFailed + return fmt.Errorf("resource %s/%s is invalid", group, plural) + } + + if group == res.Group && plural == res.Resource { + // Important: This checks if the resource are correctly scoped. If consumer is namespaced, we can't allow this. + // We terminate early to prevent triggering other controllers. + if r.informerScope.String() != scope && r.informerScope != kubebindv1alpha2.ClusterScope { + conditions.MarkFalse( + req, + kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, + "APIServiceExportRequestInvalid", + conditionsapi.ConditionSeverityError, + "APIServiceExportRequest %s is invalid: resource %s/%s has scope %q which is incompatible with backend informer scope %q", + req.Name, group, plural, scope, r.informerScope, + ) + req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseFailed + req.Status.TerminalMessage = conditions.GetMessage(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) + // We can't proceed with this request. + return fmt.Errorf("resource %s/%s has scope %q which is incompatible with backend informer scope %q", group, plural, scope, r.informerScope) + } + + // https://github.com/kube-bind/kube-bind/issues/297 to fix. + boundSchema, err := helpers.UnstructuredToBoundSchema(item) + if err != nil { + return err + } + boundSchema.Name = res.ResourceGroupName() + boundSchema.Namespace = req.Namespace + boundSchema.Spec.InformerScope = r.informerScope + boundSchema.ResourceVersion = "" + + obj, err := r.getBoundSchema(ctx, cache, boundSchema.Namespace, boundSchema.Name) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + // TODO(mjudeikis): https://github.com/kube-bind/kube-bind/issues/297 + if obj != nil { + continue + } + + if err := r.createBoundSchema(ctx, cl, boundSchema); err != nil { + return err + } + } + } + } + + return nil } func (r *reconciler) ensureExports(ctx context.Context, cl client.Client, cache cache.Cache, req *kubebindv1alpha2.APIServiceExportRequest) error { logger := klog.FromContext(ctx) + var schemas []*kubebindv1alpha2.BoundSchema + var scope apiextensionsv1.ResourceScope if req.Status.Phase == kubebindv1alpha2.APIServiceExportRequestPhasePending { for _, res := range req.Spec.Resources { - name := res.Resource + "." + res.Group - apiResourceSchema, err := r.getAPIResourceSchema(ctx, cache, name) + name := res.ResourceGroupName() + boundSchema, err := r.getBoundSchema(ctx, cache, req.Namespace, name) if err != nil { if apierrors.IsNotFound(err) { conditions.MarkFalse( req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, - "APIResourceSchemaNotFound", + "BoundSchemaNotFound", conditionsapi.ConditionSeverityError, - "APIResourceSchema %s in the service provider cluster not found", + "BoundSchema %s in the service provider cluster not found", name, ) return err @@ -79,39 +212,59 @@ func (r *reconciler) ensureExports(ctx context.Context, cl client.Client, cache return err } - if _, err := r.getServiceExport(ctx, cache, req.Namespace, name); err != nil && !apierrors.IsNotFound(err) { + // Collect all schemas for hashing. + // TODO(mjudeikis) Scope is same for all crds so we keep stamping it over. We might want to change this + scope = boundSchema.Spec.Scope + schemas = append(schemas, boundSchema) + } + + if _, err := r.getServiceExport(ctx, cache, req.Namespace, req.Name); err != nil { + if !apierrors.IsNotFound(err) { return err - } else if err == nil { - continue } + } else { + // already exists; nothing to do + conditions.MarkTrue(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) + return nil + } - hash := helpers.APIResourceSchemaCRDSpecHash(&apiResourceSchema.Spec.APIResourceSchemaCRDSpec) - export := &kubebindv1alpha2.APIServiceExport{ - ObjectMeta: metav1.ObjectMeta{ - Name: req.Name, - Namespace: req.Namespace, - Annotations: map[string]string{ - kubebindv1alpha2.SourceSpecHashAnnotationKey: hash, - }, + // https://github.com/kube-bind/kube-bind/issues/297 To fix. + hash, err := helpers.BoundSchemasSpecHash(schemas) + if err != nil { + return err + } + export := &kubebindv1alpha2.APIServiceExport{ + ObjectMeta: metav1.ObjectMeta{ + Name: req.Name, + Namespace: req.Namespace, + Annotations: map[string]string{ + kubebindv1alpha2.SourceSpecHashAnnotationKey: hash, }, - Spec: kubebindv1alpha2.APIServiceExportSpec{ - Resources: []kubebindv1alpha2.APIResourceSchemaReference{ - { - Type: "APIResourceSchema", - Name: apiResourceSchema.Name, - }, - }, - InformerScope: r.informerScope, + }, + Spec: kubebindv1alpha2.APIServiceExportSpec{ + InformerScope: r.informerScope, + }, + } + if scope == apiextensionsv1.ClusterScoped { + export.Spec.ClusterScopedIsolation = r.clusterScopedIsolation + } + + for _, res := range req.Spec.Resources { + export.Spec.Resources = append(export.Spec.Resources, kubebindv1alpha2.APIServiceExportResource{ + GroupResource: kubebindv1alpha2.GroupResource{ + Group: res.Group, + Resource: res.Resource, }, - } - if apiResourceSchema.Spec.Scope == apiextensionsv1.ClusterScoped { - export.Spec.ClusterScopedIsolation = r.clusterScopedIsolation - } + Versions: res.Versions, + }) + } - logger.V(1).Info("Creating APIServiceExport", "name", export.Name, "namespace", export.Namespace) - if err = r.createServiceExport(ctx, cl, export); err != nil { - return err + logger.V(1).Info("Creating APIServiceExport", "name", export.Name, "namespace", export.Namespace) + if err := r.createServiceExport(ctx, cl, export); err != nil { + if apierrors.IsAlreadyExists(err) { + return nil } + return err } conditions.MarkTrue(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) @@ -121,8 +274,6 @@ func (r *reconciler) ensureExports(ctx context.Context, cl client.Client, cache req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseFailed req.Status.TerminalMessage = conditions.GetMessage(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) } - - return nil } if time.Since(req.CreationTimestamp.Time) > 10*time.Minute { diff --git a/backend/http/handler.go b/backend/http/handler.go index 3ca2e8be1..e80864dfd 100644 --- a/backend/http/handler.go +++ b/backend/http/handler.go @@ -33,7 +33,6 @@ import ( "github.com/gorilla/mux" "github.com/gorilla/securecookie" "golang.org/x/oauth2" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -117,6 +116,12 @@ func (h *handler) AddRoutes(mux *mux.Router) { mux.HandleFunc("/authorize", h.handleAuthorize).Methods("GET") mux.HandleFunc("/callback", h.handleCallback).Methods("GET") + mux.HandleFunc("/healthz", h.handleHealthz).Methods("GET") +} + +func (h *handler) handleHealthz(w http.ResponseWriter, r *http.Request) { + prepareNoCache(w) + w.WriteHeader(http.StatusOK) } func (h *handler) handleServiceExport(w http.ResponseWriter, r *http.Request) { @@ -383,6 +388,12 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) { scope = "-" } + // TODO(mjudeikis): This logic is very brittle, needs rework. + // This will be improved in the permissionClaims PR. + if !strings.EqualFold(h.scope.String(), scope.(string)) && h.scope != kubebindv1alpha2.ClusterScope { + continue + } + group := item.UnstructuredContent()["spec"].(map[string]interface{})["group"] if group == nil { group = "-" @@ -435,7 +446,6 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) { func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) { logger := getLogger(r) - name := r.URL.Query().Get("name") group := r.URL.Query().Get("group") resource := r.URL.Query().Get("resource") version := r.URL.Query().Get("version") @@ -459,37 +469,6 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) { return } - // There is an intent to bind. We need to create APIResourceSchema if one does not exists. - { - apiResourceSchemas, err := h.getBackendDynamicResource(r.Context(), providerCluster) - if err != nil { - logger.Error(err, "failed to get dynamic resources") - http.Error(w, "internal error", http.StatusInternalServerError) - return - } - - schema := &unstructured.Unstructured{} - for _, item := range apiResourceSchemas.Items { - if item.GetName() == name { - schema = &item - break - } - } - if schema == nil || schema.GetName() != name { - logger.Error(nil, "no APIResourceSchema found", "name", name, "group", group, "resource", resource, "version", version) - http.Error(w, fmt.Sprintf("no APIResourceSchema found for %s.%s.%s/%s", group, resource, version, name), http.StatusNotFound) - return - } - - // create apiResourceSchema if not exists - err = h.kubeManager.CreateAPIResourceSchema(r.Context(), providerCluster, name, schema) - if err != nil && !apierrors.IsAlreadyExists(err) { - logger.Error(err, "failed to create APIResourceSchema") - http.Error(w, "internal error", http.StatusInternalServerError) - return - } - } - kfg, err := h.kubeManager.HandleResources(r.Context(), state.Token.Subject+"#"+state.ClusterID, providerCluster) if err != nil { logger.Error(err, "failed to handle resources") diff --git a/backend/kubernetes/manager.go b/backend/kubernetes/manager.go index ff7795301..e8da9ef78 100644 --- a/backend/kubernetes/manager.go +++ b/backend/kubernetes/manager.go @@ -26,7 +26,6 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" @@ -150,21 +149,6 @@ func (m *Manager) HandleResources(ctx context.Context, identity, cluster string) return kfgSecret.Data["kubeconfig"], nil } -func (m *Manager) ListAPIResourceSchemas(ctx context.Context, cluster string) (*kubebindv1alpha2.APIResourceSchemaList, error) { - cl, err := m.manager.GetCluster(ctx, cluster) - if err != nil { - return nil, err - } - cache := cl.GetCache() - - var schemas kubebindv1alpha2.APIResourceSchemaList - err = cache.List(ctx, &schemas) - if err != nil { - return nil, err - } - return &schemas, nil -} - func (m *Manager) ListCustomResourceDefinitions(ctx context.Context, cluster string, selector labels.Selector) (*apiextensionsv1.CustomResourceDefinitionList, error) { cl, err := m.manager.GetCluster(ctx, cluster) if err != nil { @@ -208,23 +192,3 @@ func (m *Manager) ListDynamicResources(ctx context.Context, cluster string, gvk return list, nil } - -func (m *Manager) CreateAPIResourceSchema(ctx context.Context, cluster string, name string, u *unstructured.Unstructured) error { - cl, err := m.manager.GetCluster(ctx, cluster) - if err != nil { - return err - } - c := cl.GetClient() - - apiResourceSchema := &kubebindv1alpha2.APIResourceSchema{} - err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), apiResourceSchema) - if err != nil { - return err - } - - apiResourceSchema.ResourceVersion = "" - apiResourceSchema.Name = name - apiResourceSchema.Spec.InformerScope = m.scope - - return c.Create(ctx, apiResourceSchema) -} diff --git a/backend/kubernetes/resources/rbac.go b/backend/kubernetes/resources/rbac.go index 082e62a2f..6e140fe63 100644 --- a/backend/kubernetes/resources/rbac.go +++ b/backend/kubernetes/resources/rbac.go @@ -104,23 +104,13 @@ func EnsureBinderClusterRole(ctx context.Context, client client.Client) error { }, { APIGroups: []string{"kube-bind.io"}, - Resources: []string{"apiresourceschemas"}, - Verbs: []string{"create", "delete", "patch", "update", "get", "list", "watch"}, - }, - { - APIGroups: []string{"kube-bind.io"}, - Resources: []string{"apiresourceschemas/status"}, - Verbs: []string{"get", "patch", "update"}, + Resources: []string{"boundschemas"}, + Verbs: []string{"get", "list", "watch"}, }, { APIGroups: []string{"kube-bind.io"}, - Resources: []string{"boundapiresourceschemas"}, - Verbs: []string{"create", "delete", "patch", "update", "get", "list", "watch"}, - }, - { - APIGroups: []string{"kube-bind.io"}, - Resources: []string{"boundapiresourceschemas/status"}, - Verbs: []string{"get", "patch", "update"}, + Resources: []string{"boundschemas/status"}, + Verbs: []string{"get", "list", "patch", "update"}, }, }, } diff --git a/backend/options/options.go b/backend/options/options.go index 65d0ba651..3a9f7c9df 100644 --- a/backend/options/options.go +++ b/backend/options/options.go @@ -56,7 +56,7 @@ type ExtraOptions struct { // Defines the source of the schema for the bind screen. // Options are: // CustomResourceDefinition.v1.apiextensions.k8s.io - // APIResourceSchema.v1alpha2.kube-bind.io + // APIResourceSchema.v1alpha1.apis.kcp.io SchemaSource string TestingAutoSelect string @@ -112,14 +112,17 @@ func (s SchemaSource) String() string { } var ( - APIResourceSchemaSource = SchemaSource("APIResourceSchema.v1alpha2.kube-bind.io") + KCPAPIResourceSchemaSource = SchemaSource("APIResourceSchema.v1alpha1.apis.kcp.io") CustomResourceDefinitionSource = SchemaSource("CustomResourceDefinition.v1.apiextensions.k8s.io") ) +// TODO(mjudeikis): https://github.com/kube-bind/kube-bind/issues/298 +// We should relax these once we happy they work with any schema. var schemaSourceAliases = map[string]string{ - CustomResourceDefinitionSource.String(): CustomResourceDefinitionSource.String(), - "apiresourceschema": APIResourceSchemaSource.String(), - "customresourcedefinition": CustomResourceDefinitionSource.String(), + CustomResourceDefinitionSource.String(): CustomResourceDefinitionSource.String(), // mostrly for e2e tests + "customresourcedefinitions": CustomResourceDefinitionSource.String(), + "apiresourceschemas": KCPAPIResourceSchemaSource.String(), + KCPAPIResourceSchemaSource.String(): KCPAPIResourceSchemaSource.String(), } func (options *Options) AddFlags(fs *pflag.FlagSet) { diff --git a/backend/server.go b/backend/server.go index d16eb4a32..b20dfd063 100644 --- a/backend/server.go +++ b/backend/server.go @@ -179,6 +179,7 @@ func NewServer(ctx context.Context, c *Config) (*Server, error) { opts, kubebindv1alpha2.InformerScope(c.Options.ConsumerScope), kubebindv1alpha2.Isolation(c.Options.ClusterScopedIsolation), + c.Options.SchemaSource, ) if err != nil { return nil, fmt.Errorf("error setting up ServiceExportRequest Controller: %w", err) diff --git a/cli/cmd/crd2apiresourceschema/cmd/crd2apiresourceschema.go b/cli/cmd/crd2apiresourceschema/cmd/crd2apiresourceschema.go deleted file mode 100644 index 11c132cbc..000000000 --- a/cli/cmd/crd2apiresourceschema/cmd/crd2apiresourceschema.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cmd - -import ( - goflags "flag" - "fmt" - "os" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/component-base/version" - "k8s.io/klog/v2" - - crd2apiresourceschemacmd "github.com/kube-bind/kube-bind/cli/pkg/crd2apiresourceschema/cmd" -) - -func CRD2APIResourceSchemaCmd() *cobra.Command { - rootCmd, err := crd2apiresourceschemacmd.New(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}) - if err != nil { - fmt.Fprintf(os.Stderr, "error: %v", err) - os.Exit(1) - } - // setup klog - fs := goflags.NewFlagSet("klog", goflags.PanicOnError) - klog.InitFlags(fs) - rootCmd.PersistentFlags().AddGoFlagSet(fs) - - if v := version.Get().String(); len(v) == 0 { - rootCmd.Version = "" - } else { - rootCmd.Version = v - } - - return rootCmd -} diff --git a/cli/cmd/crd2apiresourceschema/main.go b/cli/cmd/crd2apiresourceschema/main.go deleted file mode 100644 index f4c7207e3..000000000 --- a/cli/cmd/crd2apiresourceschema/main.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "fmt" - "os" - - "github.com/spf13/pflag" - - cmd "github.com/kube-bind/kube-bind/cli/cmd/crd2apiresourceschema/cmd" -) - -func main() { - flags := pflag.NewFlagSet("crd2apiresourceschema", pflag.ExitOnError) - pflag.CommandLine = flags - - command := cmd.CRD2APIResourceSchemaCmd() - if err := command.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) - os.Exit(1) - } -} diff --git a/cli/pkg/crd2apiresourceschema/cmd/cmd.go b/cli/pkg/crd2apiresourceschema/cmd/cmd.go deleted file mode 100644 index 68e80b079..000000000 --- a/cli/pkg/crd2apiresourceschema/cmd/cmd.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cmd - -import ( - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - _ "k8s.io/client-go/plugin/pkg/client/auth/exec" - _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - logsv1 "k8s.io/component-base/logs/api/v1" - - "github.com/kube-bind/kube-bind/cli/pkg/crd2apiresourceschema/plugin" -) - -var ( - CRD2APIResourceSchemaUses = ` -# Generate APIResourceSchemas from provided CRDs in the cluster and save them to YAML files in the specified output directory -crd2apiresourceschema --output-dir /output/dir - -# Generate APIResourceSchemas from provided CRDs in the cluster and save them to YAML files, specifying a different kubeconfig and output directory -crd2apiresourceschema --kubeconfig /path/to/your/kubeconfig --output-dir /path/to/output/dir - -# Generate and create APIResourceSchema objects for all CRDs in the cluster -crd2apiresourceschema --generate-in-cluster - -# Generate and create APIResourceSchema objects for all CRDs in the cluster, specifying a different kubeconfig -crd2apiresourceschema --kubeconfig /path/to/your/kubeconfig --generate-in-cluster -` -) - -func New(streams genericclioptions.IOStreams) (*cobra.Command, error) { - opts := plugin.NewCRD2APIResourceSchemaOptions(streams) - cmd := &cobra.Command{ - Use: "crd2apiresourceschema", - Short: "Create APIResourceSchema from provided CRDs in the cluster.", - Example: CRD2APIResourceSchemaUses, - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - if err := logsv1.ValidateAndApply(opts.Logs, nil); err != nil { - return err - } - - if len(args) > 1 { - return cmd.Help() - } - if err := opts.Complete(args); err != nil { - return err - } - - if err := opts.Validate(); err != nil { - return err - } - - return opts.Run(cmd.Context()) - }, - } - opts.AddCmdFlags(cmd) - - return cmd, nil -} diff --git a/cli/pkg/crd2apiresourceschema/plugin/crd2apiresourceschema.go b/cli/pkg/crd2apiresourceschema/plugin/crd2apiresourceschema.go deleted file mode 100644 index e5421c9ff..000000000 --- a/cli/pkg/crd2apiresourceschema/plugin/crd2apiresourceschema.go +++ /dev/null @@ -1,257 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package plugin - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "time" - - "github.com/spf13/cobra" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/dynamic" - "k8s.io/component-base/logs" - logsv1 "k8s.io/component-base/logs/api/v1" - "k8s.io/component-base/version" - - "github.com/kube-bind/kube-bind/cli/pkg/kubectl/base" - kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" - "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2/helpers" -) - -type CRD2APIResourceSchemaOptions struct { - Options *base.Options - Logs *logs.Options - Print *genericclioptions.PrintFlags - - // GenerateInCluster indicates whether to generate the APIResourceSchema in-cluster. - GenerateInCluster bool - // File containing the CRD to convert to APIResourceSchema. - File string - // OutputDir is the directory where the APIResourceSchemas will be written. - OutputDir string -} - -func NewCRD2APIResourceSchemaOptions(streams genericclioptions.IOStreams) *CRD2APIResourceSchemaOptions { - return &CRD2APIResourceSchemaOptions{ - Options: base.NewOptions(streams), - Logs: logs.NewOptions(), - Print: genericclioptions.NewPrintFlags("crd2apiresourceschema"), - } -} - -func (b *CRD2APIResourceSchemaOptions) AddCmdFlags(cmd *cobra.Command) { - b.Options.BindFlags(cmd) - logsv1.AddFlags(b.Logs, cmd.Flags()) - b.Print.AddFlags(cmd) - - cmd.Flags().BoolVar(&b.GenerateInCluster, "generate-in-cluster", b.GenerateInCluster, "Generate the APIResourceSchema in-cluster.") - cmd.Flags().StringVar(&b.File, "file", b.File, "File with CRD to convert to APIResourceSchema") - cmd.Flags().StringVar(&b.OutputDir, "output-dir", b.OutputDir, "Directory where APIResourceSchemas will be written.") -} - -func (b *CRD2APIResourceSchemaOptions) Complete(args []string) error { - return b.Options.Complete() -} - -func (b *CRD2APIResourceSchemaOptions) Validate() error { - if b.GenerateInCluster && b.OutputDir != "" { - return errors.New("output-dir and generate-in-cluster cannot be used together") - } - - return b.Options.Validate() -} - -// Run starts the process of converting CRDs to APIResourceSchema objects. -func (b *CRD2APIResourceSchemaOptions) Run(ctx context.Context) error { - config, err := b.Options.ClientConfig.ClientConfig() - if err != nil { - return err - } - client, err := dynamic.NewForConfig(config) - if err != nil { - return fmt.Errorf("failed to create dynamic client: %w", err) - } - - if b.OutputDir == "" { - b.OutputDir = "." - } - - var crdList []*apiextensionsv1.CustomResourceDefinition - if b.File != "" { - fileList, err := b.readCRDsFromFile() - if err != nil { - return fmt.Errorf("failed to read CRDs from file: %w", err) - } - crdList = fileList - } else { - clusterList, err := b.listCRDsFromCluster(ctx, client) - if err != nil { - return fmt.Errorf("failed to list CRDs from cluster: %w", err) - } - crdList = clusterList - } - - for _, crdObj := range crdList { - if crdObj.Spec.Group == "kube-bind.io" { - fmt.Fprintf(b.Options.ErrOut, "skipping CRD %s: belongs to group kube-bind.io\n", crdObj.Name) - continue - } - - prefix := fmt.Sprintf("v%s-%s", time.Now().Format("060102"), string(version.Get().GitCommit)) - apiResourceSchema, err := helpers.CRDToAPIResourceSchema(crdObj, prefix) - if err != nil { - fmt.Fprintf(b.Options.ErrOut, "failed to convert CRD %s to APIResourceSchema: %v\n", crdObj.Name, err) - continue - } - - if apiResourceSchema == nil { - fmt.Fprintf(b.Options.ErrOut, "skipping CRD %s: no schema found\n", crdObj.Name) - continue - } - - if b.GenerateInCluster { - if err := generateAPIResourceSchemaInCluster(ctx, client, apiResourceSchema, b.Options.ErrOut, b.Options.Out); err != nil { - continue - } - } - if err := writeObjectToYAML(b.OutputDir, apiResourceSchema, b.Options.Out); err != nil { - return err - } - } - - return nil -} - -func generateAPIResourceSchemaInCluster(ctx context.Context, client dynamic.Interface, apiResourceSchema *kubebindv1alpha2.APIResourceSchema, errOut, out io.Writer) error { - apiResourceSchemaGVR := kubebindv1alpha2.SchemeGroupVersion.WithResource("apiresourceschemas") - unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(apiResourceSchema) - if err != nil { - return fmt.Errorf("failed to convert APIResourceSchema to unstructured: %w", err) - } - unstructuredResource := &unstructured.Unstructured{Object: unstructuredObj} - - _, err = client.Resource(apiResourceSchemaGVR).Create(ctx, unstructuredResource, metav1.CreateOptions{}) - if err != nil { - fmt.Fprintf(errOut, "Failed to create APIResourceSchema for CRD %s: %v\n", apiResourceSchema.Name, err) - return err - } - - fmt.Fprintf(out, "Successfully created APIResourceSchema for CRD %s\n", apiResourceSchema.Name) - return nil -} - -func writeObjectToYAML(outputDir string, apiResourceSchema *kubebindv1alpha2.APIResourceSchema, logger io.Writer) error { - if err := os.MkdirAll(outputDir, 0755); err != nil { - return fmt.Errorf("failed to create output directory %s: %w", outputDir, err) - } - - scheme := runtime.NewScheme() - if err := kubebindv1alpha2.AddToScheme(scheme); err != nil { - return fmt.Errorf("failed to register kubebindv1alpha2 API group: %w", err) - } - - codecs := serializer.NewCodecFactory(scheme) - info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeYAML) - if !ok { - return fmt.Errorf("unsupported media type %q", runtime.ContentTypeYAML) - } - encoder := codecs.EncoderForVersion(info.Serializer, kubebindv1alpha2.SchemeGroupVersion) - - out, err := runtime.Encode(encoder, apiResourceSchema) - if err != nil { - return fmt.Errorf("failed to encode APIResourceSchema %s: %w", apiResourceSchema.Name, err) - } - outputPath := filepath.Join(outputDir, fmt.Sprintf("%s.yaml", apiResourceSchema.Name)) - if err := os.WriteFile(outputPath, out, 0644); err != nil { - return fmt.Errorf("failed to write APIResourceSchema to file %s: %w", outputPath, err) - } - - fmt.Fprintf(logger, "wrote APIResourceSchema %s to %s\n", apiResourceSchema.Name, outputPath) - return nil -} - -func (b *CRD2APIResourceSchemaOptions) readCRDsFromFile() ([]*apiextensionsv1.CustomResourceDefinition, error) { - data, err := os.ReadFile(b.File) - if err != nil { - return nil, fmt.Errorf("failed to read file %s: %w", b.File, err) - } - - scheme := runtime.NewScheme() - if err := apiextensionsv1.AddToScheme(scheme); err != nil { - return nil, fmt.Errorf("failed to register apiextensions v1 scheme: %w", err) - } - - decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer() - var crdList []*apiextensionsv1.CustomResourceDefinition - - objects := strings.Split(string(data), "---") - for i, obj := range objects { - obj = strings.TrimSpace(obj) - if obj == "" { - continue - } - - decodedObj, gvk, err := decoder.Decode([]byte(obj), nil, nil) - if err != nil { - fmt.Fprintf(b.Options.ErrOut, "warning: failed to decode object %d: %v\n", i+1, err) - continue - } - - if crd, ok := decodedObj.(*apiextensionsv1.CustomResourceDefinition); ok { - crdList = append(crdList, crd) - fmt.Fprintf(b.Options.Out, "found CRD: %s\n", crd.Name) - } else { - return nil, fmt.Errorf("error: non-CRD object of type %s", gvk.String()) - } - } - - if len(crdList) == 0 { - return nil, fmt.Errorf("no CustomResourceDefinition objects found in file %s", b.File) - } - - return crdList, nil -} - -func (b *CRD2APIResourceSchemaOptions) listCRDsFromCluster(ctx context.Context, client dynamic.Interface) ([]*apiextensionsv1.CustomResourceDefinition, error) { - crdGVR := apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions") - crdList, err := client.Resource(crdGVR).List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, fmt.Errorf("failed to list CRDs: %w", err) - } - - var result []*apiextensionsv1.CustomResourceDefinition - for _, crd := range crdList.Items { - crdObj := &apiextensionsv1.CustomResourceDefinition{} - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(crd.UnstructuredContent(), crdObj); err != nil { - return nil, fmt.Errorf("failed to convert CRD: %w", err) - } - result = append(result, crdObj) - } - - return result, nil -} diff --git a/cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go b/cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go index 47ef89fd7..e7825d408 100644 --- a/cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go +++ b/cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go @@ -46,7 +46,7 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co var bindings []*kubebindv1alpha2.APIServiceBinding for _, resource := range request.Spec.Resources { - name := resource.Resource + "." + resource.Group + name := resource.ResourceGroupName() existing, err := bindClient.KubeBindV1alpha2().APIServiceBindings().Get(ctx, name, metav1.GetOptions{}) if err != nil && !apierrors.IsNotFound(err) { return nil, err @@ -58,7 +58,7 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co bindings = append(bindings, existing) // checking CRD to match the binding - crd, err := apiextensionsClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, resource.Resource+"."+resource.Group, metav1.GetOptions{}) + crd, err := apiextensionsClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, resource.ResourceGroupName(), metav1.GetOptions{}) if err != nil && !apierrors.IsNotFound(err) { return nil, err } else if err == nil { @@ -78,7 +78,7 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co } created, err := bindClient.KubeBindV1alpha2().APIServiceBindings().Create(ctx, &kubebindv1alpha2.APIServiceBinding{ ObjectMeta: metav1.ObjectMeta{ - Name: resource.Resource + "." + resource.Group, + Name: resource.ResourceGroupName(), Namespace: "kube-bind", }, Spec: kubebindv1alpha2.APIServiceBindingSpec{ diff --git a/cli/pkg/kubectl/bind/plugin/bind.go b/cli/pkg/kubectl/bind/plugin/bind.go index d6cdc36b6..7cb34f8e8 100644 --- a/cli/pkg/kubectl/bind/plugin/bind.go +++ b/cli/pkg/kubectl/bind/plugin/bind.go @@ -159,7 +159,7 @@ func (b *BindOptions) Run(ctx context.Context, urlCh chan<- string) error { } if provider.APIVersion != kubebindv1alpha2.GroupVersion { - return fmt.Errorf("unsupported binding provider version: %q", provider.APIVersion) + return fmt.Errorf("unsupported binding provider version %q, expected %q", provider.APIVersion, kubebindv1alpha2.GroupVersion) } ns, err := kubeClient.CoreV1().Namespaces().Get(ctx, "kube-bind", metav1.GetOptions{}) diff --git a/deploy/crd/kube-bind.io_apiresourceschemas.yaml b/deploy/crd/kube-bind.io_apiresourceschemas.yaml deleted file mode 100644 index 6e3875153..000000000 --- a/deploy/crd/kube-bind.io_apiresourceschemas.yaml +++ /dev/null @@ -1,362 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.17.3 - name: apiresourceschemas.kube-bind.io -spec: - conversion: - strategy: None - group: kube-bind.io - names: - categories: - - kube-bindings - kind: APIResourceSchema - listKind: APIResourceSchemaList - plural: apiresourceschemas - shortNames: - - as - singular: apiresourceschema - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: APIResourceSchema - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - properties: - conversion: - description: conversion defines conversion settings for the defined - custom resource. - properties: - strategy: - description: |- - strategy specifies how custom resources are converted between versions. Allowed values are: - - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource. - - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information - is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set. - enum: - - None - - Webhook - type: string - webhook: - description: webhook describes how to call the conversion webhook. - Required when `strategy` is set to `"Webhook"`. - properties: - clientConfig: - description: clientConfig is the instructions for how to call - the webhook if strategy is `Webhook`. - properties: - caBundle: - description: |- - caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. - If unspecified, system trust roots on the apiserver are used. - format: byte - type: string - url: - description: |- - url gives the location of the webhook, in standard URL form - (`scheme://host:port/path`). - - Please note that using `localhost` or `127.0.0.1` as a `host` is - risky unless you take great care to run this webhook on all hosts - which run an apiserver which might need to make calls to this - webhook. Such installs are likely to be non-portable, i.e., not easy - to turn up in a new cluster. - - The scheme must be "https"; the URL must begin with "https://". - - A path is optional, and if present may be any string permissible in - a URL. You may use the path to pass an arbitrary string to the - webhook, for example, a cluster identifier. - - Attempting to use a user or basic auth e.g. "user:password@" is not - allowed. Fragments ("#...") and query parameters ("?...") are not - allowed, either. - format: uri - type: string - type: object - conversionReviewVersions: - description: |- - conversionReviewVersions is an ordered list of preferred `ConversionReview` - versions the Webhook expects. The API server will use the first version in - the list which it supports. If none of the versions specified in this list - are supported by API server, conversion will fail for the custom resource. - If a persisted Webhook configuration specifies allowed versions and does not - include any versions known to the API Server, calls to the webhook will fail. - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - type: object - x-kubernetes-validations: - - message: Webhook must be specified if strategy=Webhook - rule: (self.strategy == 'None' && !has(self.webhook)) || (self.strategy - == 'Webhook' && has(self.webhook)) - group: - description: "group is the API group of the defined custom resource. - Empty string means the\ncore API group. \tThe resources are served - under `/apis//...` or `/api` for the core group." - type: string - informerScope: - allOf: - - enum: - - Cluster - - Namespaced - - enum: - - Cluster - - Namespaced - description: |- - InformerScope indicates whether the informer for defined custom resource is cluster- or namespace-scoped. - Allowed values are `Cluster` and `Namespaced`. - type: string - names: - description: names specify the resource and kind names for the custom - resource. - properties: - categories: - description: |- - categories is a list of grouped resources this custom resource belongs to (e.g. 'all'). - This is published in API discovery documents, and used by clients to support invocations like - `kubectl get all`. - items: - type: string - type: array - x-kubernetes-list-type: atomic - kind: - description: |- - kind is the serialized kind of the resource. It is normally CamelCase and singular. - Custom resource instances will use this value as the `kind` attribute in API calls. - type: string - listKind: - description: listKind is the serialized kind of the list for this - resource. Defaults to "`kind`List". - type: string - plural: - description: |- - plural is the plural name of the resource to serve. - The custom resources are served under `/apis///.../`. - Must match the name of the CustomResourceDefinition (in the form `.`). - Must be all lowercase. - type: string - shortNames: - description: |- - shortNames are short names for the resource, exposed in API discovery documents, - and used by clients to support invocations like `kubectl get `. - It must be all lowercase. - items: - type: string - type: array - x-kubernetes-list-type: atomic - singular: - description: singular is the singular name of the resource. It - must be all lowercase. Defaults to lowercased `kind`. - type: string - required: - - kind - - plural - type: object - scope: - description: |- - scope indicates whether the defined custom resource is cluster- or namespace-scoped. - Allowed values are `Cluster` and `Namespaced`. - enum: - - Cluster - - Namespaced - type: string - versions: - description: |- - versions is the API version of the defined custom resource. - - Note: the OpenAPI v3 schemas must be equal for all versions until CEL - version migration is supported. - items: - description: APIResourceVersion describes one API version of a resource. - properties: - additionalPrinterColumns: - description: |- - additionalPrinterColumns specifies additional columns returned in Table output. - See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details. - If no columns are specified, a single column displaying the age of the custom resource is used. - items: - description: CustomResourceColumnDefinition specifies a column - for server side printing. - properties: - description: - description: description is a human readable description - of this column. - type: string - format: - description: |- - format is an optional OpenAPI type definition for this column. The 'name' format is applied - to the primary identifier column to assist in clients identifying column is the resource name. - See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details. - type: string - jsonPath: - description: |- - jsonPath is a simple JSON path (i.e. with array notation) which is evaluated against - each custom resource to produce the value for this column. - type: string - name: - description: name is a human readable name for the column. - type: string - priority: - description: |- - priority is an integer defining the relative importance of this column compared to others. Lower - numbers are considered higher priority. Columns that may be omitted in limited space scenarios - should be given a priority greater than 0. - format: int32 - type: integer - type: - description: |- - type is an OpenAPI type definition for this column. - See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details. - type: string - required: - - jsonPath - - name - - type - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - deprecated: - description: |- - deprecated indicates this version of the custom resource API is deprecated. - When set to true, API requests to this version receive a warning header in the server response. - Defaults to false. - type: boolean - deprecationWarning: - description: |- - deprecationWarning overrides the default warning returned to API clients. - May only be set when `deprecated` is true. - The default warning indicates this version is deprecated and recommends use - of the newest served version of equal or greater stability, if one exists. - type: string - name: - description: |- - name is the version name, e.g. “v1”, “v2beta1”, etc. - The custom resources are served under this version at `/apis///...` if `served` is true. - minLength: 1 - pattern: ^v[1-9][0-9]*([a-z]+[1-9][0-9]*)?$ - type: string - schema: - description: |- - schema describes the structural schema used for validation, pruning, and defaulting - of this version of the custom resource. - properties: - openAPIV3Schema: - description: openAPIV3Schema is the OpenAPI v3 schema to - use for validation and pruning. - type: object - x-kubernetes-map-type: atomic - x-kubernetes-preserve-unknown-fields: true - required: - - openAPIV3Schema - type: object - x-kubernetes-map-type: atomic - x-kubernetes-preserve-unknown-fields: true - served: - default: true - description: served is a flag enabling/disabling this version - from being served via REST APIs - type: boolean - storage: - description: |- - storage indicates this version should be used when persisting custom resources to storage. - There must be exactly one version with storage=true. - type: boolean - subresources: - description: subresources specify what subresources this version - of the defined custom resource have. - properties: - scale: - description: scale indicates the custom resource should - serve a `/scale` subresource that returns an `autoscaling/v1` - Scale object. - properties: - labelSelectorPath: - description: |- - labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`. - Only JSON paths without the array notation are allowed. - Must be a JSON Path under `.status` or `.spec`. - Must be set to work with HorizontalPodAutoscaler. - The field pointed by this JSON path must be a string field (not a complex selector struct) - which contains a serialized label selector in string form. - More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource - If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale` - subresource will default to the empty string. - type: string - specReplicasPath: - description: |- - specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`. - Only JSON paths without the array notation are allowed. - Must be a JSON Path under `.spec`. - If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET. - type: string - statusReplicasPath: - description: |- - statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`. - Only JSON paths without the array notation are allowed. - Must be a JSON Path under `.status`. - If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource - will default to 0. - type: string - required: - - specReplicasPath - - statusReplicasPath - type: object - status: - description: |- - status indicates the custom resource should serve a `/status` subresource. - When enabled: - 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object. - 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object. - type: object - type: object - required: - - name - - schema - - served - - storage - type: object - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - group - - informerScope - - names - - scope - - versions - type: object - type: object - served: true - storage: true - subresources: {} diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index 6a0a9eed2..138ba8419 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -491,26 +491,38 @@ spec: - message: informerScope is immutable rule: self == oldSelf resources: - description: resources specifies the API resources to export + description: resources is a list of resources that should be exported. items: - description: APIResourceSchemaReference is a list of references - to APIResourceSchemas. properties: - name: - description: Name is the name of the resource to export + group: + default: "" + description: |- + group is the name of an API group. + For core groups this is the empty string '""'. + pattern: ^(|[a-z0-9]([-a-z0-9]*[a-z0-9](\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)?)$ type: string - type: + resource: description: |- - Type of the resource to export - Currently only APIResourceSchema is supported - enum: - - APIResourceSchema + resource is the name of the resource. + Note: it is worth noting that you can not ask for permissions for resource provided by a CRD + not provided by an service binding export. + pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string + versions: + description: |- + versions is a list of versions that should be exported. If this is empty + a sensible default is chosen by the service provider. + items: + type: string + type: array required: - - name - - type + - resource type: object + minItems: 1 type: array + x-kubernetes-validations: + - message: resources are immutable + rule: self == oldSelf required: - informerScope - resources diff --git a/deploy/crd/kube-bind.io_boundapiresourceschemas.yaml b/deploy/crd/kube-bind.io_boundschemas.yaml similarity index 96% rename from deploy/crd/kube-bind.io_boundapiresourceschemas.yaml rename to deploy/crd/kube-bind.io_boundschemas.yaml index 16ba489b1..41980ee1e 100644 --- a/deploy/crd/kube-bind.io_boundapiresourceschemas.yaml +++ b/deploy/crd/kube-bind.io_boundschemas.yaml @@ -3,7 +3,7 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.17.3 - name: boundapiresourceschemas.kube-bind.io + name: boundschemas.kube-bind.io spec: conversion: strategy: None @@ -11,12 +11,12 @@ spec: names: categories: - kube-bindings - kind: BoundAPIResourceSchema - listKind: BoundAPIResourceSchemaList - plural: boundapiresourceschemas + kind: BoundSchema + listKind: BoundSchemaList + plural: boundschemas shortNames: - - bas - singular: boundapiresourceschema + - bs + singular: boundschema scope: Namespaced versions: - additionalPrinterColumns: @@ -26,7 +26,7 @@ spec: name: v1alpha2 schema: openAPIV3Schema: - description: BoundAPIResourceSchema + description: BoundSchema properties: apiVersion: description: |- @@ -46,8 +46,7 @@ spec: metadata: type: object spec: - description: BoundAPIResourceSchemaSpec defines the desired state of the - BoundAPIResourceSchema. + description: BoundSchemaSpec defines the desired state of the BoundSchema. properties: conversion: description: conversion defines conversion settings for the defined @@ -270,15 +269,6 @@ spec: description: |- schema describes the structural schema used for validation, pruning, and defaulting of this version of the custom resource. - properties: - openAPIV3Schema: - description: openAPIV3Schema is the OpenAPI v3 schema to - use for validation and pruning. - type: object - x-kubernetes-map-type: atomic - x-kubernetes-preserve-unknown-fields: true - required: - - openAPIV3Schema type: object x-kubernetes-map-type: atomic x-kubernetes-preserve-unknown-fields: true @@ -359,8 +349,7 @@ spec: - versions type: object status: - description: BoundAPIResourceSchemaStatus defines the observed state of - the BoundAPIResourceSchema. + description: BoundSchemaStatus defines the observed state of the BoundSchema. properties: acceptedNames: description: |- diff --git a/deploy/examples/apiresourceschema.yaml b/deploy/examples/apiresourceschema.yaml index 5479f3de5..aa8f1277e 100644 --- a/deploy/examples/apiresourceschema.yaml +++ b/deploy/examples/apiresourceschema.yaml @@ -2,6 +2,8 @@ apiVersion: kube-bind.io/v1alpha2 kind: APIResourceSchema metadata: name: mangodbs.mangodb.com + labels: + kube-bind.io/exported: "true" spec: informerScope: Namespaced group: mangodb.com diff --git a/deploy/patches/kube-bind.io_boundapiresourceschemas.yaml-patch b/deploy/patches/kube-bind.io_boundapiresourceschemas.yaml-patch deleted file mode 100644 index 15feab278..000000000 --- a/deploy/patches/kube-bind.io_boundapiresourceschemas.yaml-patch +++ /dev/null @@ -1,5 +0,0 @@ -# schema's Convert functions directly, but the CRD still needs to define a conversion. -- op: add - path: /spec/conversion - value: - strategy: None \ No newline at end of file diff --git a/deploy/patches/kube-bind.io_apiresourceschemas.yaml-patch b/deploy/patches/kube-bind.io_boundschemas.yaml-patch similarity index 87% rename from deploy/patches/kube-bind.io_apiresourceschemas.yaml-patch rename to deploy/patches/kube-bind.io_boundschemas.yaml-patch index 15feab278..aed790a21 100644 --- a/deploy/patches/kube-bind.io_apiresourceschemas.yaml-patch +++ b/deploy/patches/kube-bind.io_boundschemas.yaml-patch @@ -2,4 +2,4 @@ - op: add path: /spec/conversion value: - strategy: None \ No newline at end of file + strategy: None diff --git a/kcp/README.md b/kcp/README.md index 2f100f725..2c2c92a74 100644 --- a/kcp/README.md +++ b/kcp/README.md @@ -44,19 +44,20 @@ bin/backend \ --pretty-name="BigCorp.com" \ --namespace-prefix="kube-bind-" \ --cookie-signing-key=bGMHz7SR9XcI9JdDB68VmjQErrjbrAR9JdVqjAOKHzE= \ - --cookie-encryption-key=wadqi4u+w0bqnSrVFtM38Pz2ykYVIeeadhzT34XlC1Y= + --cookie-encryption-key=wadqi4u+w0bqnSrVFtM38Pz2ykYVIeeadhzT34XlC1Y= \ + --schema-source apiresourceschemas ``` -4. Copy the kubeconfig to the provider: +4. Copy the kubeconfig to the provider and create provider workspace: ```bash cp .kcp/admin.kubeconfig .kcp/provider.kubeconfig export KUBECONFIG=.kcp/provider.kubeconfig k ws use :root +kubectl ws create provider --enter ``` -5. Run `kubectl ws create provider --enter` -6. Bind the APIExport to the workspace +5. Bind the APIExport to the workspace ```bash kubectl kcp bind apiexport root:kube-bind:kube-bind.io --accept-permission-claim clusterrolebindings.rbac.authorization.k8s.io \ --accept-permission-claim clusterroles.rbac.authorization.k8s.io \ @@ -65,14 +66,17 @@ kubectl kcp bind apiexport root:kube-bind:kube-bind.io --accept-permission-claim --accept-permission-claim configmaps.core \ --accept-permission-claim secrets.core \ --accept-permission-claim namespaces.core \ - --accept-permission-claim serviceaccounts.rbac.authorization.k8s.io \ --accept-permission-claim roles.rbac.authorization.k8s.io \ - --accept-permission-claim rolebindings.rbac.authorization.k8s.io + --accept-permission-claim rolebindings.rbac.authorization.k8s.io \ + --accept-permission-claim apiresourceschemas.apis.kcp.io ``` 7. Create CRD: ```bash -kubectl apply -f deploy/examples/crd-mangodb.yaml +kubectl create -f kcp/deploy/examples/apiexport.yaml +kubectl create -f kcp/deploy/examples/apiresourceschema.yaml +# recursive bind +kubectl kcp bind apiexport root:provider:cowboys-stable ``` 8. Get LogicalCluster: @@ -126,7 +130,7 @@ go run ./cmd/konnector/ --lease-namespace default --server-address :8091 Create objects: ``` -kubectl create -f deploy/examples/mangodb.yaml +kubectl apply -f kcp/deploy/examples/cowboy.yaml ``` diff --git a/kcp/deploy/examples/apiexport.yaml b/kcp/deploy/examples/apiexport.yaml new file mode 100644 index 000000000..ec68ee67b --- /dev/null +++ b/kcp/deploy/examples/apiexport.yaml @@ -0,0 +1,16 @@ +apiVersion: apis.kcp.io/v1alpha2 +kind: APIExport +metadata: + name: cowboys-stable +spec: + resources: + - group: wildwest.dev + name: cowboys + schema: today.cowboys.wildwest.dev + storage: + crd: {} + - group: wildwest.dev + name: sheriffs + schema: today.sheriffs.wildwest.dev + storage: + crd: {} \ No newline at end of file diff --git a/kcp/deploy/examples/apiresourceschema-cowboys.yaml b/kcp/deploy/examples/apiresourceschema-cowboys.yaml new file mode 100644 index 000000000..8eeccbd44 --- /dev/null +++ b/kcp/deploy/examples/apiresourceschema-cowboys.yaml @@ -0,0 +1,48 @@ +apiVersion: apis.kcp.io/v1alpha1 +kind: APIResourceSchema +metadata: + name: today.cowboys.wildwest.dev + labels: + kube-bind.io/exported: "true" +spec: + group: wildwest.dev + names: + kind: Cowboy + listKind: CowboyList + plural: cowboys + singular: cowboy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + description: Cowboy is part of the wild west + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CowboySpec holds the desired state of the Cowboy. + properties: + intent: + type: string + type: object + status: + description: CowboyStatus communicates the observed state of the Cowboy. + properties: + result: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} \ No newline at end of file diff --git a/kcp/deploy/examples/apiresourceschema-sheriffs.yaml b/kcp/deploy/examples/apiresourceschema-sheriffs.yaml new file mode 100644 index 000000000..dfbdb00ba --- /dev/null +++ b/kcp/deploy/examples/apiresourceschema-sheriffs.yaml @@ -0,0 +1,48 @@ +apiVersion: apis.kcp.io/v1alpha1 +kind: APIResourceSchema +metadata: + name: today.sheriffs.wildwest.dev + labels: + kube-bind.io/exported: "true" +spec: + group: wildwest.dev + names: + kind: Sheriff + listKind: SheriffList + plural: sheriffs + singular: sheriff + scope: Cluster + versions: + - name: v1alpha1 + schema: + description: Sheriff is part of the wild west + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SheriffSpec holds the desired state of the Sheriff. + properties: + intent: + type: string + type: object + status: + description: SheriffStatus communicates the observed state of the Sheriff. + properties: + result: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} \ No newline at end of file diff --git a/kcp/deploy/examples/apiserviceexport-cluster.yaml b/kcp/deploy/examples/apiserviceexport-cluster.yaml new file mode 100644 index 000000000..72a3bd448 --- /dev/null +++ b/kcp/deploy/examples/apiserviceexport-cluster.yaml @@ -0,0 +1,11 @@ +apiVersion: kube-bind.io/v1alpha2 +kind: APIServiceExportRequest +metadata: + name: sheriffs.wildwest.dev +spec: + resources: + - group: wildwest.dev + resource: sheriffs + versions: + - v1alpha1 +status: {} diff --git a/kcp/deploy/examples/apiserviceexport-namespaced.yaml b/kcp/deploy/examples/apiserviceexport-namespaced.yaml new file mode 100644 index 000000000..a6ff6e1f6 --- /dev/null +++ b/kcp/deploy/examples/apiserviceexport-namespaced.yaml @@ -0,0 +1,11 @@ +apiVersion: kube-bind.io/v1alpha2 +kind: APIServiceExportRequest +metadata: + name: cowboys.wildwest.dev +spec: + resources: + - group: wildwest.dev + resource: cowboys + versions: + - v1alpha1 +status: {} diff --git a/kcp/deploy/examples/cowboy.yaml b/kcp/deploy/examples/cowboy.yaml new file mode 100644 index 000000000..36eec6152 --- /dev/null +++ b/kcp/deploy/examples/cowboy.yaml @@ -0,0 +1,5 @@ +apiVersion: wildwest.dev/v1alpha1 +kind: Cowboy +metadata: + name: my-cowboy + namespace: default \ No newline at end of file diff --git a/kcp/deploy/examples/sheriff.yaml b/kcp/deploy/examples/sheriff.yaml new file mode 100644 index 000000000..7a174bbd7 --- /dev/null +++ b/kcp/deploy/examples/sheriff.yaml @@ -0,0 +1,4 @@ +apiVersion: wildwest.dev/v1alpha1 +kind: Sheriff +metadata: + name: sheriff \ No newline at end of file diff --git a/kcp/deploy/resources/apiexport-kube-bind.io.yaml b/kcp/deploy/resources/apiexport-kube-bind.io.yaml index e3f8463c9..54d8a7688 100644 --- a/kcp/deploy/resources/apiexport-kube-bind.io.yaml +++ b/kcp/deploy/resources/apiexport-kube-bind.io.yaml @@ -37,17 +37,16 @@ spec: resource: customresourcedefinitions verbs: - '*' + - group: apis.kcp.io + resource: apiresourceschemas + verbs: + - '*' resources: - group: kube-bind.io name: apiconversions schema: v250809-5ed76a1.apiconversions.kube-bind.io storage: crd: {} - - group: kube-bind.io - name: apiresourceschemas - schema: v250809-5ed76a1.apiresourceschemas.kube-bind.io - storage: - crd: {} - group: kube-bind.io name: apiservicebindings schema: v250809-5ed76a1.apiservicebindings.kube-bind.io @@ -60,7 +59,7 @@ spec: crd: {} - group: kube-bind.io name: apiserviceexports - schema: v250809-5ed76a1.apiserviceexports.kube-bind.io + schema: v250902-878858c.apiserviceexports.kube-bind.io storage: crd: {} - group: kube-bind.io @@ -69,8 +68,8 @@ spec: storage: crd: {} - group: kube-bind.io - name: boundapiresourceschemas - schema: v250809-5ed76a1.boundapiresourceschemas.kube-bind.io + name: boundschemas + schema: v250918-f26732b.boundschemas.kube-bind.io storage: crd: {} - group: kube-bind.io diff --git a/kcp/deploy/resources/apiresourceschema-apiresourceschemas.kube-bind.io.yaml b/kcp/deploy/resources/apiresourceschema-apiresourceschemas.kube-bind.io.yaml deleted file mode 100644 index ff7a771db..000000000 --- a/kcp/deploy/resources/apiresourceschema-apiresourceschemas.kube-bind.io.yaml +++ /dev/null @@ -1,360 +0,0 @@ -apiVersion: apis.kcp.io/v1alpha1 -kind: APIResourceSchema -metadata: - creationTimestamp: null - name: v250809-5ed76a1.apiresourceschemas.kube-bind.io -spec: - conversion: - strategy: None - group: kube-bind.io - names: - categories: - - kube-bindings - kind: APIResourceSchema - listKind: APIResourceSchemaList - plural: apiresourceschemas - shortNames: - - as - singular: apiresourceschema - scope: Cluster - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - description: APIResourceSchema - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - properties: - conversion: - description: conversion defines conversion settings for the defined - custom resource. - properties: - strategy: - description: |- - strategy specifies how custom resources are converted between versions. Allowed values are: - - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource. - - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information - is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set. - enum: - - None - - Webhook - type: string - webhook: - description: webhook describes how to call the conversion webhook. - Required when `strategy` is set to `"Webhook"`. - properties: - clientConfig: - description: clientConfig is the instructions for how to call - the webhook if strategy is `Webhook`. - properties: - caBundle: - description: |- - caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. - If unspecified, system trust roots on the apiserver are used. - format: byte - type: string - url: - description: |- - url gives the location of the webhook, in standard URL form - (`scheme://host:port/path`). - - Please note that using `localhost` or `127.0.0.1` as a `host` is - risky unless you take great care to run this webhook on all hosts - which run an apiserver which might need to make calls to this - webhook. Such installs are likely to be non-portable, i.e., not easy - to turn up in a new cluster. - - The scheme must be "https"; the URL must begin with "https://". - - A path is optional, and if present may be any string permissible in - a URL. You may use the path to pass an arbitrary string to the - webhook, for example, a cluster identifier. - - Attempting to use a user or basic auth e.g. "user:password@" is not - allowed. Fragments ("#...") and query parameters ("?...") are not - allowed, either. - format: uri - type: string - type: object - conversionReviewVersions: - description: |- - conversionReviewVersions is an ordered list of preferred `ConversionReview` - versions the Webhook expects. The API server will use the first version in - the list which it supports. If none of the versions specified in this list - are supported by API server, conversion will fail for the custom resource. - If a persisted Webhook configuration specifies allowed versions and does not - include any versions known to the API Server, calls to the webhook will fail. - items: - type: string - type: array - x-kubernetes-list-type: atomic - type: object - type: object - x-kubernetes-validations: - - message: Webhook must be specified if strategy=Webhook - rule: (self.strategy == 'None' && !has(self.webhook)) || (self.strategy - == 'Webhook' && has(self.webhook)) - group: - description: "group is the API group of the defined custom resource. - Empty string means the\ncore API group. \tThe resources are served - under `/apis//...` or `/api` for the core group." - type: string - informerScope: - allOf: - - enum: - - Cluster - - Namespaced - - enum: - - Cluster - - Namespaced - description: |- - InformerScope indicates whether the informer for defined custom resource is cluster- or namespace-scoped. - Allowed values are `Cluster` and `Namespaced`. - type: string - names: - description: names specify the resource and kind names for the custom - resource. - properties: - categories: - description: |- - categories is a list of grouped resources this custom resource belongs to (e.g. 'all'). - This is published in API discovery documents, and used by clients to support invocations like - `kubectl get all`. - items: - type: string - type: array - x-kubernetes-list-type: atomic - kind: - description: |- - kind is the serialized kind of the resource. It is normally CamelCase and singular. - Custom resource instances will use this value as the `kind` attribute in API calls. - type: string - listKind: - description: listKind is the serialized kind of the list for this - resource. Defaults to "`kind`List". - type: string - plural: - description: |- - plural is the plural name of the resource to serve. - The custom resources are served under `/apis///.../`. - Must match the name of the CustomResourceDefinition (in the form `.`). - Must be all lowercase. - type: string - shortNames: - description: |- - shortNames are short names for the resource, exposed in API discovery documents, - and used by clients to support invocations like `kubectl get `. - It must be all lowercase. - items: - type: string - type: array - x-kubernetes-list-type: atomic - singular: - description: singular is the singular name of the resource. It must - be all lowercase. Defaults to lowercased `kind`. - type: string - required: - - kind - - plural - type: object - scope: - description: |- - scope indicates whether the defined custom resource is cluster- or namespace-scoped. - Allowed values are `Cluster` and `Namespaced`. - enum: - - Cluster - - Namespaced - type: string - versions: - description: |- - versions is the API version of the defined custom resource. - - Note: the OpenAPI v3 schemas must be equal for all versions until CEL - version migration is supported. - items: - description: APIResourceVersion describes one API version of a resource. - properties: - additionalPrinterColumns: - description: |- - additionalPrinterColumns specifies additional columns returned in Table output. - See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details. - If no columns are specified, a single column displaying the age of the custom resource is used. - items: - description: CustomResourceColumnDefinition specifies a column - for server side printing. - properties: - description: - description: description is a human readable description - of this column. - type: string - format: - description: |- - format is an optional OpenAPI type definition for this column. The 'name' format is applied - to the primary identifier column to assist in clients identifying column is the resource name. - See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details. - type: string - jsonPath: - description: |- - jsonPath is a simple JSON path (i.e. with array notation) which is evaluated against - each custom resource to produce the value for this column. - type: string - name: - description: name is a human readable name for the column. - type: string - priority: - description: |- - priority is an integer defining the relative importance of this column compared to others. Lower - numbers are considered higher priority. Columns that may be omitted in limited space scenarios - should be given a priority greater than 0. - format: int32 - type: integer - type: - description: |- - type is an OpenAPI type definition for this column. - See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details. - type: string - required: - - jsonPath - - name - - type - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - deprecated: - description: |- - deprecated indicates this version of the custom resource API is deprecated. - When set to true, API requests to this version receive a warning header in the server response. - Defaults to false. - type: boolean - deprecationWarning: - description: |- - deprecationWarning overrides the default warning returned to API clients. - May only be set when `deprecated` is true. - The default warning indicates this version is deprecated and recommends use - of the newest served version of equal or greater stability, if one exists. - type: string - name: - description: |- - name is the version name, e.g. “v1”, “v2beta1”, etc. - The custom resources are served under this version at `/apis///...` if `served` is true. - minLength: 1 - pattern: ^v[1-9][0-9]*([a-z]+[1-9][0-9]*)?$ - type: string - schema: - description: |- - schema describes the structural schema used for validation, pruning, and defaulting - of this version of the custom resource. - properties: - openAPIV3Schema: - description: openAPIV3Schema is the OpenAPI v3 schema to use - for validation and pruning. - type: object - x-kubernetes-map-type: atomic - x-kubernetes-preserve-unknown-fields: true - required: - - openAPIV3Schema - type: object - x-kubernetes-map-type: atomic - x-kubernetes-preserve-unknown-fields: true - served: - default: true - description: served is a flag enabling/disabling this version - from being served via REST APIs - type: boolean - storage: - description: |- - storage indicates this version should be used when persisting custom resources to storage. - There must be exactly one version with storage=true. - type: boolean - subresources: - description: subresources specify what subresources this version - of the defined custom resource have. - properties: - scale: - description: scale indicates the custom resource should serve - a `/scale` subresource that returns an `autoscaling/v1` - Scale object. - properties: - labelSelectorPath: - description: |- - labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`. - Only JSON paths without the array notation are allowed. - Must be a JSON Path under `.status` or `.spec`. - Must be set to work with HorizontalPodAutoscaler. - The field pointed by this JSON path must be a string field (not a complex selector struct) - which contains a serialized label selector in string form. - More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource - If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale` - subresource will default to the empty string. - type: string - specReplicasPath: - description: |- - specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`. - Only JSON paths without the array notation are allowed. - Must be a JSON Path under `.spec`. - If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET. - type: string - statusReplicasPath: - description: |- - statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`. - Only JSON paths without the array notation are allowed. - Must be a JSON Path under `.status`. - If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource - will default to 0. - type: string - required: - - specReplicasPath - - statusReplicasPath - type: object - status: - description: |- - status indicates the custom resource should serve a `/status` subresource. - When enabled: - 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object. - 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object. - type: object - type: object - required: - - name - - schema - - served - - storage - type: object - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - required: - - group - - informerScope - - names - - scope - - versions - type: object - type: object - served: true - storage: true - subresources: {} diff --git a/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml b/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml index 8eb68ef26..1e8a83da1 100644 --- a/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml +++ b/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml @@ -2,7 +2,7 @@ apiVersion: apis.kcp.io/v1alpha1 kind: APIResourceSchema metadata: creationTimestamp: null - name: v250809-5ed76a1.apiserviceexports.kube-bind.io + name: v250902-878858c.apiserviceexports.kube-bind.io spec: conversion: strategy: None @@ -488,26 +488,38 @@ spec: - message: informerScope is immutable rule: self == oldSelf resources: - description: resources specifies the API resources to export + description: resources is a list of resources that should be exported. items: - description: APIResourceSchemaReference is a list of references to - APIResourceSchemas. properties: - name: - description: Name is the name of the resource to export + group: + default: "" + description: |- + group is the name of an API group. + For core groups this is the empty string '""'. + pattern: ^(|[a-z0-9]([-a-z0-9]*[a-z0-9](\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)?)$ type: string - type: + resource: description: |- - Type of the resource to export - Currently only APIResourceSchema is supported - enum: - - APIResourceSchema + resource is the name of the resource. + Note: it is worth noting that you can not ask for permissions for resource provided by a CRD + not provided by an service binding export. + pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string + versions: + description: |- + versions is a list of versions that should be exported. If this is empty + a sensible default is chosen by the service provider. + items: + type: string + type: array required: - - name - - type + - resource type: object + minItems: 1 type: array + x-kubernetes-validations: + - message: resources are immutable + rule: self == oldSelf required: - informerScope - resources diff --git a/kcp/deploy/resources/apiresourceschema-boundapiresourceschemas.kube-bind.io.yaml b/kcp/deploy/resources/apiresourceschema-boundschemas.kube-bind.io.yaml similarity index 96% rename from kcp/deploy/resources/apiresourceschema-boundapiresourceschemas.kube-bind.io.yaml rename to kcp/deploy/resources/apiresourceschema-boundschemas.kube-bind.io.yaml index 279671445..e0afec0b6 100644 --- a/kcp/deploy/resources/apiresourceschema-boundapiresourceschemas.kube-bind.io.yaml +++ b/kcp/deploy/resources/apiresourceschema-boundschemas.kube-bind.io.yaml @@ -2,7 +2,7 @@ apiVersion: apis.kcp.io/v1alpha1 kind: APIResourceSchema metadata: creationTimestamp: null - name: v250809-5ed76a1.boundapiresourceschemas.kube-bind.io + name: v250918-f26732b.boundschemas.kube-bind.io spec: conversion: strategy: None @@ -10,12 +10,12 @@ spec: names: categories: - kube-bindings - kind: BoundAPIResourceSchema - listKind: BoundAPIResourceSchemaList - plural: boundapiresourceschemas + kind: BoundSchema + listKind: BoundSchemaList + plural: boundschemas shortNames: - - bas - singular: boundapiresourceschema + - bs + singular: boundschema scope: Namespaced versions: - additionalPrinterColumns: @@ -24,7 +24,7 @@ spec: type: date name: v1alpha2 schema: - description: BoundAPIResourceSchema + description: BoundSchema properties: apiVersion: description: |- @@ -44,8 +44,7 @@ spec: metadata: type: object spec: - description: BoundAPIResourceSchemaSpec defines the desired state of the - BoundAPIResourceSchema. + description: BoundSchemaSpec defines the desired state of the BoundSchema. properties: conversion: description: conversion defines conversion settings for the defined @@ -268,15 +267,6 @@ spec: description: |- schema describes the structural schema used for validation, pruning, and defaulting of this version of the custom resource. - properties: - openAPIV3Schema: - description: openAPIV3Schema is the OpenAPI v3 schema to use - for validation and pruning. - type: object - x-kubernetes-map-type: atomic - x-kubernetes-preserve-unknown-fields: true - required: - - openAPIV3Schema type: object x-kubernetes-map-type: atomic x-kubernetes-preserve-unknown-fields: true @@ -357,8 +347,7 @@ spec: - versions type: object status: - description: BoundAPIResourceSchemaStatus defines the observed state of - the BoundAPIResourceSchema. + description: BoundSchemaStatus defines the observed state of the BoundSchema. properties: acceptedNames: description: |- diff --git a/kcp/go.mod b/kcp/go.mod index ef779ae68..f42500e17 100644 --- a/kcp/go.mod +++ b/kcp/go.mod @@ -4,35 +4,54 @@ go 1.24.0 replace github.com/kube-bind/kube-bind => ../ +replace github.com/kube-bind/kube-bind/sdk/apis => ../sdk/apis + +replace github.com/kube-bind/kube-bind/sdk/client => ../sdk/client + +replace github.com/kube-bind/kube-bind/cli => ../cli + require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/kcp-dev/client-go v0.0.0-20250728134101-0355faa9361b github.com/kcp-dev/kcp v0.28.0 github.com/kcp-dev/kcp/sdk v0.28.0 github.com/kcp-dev/logicalcluster/v3 v3.0.5 + github.com/kube-bind/kube-bind v0.4.6 github.com/spf13/pflag v1.0.7 + github.com/stretchr/testify v1.10.0 + gopkg.in/headzoo/surf.v1 v1.0.1 k8s.io/apiextensions-apiserver v0.33.3 k8s.io/apimachinery v0.33.3 k8s.io/apiserver v0.33.3 + k8s.io/cli-runtime v0.32.0 k8s.io/client-go v0.33.3 k8s.io/component-base v0.33.3 k8s.io/klog/v2 v2.130.1 + sigs.k8s.io/yaml v1.4.0 ) require ( cel.dev/expr v0.19.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/PuerkitoBio/goquery v1.8.0 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-oidc v2.3.0+incompatible // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dexidp/dex/api/v2 v2.1.0 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -44,27 +63,48 @@ require ( github.com/google/cel-go v0.23.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect + github.com/headzoo/surf v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kcp-dev/apimachinery/v2 v2.0.1-0.20250728122101-adbf20db3e51 // indirect + github.com/kcp-dev/kcp/pkg/apis v0.11.0 // indirect + github.com/kcp-dev/multicluster-provider v0.1.0 // indirect + github.com/kube-bind/kube-bind/cli v0.0.0-20250515145715-d9f20e7c840d // indirect + github.com/kube-bind/kube-bind/sdk/apis v0.4.8 // indirect + github.com/kube-bind/kube-bind/sdk/client v0.4.6 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.9.0 // indirect + github.com/martinlindhe/base36 v1.1.1 // indirect + github.com/mdp/qrterminal/v3 v3.2.0 // indirect + github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/gomega v1.36.2 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect + github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/xlab/treeprint v1.2.0 // indirect go.etcd.io/etcd/api/v3 v3.5.21 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect go.etcd.io/etcd/client/v3 v3.5.21 // indirect @@ -89,21 +129,28 @@ require ( golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/grpc v1.69.2 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.33.3 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + rsc.io/qr v0.2.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect + sigs.k8s.io/controller-runtime v0.21.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/kustomize/api v0.19.0 // indirect + sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect + sigs.k8s.io/multicluster-runtime v0.21.0-alpha.8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect ) replace ( @@ -141,3 +188,7 @@ replace ( k8s.io/sample-cli-plugin => github.com/kcp-dev/kubernetes/staging/src/k8s.io/sample-cli-plugin v0.0.0-20250816165010-ffe1d7c8649b k8s.io/sample-controller => github.com/kcp-dev/kubernetes/staging/src/k8s.io/sample-controller v0.0.0-20250816165010-ffe1d7c8649b ) + +replace sigs.k8s.io/multicluster-runtime => github.com/mjudeikis/sigs-multicluster-runtime v0.0.0-20250818101434-d8ebc45e169b + +replace github.com/kcp-dev/multicluster-provider => github.com/mjudeikis/kcp-multicluster-provider v0.0.0-20250818102159-3d31cbb06ebe diff --git a/kcp/go.sum b/kcp/go.sum index 72a6d12a7..7f722d970 100644 --- a/kcp/go.sum +++ b/kcp/go.sum @@ -1,7 +1,13 @@ cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -12,25 +18,37 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-oidc v2.3.0+incompatible h1:+5vEsrgprdLjjQ9FzIKAzQz1wwPD+83hQRfUIPh7rO0= +github.com/coreos/go-oidc v2.3.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dexidp/dex/api/v2 v2.1.0 h1:V7XTnG2HM2bqWZMABDQpf4EA6F+0jWPsv9pGaUIDo+k= +github.com/dexidp/dex/api/v2 v2.1.0/go.mod h1:s91/6CI290JhYN1F8aiRifLF71qRGLVZvzq68uC6Ln4= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -51,6 +69,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= @@ -63,12 +83,22 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -77,6 +107,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= +github.com/headzoo/surf v1.0.1 h1:wk3+LT8gjnCxEwfBJl6MhaNg154En5KjgmgzAG9uMS0= +github.com/headzoo/surf v1.0.1/go.mod h1:/bct0m/iMNEqpn520y01yoaWxsAEigGFPnvyR1ewR5M= +github.com/headzoo/ut v0.0.0-20181013193318-a13b5a7a02ca h1:utFgFwgxaqx5OthzE3DSGrtOq7rox5r2sxZ2wbfTuK0= +github.com/headzoo/ut v0.0.0-20181013193318-a13b5a7a02ca/go.mod h1:8926sG02TCOX4RFRzIMFIzRw4xuc/TwO2gtN7teMJZ4= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= @@ -91,6 +125,8 @@ github.com/kcp-dev/client-go v0.0.0-20250728134101-0355faa9361b h1:2LGrXvY9sc4l5 github.com/kcp-dev/client-go v0.0.0-20250728134101-0355faa9361b/go.mod h1:QdO8AaGAZPr/rIZ1iVanCM3tUOiiuX897GWv7WTByLE= github.com/kcp-dev/kcp v0.28.0 h1:J3oaOPqc4A2Q+wZveL0iVElAuOLivFmKTCpaKVx8iXA= github.com/kcp-dev/kcp v0.28.0/go.mod h1:q28Fx8sU/KA8kz8HGwtaqA7Iom8oR90ydoPK39jMaxo= +github.com/kcp-dev/kcp/pkg/apis v0.11.0 h1:K6p+tNHNcvfACCPLcHgY0EMLeaIwR1jS491FyLfXMII= +github.com/kcp-dev/kcp/pkg/apis v0.11.0/go.mod h1:8cUAmfMJcksauz53UtsLYG8Phhx62rvuCnd/5t/Zihk= github.com/kcp-dev/kcp/sdk v0.28.0 h1:AOgGrgpqhrplbXMSbcvjFwCqwg4UlysTwIFZ0LvFxlk= github.com/kcp-dev/kcp/sdk v0.28.0/go.mod h1:8oZpWxkoMu2TDpx5DgdIGDigByKHKkeqVMA4GiWneoI= github.com/kcp-dev/kubernetes/staging/src/k8s.io/api v0.0.0-20250816165010-ffe1d7c8649b h1:CyQuxPfhWg8KdwfmY5aE6KABsh/QhkDXTH2msezxCFY= @@ -101,6 +137,8 @@ github.com/kcp-dev/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20250816165 github.com/kcp-dev/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:6XMZJoNYwuMArBvS2acFkTR1KqyHSp2QXRLRx9eTk5w= github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250816165010-ffe1d7c8649b h1:C21pLvKT2MUE38+ZNDXeucEbRdb7rewRpBp4C5lzz6M= github.com/kcp-dev/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:STCgTiD+xCCHsfLOPHn5sNVsyktakX/ctW3dMv3erh0= +github.com/kcp-dev/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20250816165010-ffe1d7c8649b h1:sQ7otAwO/YMn5cFt4Fftzt6P0cz2gmW7OnTx2i1Qfis= +github.com/kcp-dev/kubernetes/staging/src/k8s.io/cli-runtime v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:m/w/xSuYRnkX3ocgdBKM/TX7B8aNzScl6qCp1Z3XIGw= github.com/kcp-dev/kubernetes/staging/src/k8s.io/client-go v0.0.0-20250816165010-ffe1d7c8649b h1:kEieYK/XCUycPf5DCEUZNPvDVHr4ao+rxZvdOQXlMQk= github.com/kcp-dev/kubernetes/staging/src/k8s.io/client-go v0.0.0-20250816165010-ffe1d7c8649b/go.mod h1:omt22adyHpxAelVTfG1bssg+xoAUc+Cg+0CXn0Oaim0= github.com/kcp-dev/kubernetes/staging/src/k8s.io/component-base v0.0.0-20250816165010-ffe1d7c8649b h1:OazHpbyl1+WvViAUEZw2PxMZNrd5LOPDD+bhnfL5cQM= @@ -113,30 +151,51 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/martinlindhe/base36 v1.1.1 h1:1F1MZ5MGghBXDZ2KJ3QfxmiydlWOGB8HCEtkap5NkVg= +github.com/martinlindhe/base36 v1.1.1/go.mod h1:vMS8PaZ5e/jV9LwFKlm0YLnXl/hpOihiBxKkIoc3g08= +github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk= +github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk= +github.com/mjudeikis/kcp-multicluster-provider v0.0.0-20250818102159-3d31cbb06ebe h1:rSMxNO43EhRCu49OxJrcueT3x8QJdtDgg9QNsjj8UCI= +github.com/mjudeikis/kcp-multicluster-provider v0.0.0-20250818102159-3d31cbb06ebe/go.mod h1:AQbVcrm76lpSFQ/8Gkbf0ev1eTqbk+dynDw6IW8oprA= +github.com/mjudeikis/sigs-multicluster-runtime v0.0.0-20250818101434-d8ebc45e169b h1:rWXhKkj+BFmR08VYCRVW1/5n+PgKAzcrueYVPjN3K/g= +github.com/mjudeikis/sigs-multicluster-runtime v0.0.0-20250818101434-d8ebc45e169b/go.mod h1:CpBzLMLQKdm+UCchd2FiGPiDdCxM5dgCCPKuaQ6Fsv0= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= +github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -148,6 +207,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= @@ -165,6 +226,8 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -172,10 +235,16 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= @@ -232,9 +301,12 @@ golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5N golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= @@ -247,12 +319,18 @@ golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= @@ -267,6 +345,11 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= @@ -278,10 +361,15 @@ google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7 google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= +gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= +gopkg.in/headzoo/surf.v1 v1.0.1 h1:oDBy9b5NlTb2Hvl3hF8NN+Qy7ypC9/g5YDP85pPh13k= +gopkg.in/headzoo/surf.v1 v1.0.1/go.mod h1:T0BH8276y+OPL0E4tisxCFjBVIAKGbwdYU7AS7/EpQQ= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -295,10 +383,18 @@ k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUy k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= +sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= +sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= +sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= +sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= diff --git a/pkg/indexers/serviceexport.go b/pkg/indexers/serviceexport.go index f449af6a1..ce76d97a3 100644 --- a/pkg/indexers/serviceexport.go +++ b/pkg/indexers/serviceexport.go @@ -24,7 +24,7 @@ import ( const ( ServiceExportByCustomResourceDefinition = "serviceExportByCustomResourceDefinition" - ServiceExportByAPIResourceSchema = "ServiceExportByAPIResourceSchema" + ServiceExportByBoundSchema = "serviceExportByBoundSchema" ) func IndexServiceExportByCustomResourceDefinition(obj any) ([]string, error) { @@ -36,16 +36,16 @@ func IndexServiceExportByCustomResourceDefinition(obj any) ([]string, error) { return []string{export.Name}, nil } -// IndexServiceExportByAPIResourceSchema is a controller-runtime compatible indexer function. -func IndexServiceExportByAPIResourceSchema(obj client.Object) []string { +// IndexServiceExportByBoundSchema is a controller-runtime compatible indexer function. +func IndexServiceExportByBoundSchema(obj client.Object) []string { export, ok := obj.(*v1alpha2.APIServiceExport) if !ok { return nil } names := make([]string, 0, len(export.Spec.Resources)) - for _, resource := range export.Spec.Resources { - names = append(names, resource.Name) + for _, res := range export.Spec.Resources { + names = append(names, res.ResourceGroupName()) } return names diff --git a/pkg/indexers/serviceexportrequest.go b/pkg/indexers/serviceexportrequest.go index fd7a04c5e..986c05edf 100644 --- a/pkg/indexers/serviceexportrequest.go +++ b/pkg/indexers/serviceexportrequest.go @@ -36,7 +36,7 @@ func IndexServiceExportRequestByGroupResource(obj client.Object) []string { } keys := []string{} for _, gr := range apiServiceExportRequest.Spec.Resources { - keys = append(keys, gr.Resource+"."+gr.Group) + keys = append(keys, gr.ResourceGroupName()) } return keys } @@ -50,7 +50,7 @@ func IndexServiceExportRequestByServiceExport(obj client.Object) []string { } keys := []string{} for _, gr := range apiServiceExportRequest.Spec.Resources { - keys = append(keys, apiServiceExportRequest.Namespace+"/"+gr.Resource+"."+gr.Group) + keys = append(keys, apiServiceExportRequest.Namespace+"/"+gr.ResourceGroupName()) } return keys } diff --git a/pkg/konnector/controllers/cluster/cluster_controller.go b/pkg/konnector/controllers/cluster/cluster_controller.go index 471048403..59e90595e 100644 --- a/pkg/konnector/controllers/cluster/cluster_controller.go +++ b/pkg/konnector/controllers/cluster/cluster_controller.go @@ -132,6 +132,7 @@ func NewController( providerConfig, serviceBindingInformer, providerBindInformers.KubeBind().V1alpha2().APIServiceExports(), + providerBindInformers.KubeBind().V1alpha2().APIServiceExportRequests(), crdInformer, ) if err != nil { diff --git a/pkg/konnector/controllers/cluster/servicebinding/servicebinding_controller.go b/pkg/konnector/controllers/cluster/servicebinding/servicebinding_controller.go index f41d16c21..7777fa8cd 100644 --- a/pkg/konnector/controllers/cluster/servicebinding/servicebinding_controller.go +++ b/pkg/konnector/controllers/cluster/servicebinding/servicebinding_controller.go @@ -54,6 +54,7 @@ func NewController( consumerConfig, providerConfig *rest.Config, serviceBindingInformer dynamic.Informer[bindlisters.APIServiceBindingLister], serviceExportInformer bindinformers.APIServiceExportInformer, + serviceExportRequestInformer bindinformers.APIServiceExportRequestInformer, crdInformer dynamic.Informer[apiextensionslisters.CustomResourceDefinitionLister], ) (*controller, error) { queue := workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[string](), workqueue.TypedRateLimitingQueueConfig[string]{Name: controllerName}) @@ -97,17 +98,17 @@ func NewController( getServiceExport: func(name string) (*kubebindv1alpha2.APIServiceExport, error) { return serviceExportInformer.Lister().APIServiceExports(providerNamespace).Get(name) }, + getServiceExportRequest: func(name string) (*kubebindv1alpha2.APIServiceExportRequest, error) { + return serviceExportRequestInformer.Lister().APIServiceExportRequests(providerNamespace).Get(name) + }, getServiceBinding: func(name string) (*kubebindv1alpha2.APIServiceBinding, error) { return serviceBindingInformer.Lister().Get(name) }, getClusterBinding: func(ctx context.Context) (*kubebindv1alpha2.ClusterBinding, error) { return providerBindClient.KubeBindV1alpha2().ClusterBindings(providerNamespace).Get(ctx, "cluster", metav1.GetOptions{}) }, - getAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - return providerBindClient.KubeBindV1alpha2().APIResourceSchemas().Get(ctx, name, metav1.GetOptions{}) - }, - getBoundAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { - return providerBindClient.KubeBindV1alpha2().BoundAPIResourceSchemas(providerNamespace).Get(ctx, name, metav1.GetOptions{}) + getBoundSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.BoundSchema, error) { + return providerBindClient.KubeBindV1alpha2().BoundSchemas(providerNamespace).Get(ctx, name, metav1.GetOptions{}) }, updateServiceExportStatus: func(ctx context.Context, export *kubebindv1alpha2.APIServiceExport) (*kubebindv1alpha2.APIServiceExport, error) { return providerBindClient.KubeBindV1alpha2().APIServiceExports(providerNamespace).UpdateStatus(ctx, export, metav1.UpdateOptions{}) diff --git a/pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go b/pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go index 5281003d7..c3e37e623 100644 --- a/pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go +++ b/pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go @@ -35,12 +35,12 @@ import ( type reconciler struct { consumerSecretRefKey, providerNamespace string - reconcileServiceBinding func(binding *kubebindv1alpha2.APIServiceBinding) bool - getServiceExport func(name string) (*kubebindv1alpha2.APIServiceExport, error) - getServiceBinding func(name string) (*kubebindv1alpha2.APIServiceBinding, error) - getClusterBinding func(ctx context.Context) (*kubebindv1alpha2.ClusterBinding, error) - getAPIResourceSchema func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) - getBoundAPIResourceSchema func(ctx context.Context, name string) (*kubebindv1alpha2.BoundAPIResourceSchema, error) + reconcileServiceBinding func(binding *kubebindv1alpha2.APIServiceBinding) bool + getServiceExport func(name string) (*kubebindv1alpha2.APIServiceExport, error) + getServiceExportRequest func(name string) (*kubebindv1alpha2.APIServiceExportRequest, error) + getServiceBinding func(name string) (*kubebindv1alpha2.APIServiceBinding, error) + getClusterBinding func(ctx context.Context) (*kubebindv1alpha2.ClusterBinding, error) + getBoundSchema func(ctx context.Context, name string) (*kubebindv1alpha2.BoundSchema, error) updateServiceExportStatus func(ctx context.Context, export *kubebindv1alpha2.APIServiceExport) (*kubebindv1alpha2.APIServiceExport, error) @@ -79,6 +79,21 @@ func (r *reconciler) ensureValidServiceExport(_ context.Context, binding *kubebi if _, err := r.getServiceExport(binding.Name); err != nil && !errors.IsNotFound(err) { return err } else if errors.IsNotFound(err) { + // If serviceexport is not found, check if request is not in failed state already: + request, errRequest := r.getServiceExportRequest(binding.Name) + if errRequest == nil && request.Status.Phase == kubebindv1alpha2.APIServiceExportRequestPhaseFailed { + // If request is in failed state, propagate the message to binding status and mark as failed + conditions.MarkFalse( + binding, + kubebindv1alpha2.APIServiceBindingConditionConnected, + "APIServiceExportFailed", + conditionsapi.ConditionSeverityError, + request.Status.TerminalMessage, + binding.Name, + ) + return fmt.Errorf("APIServiceExportRequest %s is in failed state: %s", binding.Name, request.Status.TerminalMessage) + } + conditions.MarkFalse( binding, kubebindv1alpha2.APIServiceBindingConditionConnected, @@ -117,14 +132,14 @@ func (r *reconciler) ensureCRDs(ctx context.Context, binding *kubebindv1alpha2.A } // Get all APIResourceSchema objects referenced by the export - schemas, err := r.getAPIResourceSchemasFromExport(ctx, export) + schemas, err := r.getSchemasFromExport(ctx, export) if err != nil { conditions.MarkFalse( binding, kubebindv1alpha2.APIServiceBindingConditionConnected, - "APIResourceSchemaFetchFailed", + "BoundSchemaFetchFailed", conditionsapi.ConditionSeverityError, - "Failed to fetch APIResourceSchema objects: %s", + "Failed to fetch BoundSchema objects: %s", err, ) // We dont have schema - try again. Might be a race on provider side. @@ -137,7 +152,7 @@ func (r *reconciler) ensureCRDs(ctx context.Context, binding *kubebindv1alpha2.A errs = append(errs, err) } - if err := r.ensureCRDsFromAPIResourceSchema(ctx, binding, schema); err != nil { + if err := r.ensureCRDsFromBoundSchema(ctx, binding, schema); err != nil { errs = append(errs, err) } } @@ -152,17 +167,17 @@ func (r *reconciler) ensureCRDs(ctx context.Context, binding *kubebindv1alpha2.A } func (r *reconciler) referenceBoundAPIResourceSchema(ctx context.Context, binding *kubebindv1alpha2.APIServiceBinding, name string) error { - boundSchema, err := r.getBoundAPIResourceSchema(ctx, name) + boundSchema, err := r.getBoundSchema(ctx, name) if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("failed to get BoundAPIResourceSchema %s: %w", name, err) + return fmt.Errorf("failed to get BoundSchema %s: %w", name, err) } if boundSchema == nil { return nil } - group := boundSchema.Spec.APIResourceSchemaCRDSpec.Group - resource := boundSchema.Spec.APIResourceSchemaCRDSpec.Names.Plural + group := boundSchema.Spec.Group + resource := boundSchema.Spec.Names.Plural if len(binding.Status.BoundSchemas) > 0 { for _, ref := range binding.Status.BoundSchemas { @@ -187,20 +202,9 @@ func (r *reconciler) referenceBoundAPIResourceSchema(ctx context.Context, bindin return nil } -func (r *reconciler) ensureCRDsFromAPIResourceSchema(ctx context.Context, binding *kubebindv1alpha2.APIServiceBinding, schema *kubebindv1alpha2.APIResourceSchema) error { +func (r *reconciler) ensureCRDsFromBoundSchema(ctx context.Context, binding *kubebindv1alpha2.APIServiceBinding, schema *kubebindv1alpha2.BoundSchema) error { var errs []error - crd, err := kubebindhelpers.APIResourceSchemaToCRD(schema) - if err != nil { - conditions.MarkFalse( - binding, - kubebindv1alpha2.APIServiceBindingConditionConnected, - "APIResourceSchemaInvalid", - conditionsapi.ConditionSeverityError, - "APIResourceSchema %s is invalid: %s", - binding.Name, err, - ) - return nil - } + crd := kubebindhelpers.BoundSchemaToCRD(schema) newReference := metav1.OwnerReference{ APIVersion: kubebindv1alpha2.SchemeGroupVersion.String(), @@ -275,19 +279,15 @@ func (r *reconciler) ensurePrettyName(ctx context.Context, binding *kubebindv1al return nil } -func (r *reconciler) getAPIResourceSchemasFromExport(ctx context.Context, export *kubebindv1alpha2.APIServiceExport) ([]*kubebindv1alpha2.APIResourceSchema, error) { - schemas := make([]*kubebindv1alpha2.APIResourceSchema, 0, len(export.Spec.Resources)) - - for _, ref := range export.Spec.Resources { - if ref.Type != "APIResourceSchema" { - return nil, fmt.Errorf("unsupported resource type %q in APIServiceExport %s", - ref.Type, export.Name) - } +// getSchemasFromExport will return all schemas, based on export we are dealing with. +func (r *reconciler) getSchemasFromExport(ctx context.Context, export *kubebindv1alpha2.APIServiceExport) ([]*kubebindv1alpha2.BoundSchema, error) { + schemas := make([]*kubebindv1alpha2.BoundSchema, 0, len(export.Spec.Resources)) - schema, err := r.getAPIResourceSchema(ctx, ref.Name) + for _, res := range export.Spec.Resources { + schema, err := r.getBoundSchema(ctx, res.ResourceGroupName()) if err != nil { - return nil, fmt.Errorf("failed to get APIResourceSchema %s: %w", - ref.Name, err) + return nil, fmt.Errorf("failed to get Schema %s: %w", + res.ResourceGroupName(), err) } schemas = append(schemas, schema) diff --git a/pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile_test.go b/pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile_test.go index 23b6f30c8..d51cee75e 100644 --- a/pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile_test.go +++ b/pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile_test.go @@ -34,8 +34,7 @@ func TestEnsureCRDs(t *testing.T) { name string bindingName string getCRD func(name string) (*apiextensionsv1.CustomResourceDefinition, error) - boundSchema *kubebindv1alpha2.BoundAPIResourceSchema - schema *kubebindv1alpha2.APIResourceSchema + boundSchema *kubebindv1alpha2.BoundSchema serviceExport *kubebindv1alpha2.APIServiceExport expectConditions conditionsapi.Conditions wantErr bool @@ -46,10 +45,9 @@ func TestEnsureCRDs(t *testing.T) { getCRD: func(name string) (*apiextensionsv1.CustomResourceDefinition, error) { return nil, errors.NewNotFound(apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions").GroupResource(), name) }, - schema: newAPIResourceSchema("test-schema", "default", "example.com", "tests"), - boundSchema: newBoundAPIResourceSchema("test-schema", "default", "example.com", "tests"), - serviceExport: newServiceExportWithResources("test-binding", "default", []kubebindv1alpha2.APIResourceSchemaReference{ - {Name: "test-schema", Type: "APIResourceSchema"}, + boundSchema: newBoundSchema("tests.example.com", "default", "example.com", "tests"), + serviceExport: newServiceExportWithResources("test-binding", "default", []kubebindv1alpha2.APIServiceExportRequestResource{ + {GroupResource: kubebindv1alpha2.GroupResource{Group: "example.com", Resource: "tests"}}, }), expectConditions: conditionsapi.Conditions{ conditionsapi.Condition{Type: "Connected", Status: "True"}, @@ -60,10 +58,9 @@ func TestEnsureCRDs(t *testing.T) { name: "fail-when-external-crd-present", bindingName: "test-binding", getCRD: newGetCRD("tests.example.com", newCRD("tests.example.com")), - schema: newAPIResourceSchema("test-schema", "default", "example.com", "tests"), - boundSchema: newBoundAPIResourceSchema("test-schema", "default", "example.com", "tests"), - serviceExport: newServiceExportWithResources("test-binding", "default", []kubebindv1alpha2.APIResourceSchemaReference{ - {Name: "test-schema", Type: "APIResourceSchema"}, + boundSchema: newBoundSchema("tests.example.com", "default", "example.com", "tests"), + serviceExport: newServiceExportWithResources("test-binding", "default", []kubebindv1alpha2.APIServiceExportRequestResource{ + {GroupResource: kubebindv1alpha2.GroupResource{Group: "example.com", Resource: "tests"}}, }), expectConditions: conditionsapi.Conditions{ conditionsapi.Condition{ @@ -82,17 +79,11 @@ func TestEnsureCRDs(t *testing.T) { r := &reconciler{ getCRD: tt.getCRD, getServiceExport: newGetServiceExport(tt.serviceExport.Name, tt.serviceExport), - getAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - if name == tt.schema.Name { - return tt.schema, nil - } - return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("apiresourceschemas").GroupResource(), name) - }, - getBoundAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { - if name == tt.schema.Name { + getBoundSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.BoundSchema, error) { + if name == tt.boundSchema.Name { return tt.boundSchema, nil } - return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("boundapiresourceschemas").GroupResource(), name) + return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("boundschemas").GroupResource(), name) }, createCRD: func(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition) (*apiextensionsv1.CustomResourceDefinition, error) { return crd.DeepCopy(), nil @@ -116,48 +107,34 @@ func TestEnsureCRDs(t *testing.T) { }) } } -func newBoundAPIResourceSchema(name, namespace string, group, plural string) *kubebindv1alpha2.BoundAPIResourceSchema { - return &kubebindv1alpha2.BoundAPIResourceSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: kubebindv1alpha2.BoundAPIResourceSchemaSpec{ - APIResourceSchemaCRDSpec: kubebindv1alpha2.APIResourceSchemaCRDSpec{ - Group: group, - Names: apiextensionsv1.CustomResourceDefinitionNames{ - Plural: plural, - }, - }, - }, - } -} - -func newAPIResourceSchema(name, namespace, group, plural string) *kubebindv1alpha2.APIResourceSchema { - return &kubebindv1alpha2.APIResourceSchema{ +func newBoundSchema(name, namespace string, group, plural string) *kubebindv1alpha2.BoundSchema { + return &kubebindv1alpha2.BoundSchema{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, - Spec: kubebindv1alpha2.APIResourceSchemaSpec{ - APIResourceSchemaCRDSpec: kubebindv1alpha2.APIResourceSchemaCRDSpec{ + Spec: kubebindv1alpha2.BoundSchemaSpec{ + InformerScope: kubebindv1alpha2.NamespacedScope, + APICRDSpec: kubebindv1alpha2.APICRDSpec{ Group: group, Names: apiextensionsv1.CustomResourceDefinitionNames{ Plural: plural, }, + Scope: apiextensionsv1.NamespaceScoped, }, }, } } -func newServiceExportWithResources(name, namespace string, resources []kubebindv1alpha2.APIResourceSchemaReference) *kubebindv1alpha2.APIServiceExport { +func newServiceExportWithResources(name, namespace string, resources []kubebindv1alpha2.APIServiceExportRequestResource) *kubebindv1alpha2.APIServiceExport { return &kubebindv1alpha2.APIServiceExport{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Spec: kubebindv1alpha2.APIServiceExportSpec{ - Resources: resources, + Resources: resources, + InformerScope: kubebindv1alpha2.NamespacedScope, }, } } diff --git a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go index 75cb35632..a5be2edd6 100644 --- a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go +++ b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go @@ -96,23 +96,18 @@ func NewController( syncContext: map[string]syncContext{}, - getCRD: func(name string) (*apiextensionsv1.CustomResourceDefinition, error) { - return crdInformer.Lister().Get(name) - }, getServiceBinding: func(name string) (*kubebindv1alpha2.APIServiceBinding, error) { return serviceBindingInformer.Lister().Get(name) }, - getAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - return providerBindClient.KubeBindV1alpha2().APIResourceSchemas().Get(ctx, name, metav1.GetOptions{}) - }, - getBoundAPIResourceSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { - return providerBindClient.KubeBindV1alpha2().BoundAPIResourceSchemas(providerNamespace).Get(ctx, name, metav1.GetOptions{}) + getCRD: func(name string) (*apiextensionsv1.CustomResourceDefinition, error) { + return crdInformer.Lister().Get(name) }, - createBoundAPIResourceSchema: func(ctx context.Context, boundSchema *kubebindv1alpha2.BoundAPIResourceSchema) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { - return providerBindClient.KubeBindV1alpha2().BoundAPIResourceSchemas(providerNamespace).Create(ctx, boundSchema, metav1.CreateOptions{}) + getRemoteBoundSchema: func(ctx context.Context, name string) (*kubebindv1alpha2.BoundSchema, error) { + return providerBindClient.KubeBindV1alpha2().BoundSchemas(providerNamespace).Get(ctx, name, metav1.GetOptions{}) }, - updateBoundAPIResourceSchema: func(ctx context.Context, boundSchema *kubebindv1alpha2.BoundAPIResourceSchema) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { - return providerBindClient.KubeBindV1alpha2().BoundAPIResourceSchemas(providerNamespace).Update(ctx, boundSchema, metav1.UpdateOptions{}) + updateRemoteBoundSchema: func(ctx context.Context, boundSchema *kubebindv1alpha2.BoundSchema) error { + _, err := providerBindClient.KubeBindV1alpha2().BoundSchemas(providerNamespace).UpdateStatus(ctx, boundSchema, metav1.UpdateOptions{}) + return err }, }, diff --git a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go index a625490f7..1b8b0d692 100644 --- a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go +++ b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go @@ -19,7 +19,6 @@ package serviceexport import ( "context" "fmt" - "reflect" "strings" "sync" "time" @@ -57,12 +56,11 @@ type reconciler struct { lock sync.Mutex syncContext map[string]syncContext // by CRD name - getCRD func(name string) (*apiextensionsv1.CustomResourceDefinition, error) - getServiceBinding func(name string) (*kubebindv1alpha2.APIServiceBinding, error) - getAPIResourceSchema func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) - getBoundAPIResourceSchema func(ctx context.Context, name string) (*kubebindv1alpha2.BoundAPIResourceSchema, error) - createBoundAPIResourceSchema func(ctx context.Context, boundSchema *kubebindv1alpha2.BoundAPIResourceSchema) (*kubebindv1alpha2.BoundAPIResourceSchema, error) - updateBoundAPIResourceSchema func(ctx context.Context, boundSchema *kubebindv1alpha2.BoundAPIResourceSchema) (*kubebindv1alpha2.BoundAPIResourceSchema, error) + getCRD func(name string) (*apiextensionsv1.CustomResourceDefinition, error) + getServiceBinding func(name string) (*kubebindv1alpha2.APIServiceBinding, error) + getRemoteBoundSchema func(ctx context.Context, name string) (*kubebindv1alpha2.BoundSchema, error) + + updateRemoteBoundSchema func(ctx context.Context, boundSchema *kubebindv1alpha2.BoundSchema) error } type syncContext struct { @@ -131,21 +129,17 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export var errs []error processedSchemas := make(map[string]bool) - for _, resourceRef := range export.Spec.Resources { - if resourceRef.Type != "APIResourceSchema" { - logger.V(1).Info("Skipping unsupported resource type", "type", resourceRef.Type) - continue - } - + for _, res := range export.Spec.Resources { + name := res.ResourceGroupName() // Fetch the APIResourceSchema - schema, err := r.getAPIResourceSchema(ctx, resourceRef.Name) + schema, err := r.getRemoteBoundSchema(ctx, name) if err != nil { if errors.IsNotFound(err) { // Stop the controller for this schema if it exists r.lock.Lock() - key := resourceRef.Name + "." + export.Name + key := name + "." + export.Name if c, found := r.syncContext[key]; found { - logger.V(1).Info("Stopping APIServiceExport resource sync", "key", key, "reason", "APIResourceSchema not found") + logger.V(1).Info("Stopping APIServiceExport resource sync", "key", key, "reason", "BoundSchema not found") c.cancel() delete(r.syncContext, key) } @@ -156,18 +150,12 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export continue } - // Ensure BoundAPIResourceSchema exists for tracking status - if err := r.ensureBoundAPIResourceSchema(ctx, export, schema); err != nil { - errs = append(errs, err) - continue - } - // Start/update controller for this schema if err := r.ensureControllerForSchema(ctx, export, schema); err != nil { errs = append(errs, err) } - processedSchemas[resourceRef.Name] = true + processedSchemas[name] = true } // Stop controllers for schemas that are no longer referenced @@ -188,66 +176,7 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export return utilerrors.NewAggregate(errs) } -func (r *reconciler) ensureBoundAPIResourceSchema(ctx context.Context, export *kubebindv1alpha2.APIServiceExport, schema *kubebindv1alpha2.APIResourceSchema) error { - boundSchema, err := r.getBoundAPIResourceSchema(ctx, schema.Name) - if err != nil { - if errors.IsNotFound(err) { - // Create new BoundAPIResourceSchema - boundSchema = &kubebindv1alpha2.BoundAPIResourceSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: schema.Name, - Namespace: export.Namespace, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: kubebindv1alpha2.SchemeGroupVersion.String(), - Kind: "APIServiceExport", - Name: export.Name, - UID: export.UID, - Controller: func() *bool { b := true; return &b }(), - }, - }, - }, - Spec: kubebindv1alpha2.BoundAPIResourceSchemaSpec{ - InformerScope: schema.Spec.InformerScope, - APIResourceSchemaCRDSpec: schema.Spec.APIResourceSchemaCRDSpec, - }, - } - - conditions.MarkFalse( - boundSchema, - kubebindv1alpha2.BoundAPIResourceSchemaValid, - string(kubebindv1alpha2.BoundAPIResourceSchemaPending), - conditionsapi.ConditionSeverityInfo, - "Waiting for CRD to be created in consumer cluster", - ) - - _, err := r.createBoundAPIResourceSchema(ctx, boundSchema) - if err != nil { - return fmt.Errorf("failed to create BoundAPIResourceSchema %s: %w", boundSchema.Name, err) - } - return nil - } - return err - } - - // Check if InformerScope needs updating - if boundSchema.Spec.InformerScope != schema.Spec.InformerScope { - boundSchema.Spec.InformerScope = schema.Spec.InformerScope - } - - if !reflect.DeepEqual(boundSchema.Spec.APIResourceSchemaCRDSpec, schema.Spec.APIResourceSchemaCRDSpec) { - boundSchema.Spec.APIResourceSchemaCRDSpec = schema.Spec.APIResourceSchemaCRDSpec - } - - _, err = r.updateBoundAPIResourceSchema(ctx, boundSchema) - if err != nil { - return fmt.Errorf("failed to update BoundAPIResourceSchema %s: %w", boundSchema.Name, err) - } - - return nil -} - -func (r *reconciler) ensureControllerForSchema(ctx context.Context, export *kubebindv1alpha2.APIServiceExport, schema *kubebindv1alpha2.APIResourceSchema) error { +func (r *reconciler) ensureControllerForSchema(ctx context.Context, export *kubebindv1alpha2.APIServiceExport, schema *kubebindv1alpha2.BoundSchema) error { logger := klog.FromContext(ctx) key := schema.Name + "." + export.Name @@ -274,7 +203,7 @@ func (r *reconciler) ensureControllerForSchema(ctx context.Context, export *kube } } if syncVersion == "" { - return fmt.Errorf("no served version found for APIResourceSchema %s", schema.Name) + return fmt.Errorf("no served version found for BoundSchema %s", schema.Name) } gvr := runtimeschema.GroupVersionResource{ @@ -387,8 +316,8 @@ func (r *reconciler) ensureCRDConditionsCopiedToBoundSchema(ctx context.Context, } var errs []error allValid := true // assume all BoundAPIResourceSchemas are valid - for _, resourceRef := range export.Spec.Resources { - schema, err := r.getAPIResourceSchema(ctx, resourceRef.Name) + for _, res := range export.Spec.Resources { + boundSchema, err := r.getRemoteBoundSchema(ctx, res.ResourceGroupName()) if err != nil { if errors.IsNotFound(err) { continue @@ -397,7 +326,7 @@ func (r *reconciler) ensureCRDConditionsCopiedToBoundSchema(ctx context.Context, continue } - crd, err := r.getCRD(schema.Name) + crd, err := r.getCRD(boundSchema.Name) if err != nil { if errors.IsNotFound(err) { continue @@ -406,15 +335,6 @@ func (r *reconciler) ensureCRDConditionsCopiedToBoundSchema(ctx context.Context, continue } - boundSchema, err := r.getBoundAPIResourceSchema(ctx, schema.Name) - if err != nil { - if errors.IsNotFound(err) { - continue // BoundAPIResourceSchema not found, nothing to update - } - errs = append(errs, err) - continue - } - boundSchemaIndex := map[conditionsapi.ConditionType]int{} for i, c := range boundSchema.Status.Conditions { boundSchemaIndex[c.Type] = i @@ -449,7 +369,7 @@ func (r *reconciler) ensureCRDConditionsCopiedToBoundSchema(ctx context.Context, boundSchema.Status.AcceptedNames = crd.Status.AcceptedNames boundSchema.Status.StoredVersions = crd.Status.StoredVersions - if _, err := r.updateBoundAPIResourceSchema(ctx, boundSchema); err != nil { + if err := r.updateRemoteBoundSchema(ctx, boundSchema); err != nil { errs = append(errs, err) allValid = false // at least one BoundAPIResourceSchema is not valid } diff --git a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile_test.go b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile_test.go index 7aee3b3df..bbf1c2efb 100644 --- a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile_test.go +++ b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile_test.go @@ -34,10 +34,9 @@ func TestEnsureCRDConditionsCopiedToBoundSchema(t *testing.T) { tests := []struct { name string getCRD func(name string) (*apiextensionsv1.CustomResourceDefinition, error) - schema *kubebindv1alpha2.APIResourceSchema - boundSchema *kubebindv1alpha2.BoundAPIResourceSchema + boundSchema *kubebindv1alpha2.BoundSchema export *kubebindv1alpha2.APIServiceExport - expected *kubebindv1alpha2.BoundAPIResourceSchema + expected *kubebindv1alpha2.BoundSchema wantErr bool }{ { @@ -46,16 +45,15 @@ func TestEnsureCRDConditionsCopiedToBoundSchema(t *testing.T) { {Type: "Something", Status: "True", Reason: "Reason", Message: "message"}, {Type: "Established", Status: "True", Reason: "Reason", Message: "message"}, })), - schema: newAPIResourceSchema("foo-schema", "default", "example.com", "foos"), - boundSchema: newBoundAPIResourceSchema("foo-schema", []conditionsapi.Condition{ + boundSchema: newBoundSchema("foo-schema", []conditionsapi.Condition{ {Type: "Ready", Status: "False", Severity: "Warning", Reason: "SomethingElseWrong", Message: "something else went wrong"}, {Type: "Established", Status: "True", Severity: "None", Reason: "Reason", Message: "message"}, {Type: "Structural", Status: "False", Severity: "Warning", Reason: "SomethingWrong", Message: "something went wrong"}, }), - export: newExportWithResources("test-export", "default", []kubebindv1alpha2.APIResourceSchemaReference{ - {Name: "foo-schema", Type: "APIResourceSchema"}, + export: newExportWithResources("test-export", "default", []kubebindv1alpha2.APIServiceExportRequestResource{ + {GroupResource: kubebindv1alpha2.GroupResource{Group: "example.com", Resource: "foos"}}, }), - expected: newBoundAPIResourceSchema("foo-schema", []conditionsapi.Condition{ + expected: newBoundSchema("foo-schema", []conditionsapi.Condition{ {Type: "Ready", Status: "False", Severity: "Warning", Reason: "SomethingWrong", Message: "something went wrong"}, {Type: "Established", Status: "True", Severity: "", Reason: "Reason", Message: "message"}, {Type: "Something", Status: "True", Severity: "", Reason: "Reason", Message: "message"}, @@ -67,13 +65,12 @@ func TestEnsureCRDConditionsCopiedToBoundSchema(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Track updated schema - var updatedSchema *kubebindv1alpha2.BoundAPIResourceSchema + var updatedSchema *kubebindv1alpha2.BoundSchema ctx := context.Background() r := &reconciler{ - getCRD: tt.getCRD, - getAPIResourceSchema: newGetAPIResourceSchema(ctx, tt.schema), - getBoundAPIResourceSchema: newGetBoundAPIResourceSchema(ctx, tt.boundSchema), - updateBoundAPIResourceSchema: newUpdateBoundAPIResourceSchema(&updatedSchema), + getCRD: tt.getCRD, + getRemoteBoundSchema: newGetBoundSchema(ctx, tt.boundSchema), + updateRemoteBoundSchema: newUpdateBoundSchema(&updatedSchema), } if err := r.ensureCRDConditionsCopiedToBoundSchema(context.Background(), tt.export); (err != nil) != tt.wantErr { @@ -113,66 +110,41 @@ func newCRD(name string, conditions []apiextensionsv1.CustomResourceDefinitionCo } } -func newAPIResourceSchema(name, namespace, group, plural string) *kubebindv1alpha2.APIResourceSchema { - return &kubebindv1alpha2.APIResourceSchema{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: kubebindv1alpha2.APIResourceSchemaSpec{ - APIResourceSchemaCRDSpec: kubebindv1alpha2.APIResourceSchemaCRDSpec{ - Group: group, - Names: apiextensionsv1.CustomResourceDefinitionNames{ - Plural: plural, - }, - }, - }, - } -} - -func newGetAPIResourceSchema(_ context.Context, schema *kubebindv1alpha2.APIResourceSchema) func(ctx context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - return func(_ context.Context, name string) (*kubebindv1alpha2.APIResourceSchema, error) { - if name == schema.Name { - return schema, nil - } - return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("apiresourceschemas").GroupResource(), name) - } -} - -func newGetBoundAPIResourceSchema(_ context.Context, boundSchema *kubebindv1alpha2.BoundAPIResourceSchema) func(ctx context.Context, name string) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { - return func(ctx context.Context, name string) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { +func newGetBoundSchema(_ context.Context, boundSchema *kubebindv1alpha2.BoundSchema) func(ctx context.Context, name string) (*kubebindv1alpha2.BoundSchema, error) { + return func(ctx context.Context, name string) (*kubebindv1alpha2.BoundSchema, error) { if name == boundSchema.Name { return boundSchema, nil } - return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("boundapiresourceschemas").GroupResource(), name) + return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("boundschemas").GroupResource(), name) } } -func newUpdateBoundAPIResourceSchema(updatedSchemaPtr **kubebindv1alpha2.BoundAPIResourceSchema) func(context.Context, *kubebindv1alpha2.BoundAPIResourceSchema) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { - return func(ctx context.Context, boundSchema *kubebindv1alpha2.BoundAPIResourceSchema) (*kubebindv1alpha2.BoundAPIResourceSchema, error) { +func newUpdateBoundSchema(updatedSchemaPtr **kubebindv1alpha2.BoundSchema) func(context.Context, *kubebindv1alpha2.BoundSchema) error { + return func(ctx context.Context, boundSchema *kubebindv1alpha2.BoundSchema) error { *updatedSchemaPtr = boundSchema.DeepCopy() - return boundSchema, nil + return nil } } -func newExportWithResources(name, namespace string, resources []kubebindv1alpha2.APIResourceSchemaReference) *kubebindv1alpha2.APIServiceExport { +func newExportWithResources(name, namespace string, resources []kubebindv1alpha2.APIServiceExportRequestResource) *kubebindv1alpha2.APIServiceExport { return &kubebindv1alpha2.APIServiceExport{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, Spec: kubebindv1alpha2.APIServiceExportSpec{ - Resources: resources, + Resources: resources, + InformerScope: kubebindv1alpha2.NamespacedScope, }, } } -func newBoundAPIResourceSchema(name string, conditions []conditionsapi.Condition) *kubebindv1alpha2.BoundAPIResourceSchema { - return &kubebindv1alpha2.BoundAPIResourceSchema{ +func newBoundSchema(name string, conditions []conditionsapi.Condition) *kubebindv1alpha2.BoundSchema { + return &kubebindv1alpha2.BoundSchema{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, - Status: kubebindv1alpha2.BoundAPIResourceSchemaStatus{ + Status: kubebindv1alpha2.BoundSchemaStatus{ Conditions: conditions, }, } diff --git a/sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go b/sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go index 2be34e89e..f51f8eff1 100644 --- a/sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go +++ b/sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go @@ -73,11 +73,19 @@ func (in *APIServiceExport) SetConditions(conditions conditionsapi.Conditions) { in.Status.Conditions = conditions } +// APIServiceExportResource is a type alias for APIServiceExportRequestResource. +type APIServiceExportResource = APIServiceExportRequestResource + // APIServiceExportSpec defines the desired state of APIServiceExport. type APIServiceExportSpec struct { - // resources specifies the API resources to export + // resources is a list of resources that should be exported. + // // +required - Resources []APIResourceSchemaReference `json:"resources"` + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="resources are immutable" + Resources []APIServiceExportResource `json:"resources"` + // informerScope is the scope of the APIServiceExport. It can be either Cluster or Namespace. // // Cluster: The konnector has permission to watch all namespaces at once and cluster-scoped resources. @@ -95,20 +103,6 @@ type APIServiceExportSpec struct { ClusterScopedIsolation Isolation `json:"clusterScopedIsolation,omitempty"` } -// APIResourceSchemaReference is a list of references to APIResourceSchemas. -type APIResourceSchemaReference struct { - // Name is the name of the resource to export - // +required - // +kubebuilder:validation:Required - Name string `json:"name"` - - // Type of the resource to export - // Currently only APIResourceSchema is supported - // +kubebuilder:validation:Enum=APIResourceSchema - // +required - Type string `json:"type"` -} - // Isolation is an enum defining the different ways to isolate cluster scoped objects // // +kubebuilder:validation:Enum=Prefixed;Namespaced;None diff --git a/sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go b/sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go index 302dff235..938ecb9a1 100644 --- a/sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go +++ b/sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go @@ -17,6 +17,8 @@ limitations under the License. package v1alpha2 import ( + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -114,6 +116,13 @@ type APIServiceExportRequestResource struct { Versions []string `json:"versions,omitempty"` } +func (r APIServiceExportRequestResource) ResourceGroupName() string { + if r.Group == "" { + r.Group = "core" + } + return fmt.Sprintf("%s.%s", r.Resource, r.Group) +} + // GroupResource identifies a resource. type GroupResource struct { // group is the name of an API group. diff --git a/sdk/apis/kubebind/v1alpha2/boundapiresourceschema_types.go b/sdk/apis/kubebind/v1alpha2/boundapiresourceschema_types.go deleted file mode 100644 index 7c0e74df3..000000000 --- a/sdk/apis/kubebind/v1alpha2/boundapiresourceschema_types.go +++ /dev/null @@ -1,116 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha2 - -import ( - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - conditionsapi "github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" -) - -// BoundAPIResourceSchema -// +crd -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +kubebuilder:resource:scope=Namespaced,categories=kube-bindings,shortName=bas -// +kubebuilder:subresource:status -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" -type BoundAPIResourceSchema struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec BoundAPIResourceSchemaSpec `json:"spec"` - Status BoundAPIResourceSchemaStatus `json:"status,omitempty"` -} - -// BoundAPIResourceSchemaSpec defines the desired state of the BoundAPIResourceSchema. -type BoundAPIResourceSchemaSpec struct { - // InformerScope indicates whether the informer for defined custom resource is cluster- or namespace-scoped. - // Allowed values are `Cluster` and `Namespaced`. - // - // +required - // +kubebuilder:validation:Enum=Cluster;Namespaced - InformerScope InformerScope `json:"informerScope"` - - APIResourceSchemaCRDSpec `json:",inline"` -} - -const ( - // BoundAPIResourceSchemaReady indicates that the API resource schema is ready. - // It is set to true when the API resource schema is accepted and there are no drifts detected. - BoundAPIResourceSchemaValid conditionsapi.ConditionType = "Valid" - // BoundAPIResourceSchemaDriftDetected indicates that there is a drift between the consumer's API and the expected API. - // It is set to true when the API resource schema is not accepted or there are drifts detected. - BoundAPIResourceSchemaInvalid conditionsapi.ConditionType = "Invalid" -) - -// BoundAPIResourceSchemaConditionReason is the set of reasons for specific condition type. -// +kubebuilder:validation:Enum=Accepted;Rejected;Pending;DriftDetected -type BoundAPIResourceSchemaConditionReason string - -const ( - // BoundAPIResourceSchemaAccepted indicates that the API resource schema is accepted. - BoundAPIResourceSchemaAccepted BoundAPIResourceSchemaConditionReason = "Accepted" - // BoundAPIResourceSchemaRejected indicates that the API resource schema is rejected. - BoundAPIResourceSchemaRejected BoundAPIResourceSchemaConditionReason = "Rejected" - // BoundAPIResourceSchemaPending indicates that the API resource schema is pending. - BoundAPIResourceSchemaPending BoundAPIResourceSchemaConditionReason = "Pending" - // BoundAPIResourceSchemaDriftDetected indicates that there is a drift between the consumer's API and the expected API. - BoundAPIResourceSchemaDriftDetected BoundAPIResourceSchemaConditionReason = "DriftDetected" -) - -func (in *BoundAPIResourceSchema) GetConditions() conditionsapi.Conditions { - return in.Status.Conditions -} - -func (in *BoundAPIResourceSchema) SetConditions(conditions conditionsapi.Conditions) { - in.Status.Conditions = conditions -} - -// BoundAPIResourceSchemaStatus defines the observed state of the BoundAPIResourceSchema. -type BoundAPIResourceSchemaStatus struct { - // acceptedNames are the names that are actually being used to serve discovery. - // They may be different than the names in spec. - // +optional - AcceptedNames apiextensionsv1.CustomResourceDefinitionNames `json:"acceptedNames"` - - // storedVersions lists all versions of CustomResources that were ever persisted. Tracking these - // versions allows a migration path for stored versions in etcd. The field is mutable - // so a migration controller can finish a migration to another version (ensuring - // no old objects are left in storage), and then remove the rest of the - // versions from this list. - // Versions may not be removed from `spec.versions` while they exist in this list. - // +optional - StoredVersions []string `json:"storedVersions"` - // Conditions represent the latest available observations of the object's state. - // +optional - Conditions []conditionsapi.Condition `json:"conditions,omitempty"` - - // Instantiations tracks the number of instances of the resource on the consumer side. - // +optional - Instantiations int `json:"instantiations,omitempty"` -} - -// BoundAPIResourceSchemaList is a list of BoundAPIResourceSchemas. -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type BoundAPIResourceSchemaList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []BoundAPIResourceSchema `json:"items"` -} diff --git a/sdk/apis/kubebind/v1alpha2/apiresourceschema_types.go b/sdk/apis/kubebind/v1alpha2/boundchema_types.go similarity index 70% rename from sdk/apis/kubebind/v1alpha2/apiresourceschema_types.go rename to sdk/apis/kubebind/v1alpha2/boundchema_types.go index 589f3ede0..a1122d772 100644 --- a/sdk/apis/kubebind/v1alpha2/apiresourceschema_types.go +++ b/sdk/apis/kubebind/v1alpha2/boundchema_types.go @@ -22,8 +22,25 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + + conditionsapi "github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/apis/conditions/v1alpha1" ) +// BoundSchema +// +crd +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Namespaced,categories=kube-bindings,shortName=bs +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +type BoundSchema struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BoundSchemaSpec `json:"spec"` + Status BoundSchemaStatus `json:"status,omitempty"` +} + // InformerScope is the scope of the Api. // // +kubebuilder:validation:Enum=Cluster;Namespaced @@ -34,21 +51,13 @@ const ( NamespacedScope InformerScope = "Namespaced" ) -// APIResourceSchema -// +crd -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +kubebuilder:resource:scope=Cluster,categories=kube-bindings,shortName=as -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" -type APIResourceSchema struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec APIResourceSchemaSpec `json:"spec"` +// String returns the string representation of the InformerScope. +func (in InformerScope) String() string { + return string(in) } -type APIResourceSchemaSpec struct { +// BoundSchemaSpec defines the desired state of the BoundSchema. +type BoundSchemaSpec struct { // InformerScope indicates whether the informer for defined custom resource is cluster- or namespace-scoped. // Allowed values are `Cluster` and `Namespaced`. // @@ -56,10 +65,11 @@ type APIResourceSchemaSpec struct { // +kubebuilder:validation:Enum=Cluster;Namespaced InformerScope InformerScope `json:"informerScope"` - APIResourceSchemaCRDSpec `json:",inline"` + // API CRD Spec is copy paste from apiextensionsv1.CustomResourceDefinitionSpec to allow deep copy + APICRDSpec `json:",inline"` } -type APIResourceSchemaCRDSpec struct { +type APICRDSpec struct { // group is the API group of the defined custom resource. Empty string means the // core API group. The resources are served under `/apis//...` or `/api` for the core group. // @@ -93,6 +103,70 @@ type APIResourceSchemaCRDSpec struct { Conversion *CustomResourceConversion `json:"conversion,omitempty"` } +// CustomResourceConversion describes how to convert different versions of a CR. +// +kubebuilder:validation:XValidation:message="Webhook must be specified if strategy=Webhook",rule="(self.strategy == 'None' && !has(self.webhook)) || (self.strategy == 'Webhook' && has(self.webhook))" +type CustomResourceConversion struct { + // strategy specifies how custom resources are converted between versions. Allowed values are: + // - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource. + // - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information + // is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set. + // +kubebuilder:validation:Enum=None;Webhook + Strategy ConversionStrategyType `json:"strategy"` + + // webhook describes how to call the conversion webhook. Required when `strategy` is set to `"Webhook"`. + // +optional + Webhook *WebhookConversion `json:"webhook,omitempty"` +} + +// WebhookConversion describes how to call a conversion webhook +type WebhookConversion struct { + // clientConfig is the instructions for how to call the webhook if strategy is `Webhook`. + // +optional + ClientConfig *WebhookClientConfig `json:"clientConfig,omitempty"` + + // conversionReviewVersions is an ordered list of preferred `ConversionReview` + // versions the Webhook expects. The API server will use the first version in + // the list which it supports. If none of the versions specified in this list + // are supported by API server, conversion will fail for the custom resource. + // If a persisted Webhook configuration specifies allowed versions and does not + // include any versions known to the API Server, calls to the webhook will fail. + // +listType=atomic + ConversionReviewVersions []string `json:"conversionReviewVersions"` +} + +// WebhookClientConfig contains the information to make a TLS connection with the webhook. +type WebhookClientConfig struct { + // url gives the location of the webhook, in standard URL form + // (`scheme://host:port/path`). + // + // Please note that using `localhost` or `127.0.0.1` as a `host` is + // risky unless you take great care to run this webhook on all hosts + // which run an apiserver which might need to make calls to this + // webhook. Such installs are likely to be non-portable, i.e., not easy + // to turn up in a new cluster. + // + // The scheme must be "https"; the URL must begin with "https://". + // + // A path is optional, and if present may be any string permissible in + // a URL. You may use the path to pass an arbitrary string to the + // webhook, for example, a cluster identifier. + // + // Attempting to use a user or basic auth e.g. "user:password@" is not + // allowed. Fragments ("#...") and query parameters ("?...") are not + // allowed, either. + // + // +kubebuilder:validation:Format=uri + URL *string `json:"url,omitempty"` + + // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // If unspecified, system trust roots on the apiserver are used. + // +optional + CABundle []byte `json:"caBundle,omitempty"` +} + +// ConversionStrategyType describes different conversion types. +type ConversionStrategyType string + // APIResourceVersion describes one API version of a resource. type APIResourceVersion struct { // name is the version name, e.g. “v1”, “v2beta1”, etc. @@ -133,7 +207,7 @@ type APIResourceVersion struct { // +required // +kubebuilder:pruning:PreserveUnknownFields // +structType=atomic - Schema CRDVersionSchema `json:"schema"` + Schema runtime.RawExtension `json:"schema"` // subresources specify what subresources this version of the defined custom resource have. // // +optional @@ -148,96 +222,77 @@ type APIResourceVersion struct { AdditionalPrinterColumns []apiextensionsv1.CustomResourceColumnDefinition `json:"additionalPrinterColumns,omitempty"` } -type CRDVersionSchema struct { - // openAPIV3Schema is the OpenAPI v3 schema to use for validation and pruning. - // - // +kubebuilder:pruning:PreserveUnknownFields - // +structType=atomic - // +required - // +kubebuilder:validation:Required - OpenAPIV3Schema runtime.RawExtension `json:"openAPIV3Schema"` -} +const ( + // BoundSchemaReady indicates that the API resource schema is ready. + // It is set to true when the API resource schema is accepted and there are no drifts detected. + BoundSchemaValid conditionsapi.ConditionType = "Valid" + // BoundSchemaDriftDetected indicates that there is a drift between the consumer's API and the expected API. + // It is set to true when the API resource schema is not accepted or there are drifts detected. + BoundSchemaInvalid conditionsapi.ConditionType = "Invalid" +) -// CustomResourceConversion describes how to convert different versions of a CR. -// +kubebuilder:validation:XValidation:message="Webhook must be specified if strategy=Webhook",rule="(self.strategy == 'None' && !has(self.webhook)) || (self.strategy == 'Webhook' && has(self.webhook))" -type CustomResourceConversion struct { - // strategy specifies how custom resources are converted between versions. Allowed values are: - // - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource. - // - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information - // is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set. - // +kubebuilder:validation:Enum=None;Webhook - Strategy ConversionStrategyType `json:"strategy"` +// BoundSchemaConditionReason is the set of reasons for specific condition type. +// +kubebuilder:validation:Enum=Accepted;Rejected;Pending;DriftDetected +type BoundSchemaConditionReason string - // webhook describes how to call the conversion webhook. Required when `strategy` is set to `"Webhook"`. - // +optional - Webhook *WebhookConversion `json:"webhook,omitempty"` +const ( + // BoundSchemaAccepted indicates that the API resource schema is accepted. + BoundSchemaAccepted BoundSchemaConditionReason = "Accepted" + // BoundSchemaRejected indicates that the API resource schema is rejected. + BoundSchemaRejected BoundSchemaConditionReason = "Rejected" + // BoundSchemaPending indicates that the API resource schema is pending. + BoundSchemaPending BoundSchemaConditionReason = "Pending" + // BoundSchemaDriftDetected indicates that there is a drift between the consumer's API and the expected API. + BoundSchemaDriftDetected BoundSchemaConditionReason = "DriftDetected" +) + +func (in *BoundSchema) GetConditions() conditionsapi.Conditions { + return in.Status.Conditions } -// ConversionStrategyType describes different conversion types. -type ConversionStrategyType string +func (in *BoundSchema) SetConditions(conditions conditionsapi.Conditions) { + in.Status.Conditions = conditions +} -// WebhookConversion describes how to call a conversion webhook -type WebhookConversion struct { - // clientConfig is the instructions for how to call the webhook if strategy is `Webhook`. +// BoundSchemaStatus defines the observed state of the BoundSchema. +type BoundSchemaStatus struct { + // acceptedNames are the names that are actually being used to serve discovery. + // They may be different than the names in spec. // +optional - ClientConfig *WebhookClientConfig `json:"clientConfig,omitempty"` - - // conversionReviewVersions is an ordered list of preferred `ConversionReview` - // versions the Webhook expects. The API server will use the first version in - // the list which it supports. If none of the versions specified in this list - // are supported by API server, conversion will fail for the custom resource. - // If a persisted Webhook configuration specifies allowed versions and does not - // include any versions known to the API Server, calls to the webhook will fail. - // +listType=atomic - ConversionReviewVersions []string `json:"conversionReviewVersions"` -} + AcceptedNames apiextensionsv1.CustomResourceDefinitionNames `json:"acceptedNames"` -// WebhookClientConfig contains the information to make a TLS connection with the webhook. -type WebhookClientConfig struct { - // url gives the location of the webhook, in standard URL form - // (`scheme://host:port/path`). - // - // Please note that using `localhost` or `127.0.0.1` as a `host` is - // risky unless you take great care to run this webhook on all hosts - // which run an apiserver which might need to make calls to this - // webhook. Such installs are likely to be non-portable, i.e., not easy - // to turn up in a new cluster. - // - // The scheme must be "https"; the URL must begin with "https://". - // - // A path is optional, and if present may be any string permissible in - // a URL. You may use the path to pass an arbitrary string to the - // webhook, for example, a cluster identifier. - // - // Attempting to use a user or basic auth e.g. "user:password@" is not - // allowed. Fragments ("#...") and query parameters ("?...") are not - // allowed, either. - // - // +kubebuilder:validation:Format=uri - URL *string `json:"url,omitempty"` + // storedVersions lists all versions of CustomResources that were ever persisted. Tracking these + // versions allows a migration path for stored versions in etcd. The field is mutable + // so a migration controller can finish a migration to another version (ensuring + // no old objects are left in storage), and then remove the rest of the + // versions from this list. + // Versions may not be removed from `spec.versions` while they exist in this list. + // +optional + StoredVersions []string `json:"storedVersions"` + // Conditions represent the latest available observations of the object's state. + // +optional + Conditions conditionsapi.Conditions `json:"conditions,omitempty"` - // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. - // If unspecified, system trust roots on the apiserver are used. + // Instantiations tracks the number of instances of the resource on the consumer side. // +optional - CABundle []byte `json:"caBundle,omitempty"` + Instantiations int `json:"instantiations,omitempty"` } -// APIResourceSchemaList is a list of APIResourceSchemas. -// +// BoundSchemaList is a list of BoundSchemas. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type APIResourceSchemaList struct { +type BoundSchemaList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata"` - Items []APIResourceSchema `json:"items"` + Items []BoundSchema `json:"items"` } func (v *APIResourceVersion) GetSchema() (*apiextensionsv1.JSONSchemaProps, error) { - if v.Schema.OpenAPIV3Schema.Raw == nil { + if v.Schema.Raw == nil { return nil, nil } var schema apiextensionsv1.JSONSchemaProps - if err := json.Unmarshal(v.Schema.OpenAPIV3Schema.Raw, &schema); err != nil { + if err := json.Unmarshal(v.Schema.Raw, &schema); err != nil { return nil, err } return &schema, nil @@ -245,13 +300,13 @@ func (v *APIResourceVersion) GetSchema() (*apiextensionsv1.JSONSchemaProps, erro func (v *APIResourceVersion) SetSchema(schema *apiextensionsv1.JSONSchemaProps) error { if schema == nil { - v.Schema.OpenAPIV3Schema.Raw = nil + v.Schema.Raw = nil return nil } raw, err := json.Marshal(schema) if err != nil { return err } - v.Schema.OpenAPIV3Schema.Raw = raw + v.Schema.Raw = raw return nil } diff --git a/sdk/apis/kubebind/v1alpha2/helpers/apiresourceschema.go b/sdk/apis/kubebind/v1alpha2/helpers/apiresourceschema.go deleted file mode 100644 index c66388cbc..000000000 --- a/sdk/apis/kubebind/v1alpha2/helpers/apiresourceschema.go +++ /dev/null @@ -1,188 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package helpers - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - "math/big" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - - kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" -) - -// CRDToAPIResourceSchema converts a CustomResourceDefinition to an APIResourceSchema. -func CRDToAPIResourceSchema(crd *apiextensionsv1.CustomResourceDefinition, prefix string) (*kubebindv1alpha2.APIResourceSchema, error) { - name := crd.Name - if prefix != "" { - name = prefix + "." + crd.Name - } - - informerScope := kubebindv1alpha2.NamespacedScope - apiResourceSchema := &kubebindv1alpha2.APIResourceSchema{ - TypeMeta: metav1.TypeMeta{ - APIVersion: kubebindv1alpha2.SchemeGroupVersion.String(), - Kind: "APIResourceSchema", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: kubebindv1alpha2.APIResourceSchemaSpec{ - InformerScope: informerScope, - APIResourceSchemaCRDSpec: kubebindv1alpha2.APIResourceSchemaCRDSpec{ - Group: crd.Spec.Group, - Names: crd.Spec.Names, - Scope: crd.Spec.Scope, - }, - }, - } - - if len(crd.Spec.Versions) > 1 && crd.Spec.Conversion == nil { - return nil, fmt.Errorf("multiple versions specified for CRD %q but no conversion strategy", crd.Name) - } - - if crd.Spec.Conversion != nil { - crConversion := &kubebindv1alpha2.CustomResourceConversion{ - Strategy: kubebindv1alpha2.ConversionStrategyType(crd.Spec.Conversion.Strategy), - } - - if crd.Spec.Conversion.Strategy == "Webhook" { - crConversion.Webhook = &kubebindv1alpha2.WebhookConversion{ - ConversionReviewVersions: crd.Spec.Conversion.Webhook.ConversionReviewVersions, - } - - if crd.Spec.Conversion.Webhook.ClientConfig != nil { - crConversion.Webhook.ClientConfig = &kubebindv1alpha2.WebhookClientConfig{ - URL: crd.Spec.Conversion.Webhook.ClientConfig.URL, - CABundle: crd.Spec.Conversion.Webhook.ClientConfig.CABundle, - } - } - } - - apiResourceSchema.Spec.Conversion = crConversion - } - - for _, crdVersion := range crd.Spec.Versions { - apiResourceVersion := kubebindv1alpha2.APIResourceVersion{ - Name: crdVersion.Name, - Served: crdVersion.Served, - Storage: crdVersion.Storage, - Deprecated: crdVersion.Deprecated, - DeprecationWarning: crdVersion.DeprecationWarning, - AdditionalPrinterColumns: crdVersion.AdditionalPrinterColumns, - } - if crdVersion.Schema != nil && crdVersion.Schema.OpenAPIV3Schema != nil { - rawSchema, err := json.Marshal(crdVersion.Schema.OpenAPIV3Schema) - if err != nil { - return nil, fmt.Errorf("error converting schema for version %q: %w", crdVersion.Name, err) - } - apiResourceVersion.Schema = kubebindv1alpha2.CRDVersionSchema{ - OpenAPIV3Schema: runtime.RawExtension{Raw: rawSchema}, - } - } - - if crdVersion.Subresources != nil { - apiResourceVersion.Subresources = *crdVersion.Subresources - } - - apiResourceSchema.Spec.Versions = append(apiResourceSchema.Spec.Versions, apiResourceVersion) - } - - return apiResourceSchema, nil -} - -// APIResourceSchemaToCRD converts an APIResourceSchema to a CustomResourceDefinition. -func APIResourceSchemaToCRD(schema *kubebindv1alpha2.APIResourceSchema) (*apiextensionsv1.CustomResourceDefinition, error) { - crd := &apiextensionsv1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: schema.Spec.Names.Plural + "." + schema.Spec.Group, - Annotations: map[string]string{ - "kube-bind.io/source-schema": schema.Name, - }, - }, - Spec: apiextensionsv1.CustomResourceDefinitionSpec{ - Group: schema.Spec.Group, - Names: schema.Spec.Names, - Scope: schema.Spec.Scope, - }, - } - - for _, schemaVersion := range schema.Spec.Versions { - crdVersion := apiextensionsv1.CustomResourceDefinitionVersion{ - Name: schemaVersion.Name, - Served: schemaVersion.Served, - Storage: schemaVersion.Storage, - Deprecated: schemaVersion.Deprecated, - DeprecationWarning: schemaVersion.DeprecationWarning, - AdditionalPrinterColumns: schemaVersion.AdditionalPrinterColumns, - } - - if schemaVersion.Schema.OpenAPIV3Schema.Raw != nil { - var schemaObj apiextensionsv1.JSONSchemaProps - if err := json.Unmarshal(schemaVersion.Schema.OpenAPIV3Schema.Raw, &schemaObj); err != nil { - return nil, fmt.Errorf("failed to unmarshal schema for version %s: %v", schemaVersion.Name, err) - } - crdVersion.Schema = &apiextensionsv1.CustomResourceValidation{ - OpenAPIV3Schema: &schemaObj, - } - } - - if schemaVersion.Subresources.Status != nil || schemaVersion.Subresources.Scale != nil { - crdVersion.Subresources = &apiextensionsv1.CustomResourceSubresources{} - - if schemaVersion.Subresources.Status != nil { - crdVersion.Subresources.Status = &apiextensionsv1.CustomResourceSubresourceStatus{} - } - - if schemaVersion.Subresources.Scale != nil { - crdVersion.Subresources.Scale = &apiextensionsv1.CustomResourceSubresourceScale{ - SpecReplicasPath: schemaVersion.Subresources.Scale.SpecReplicasPath, - StatusReplicasPath: schemaVersion.Subresources.Scale.StatusReplicasPath, - LabelSelectorPath: schemaVersion.Subresources.Scale.LabelSelectorPath, - } - } - } - - crd.Spec.Versions = append(crd.Spec.Versions, crdVersion) - } - - return crd, nil -} -func APIResourceSchemaCRDSpecHash(obj *kubebindv1alpha2.APIResourceSchemaCRDSpec) string { - bs, err := json.Marshal(obj) - if err != nil { - utilruntime.HandleError(err) - return "" - } - - return toSha224Base62(string(bs)) -} - -func toSha224Base62(s string) string { - return toBase62(sha256.Sum224([]byte(s))) -} - -func toBase62(hash [28]byte) string { - var i big.Int - i.SetBytes(hash[:]) - return i.Text(62) -} diff --git a/sdk/apis/kubebind/v1alpha2/helpers/apiresourceschema_test.go b/sdk/apis/kubebind/v1alpha2/helpers/apiresourceschema_test.go deleted file mode 100644 index e5722623a..000000000 --- a/sdk/apis/kubebind/v1alpha2/helpers/apiresourceschema_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package helpers - -import ( - "testing" - - "github.com/stretchr/testify/require" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/utils/ptr" -) - -func TestWebhookCRDStorageVersion(t *testing.T) { - input := v1.CustomResourceDefinition{ - Spec: v1.CustomResourceDefinitionSpec{ - Versions: []v1.CustomResourceDefinitionVersion{ - { - Served: true, - Name: "v1alpha2", - Storage: false, - }, - { - Served: true, - Name: "v1", - Storage: true, - }, - }, - Conversion: &v1.CustomResourceConversion{ - Strategy: v1.WebhookConverter, - Webhook: &v1.WebhookConversion{ - ClientConfig: &v1.WebhookClientConfig{ - URL: ptr.To("https://example.com/webhook"), - CABundle: []byte("1234")}, - }, - }, - }, - } - - output, err := CRDToAPIResourceSchema(&input, "test") - - require.NoError(t, err) - - atLeastOneStorageVersion := false - for _, v := range output.Spec.Versions { - atLeastOneStorageVersion = atLeastOneStorageVersion || v.Storage - } - if !atLeastOneStorageVersion { - t.Fatal("returned ResourceExport has no storage version", output) - } -} diff --git a/sdk/apis/kubebind/v1alpha2/helpers/boundapiresourceschema.go b/sdk/apis/kubebind/v1alpha2/helpers/boundapiresourceschema.go deleted file mode 100644 index 01d664722..000000000 --- a/sdk/apis/kubebind/v1alpha2/helpers/boundapiresourceschema.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2025 The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package helpers - -import ( - "encoding/json" - "fmt" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" -) - -// CRDToBoundAPIResourceSchema converts a CustomResourceDefinition to an BoundAPIResourceSchema. -func CRDToBoundAPIResourceSchema(crd *apiextensionsv1.CustomResourceDefinition, prefix string) (*kubebindv1alpha2.APIResourceSchema, error) { - name := prefix + "." + crd.Name - informerScope := kubebindv1alpha2.NamespacedScope - apiResourceSchema := &kubebindv1alpha2.APIResourceSchema{ - TypeMeta: metav1.TypeMeta{ - APIVersion: kubebindv1alpha2.SchemeGroupVersion.String(), - Kind: "APIResourceSchema", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: kubebindv1alpha2.APIResourceSchemaSpec{ - InformerScope: informerScope, - APIResourceSchemaCRDSpec: kubebindv1alpha2.APIResourceSchemaCRDSpec{ - Group: crd.Spec.Group, - Names: crd.Spec.Names, - Scope: crd.Spec.Scope, - }, - }, - } - - if len(crd.Spec.Versions) > 1 && crd.Spec.Conversion == nil { - return nil, fmt.Errorf("multiple versions specified for CRD %q but no conversion strategy", crd.Name) - } - - if crd.Spec.Conversion != nil { - crConversion := &kubebindv1alpha2.CustomResourceConversion{ - Strategy: kubebindv1alpha2.ConversionStrategyType(crd.Spec.Conversion.Strategy), - } - - if crd.Spec.Conversion.Strategy == "Webhook" { - crConversion.Webhook = &kubebindv1alpha2.WebhookConversion{ - ConversionReviewVersions: crd.Spec.Conversion.Webhook.ConversionReviewVersions, - } - - if crd.Spec.Conversion.Webhook.ClientConfig != nil { - crConversion.Webhook.ClientConfig = &kubebindv1alpha2.WebhookClientConfig{ - URL: crd.Spec.Conversion.Webhook.ClientConfig.URL, - CABundle: crd.Spec.Conversion.Webhook.ClientConfig.CABundle, - } - } - } - - apiResourceSchema.Spec.Conversion = crConversion - } - - for _, crdVersion := range crd.Spec.Versions { - apiResourceVersion := kubebindv1alpha2.APIResourceVersion{ - Name: crdVersion.Name, - Served: crdVersion.Served, - Storage: crdVersion.Storage, - Deprecated: crdVersion.Deprecated, - DeprecationWarning: crdVersion.DeprecationWarning, - AdditionalPrinterColumns: crdVersion.AdditionalPrinterColumns, - } - if crdVersion.Schema != nil && crdVersion.Schema.OpenAPIV3Schema != nil { - rawSchema, err := json.Marshal(crdVersion.Schema.OpenAPIV3Schema) - if err != nil { - return nil, fmt.Errorf("error converting schema for version %q: %w", crdVersion.Name, err) - } - apiResourceVersion.Schema = kubebindv1alpha2.CRDVersionSchema{ - OpenAPIV3Schema: runtime.RawExtension{Raw: rawSchema}, - } - } - - if crdVersion.Subresources != nil { - apiResourceVersion.Subresources = *crdVersion.Subresources - } - - apiResourceSchema.Spec.Versions = append(apiResourceSchema.Spec.Versions, apiResourceVersion) - } - - return apiResourceSchema, nil -} diff --git a/sdk/apis/kubebind/v1alpha2/helpers/boundschema.go b/sdk/apis/kubebind/v1alpha2/helpers/boundschema.go new file mode 100644 index 000000000..15ebaa7c8 --- /dev/null +++ b/sdk/apis/kubebind/v1alpha2/helpers/boundschema.go @@ -0,0 +1,200 @@ +/* +Copyright 2025 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" +) + +// CRDToBoundSchema converts a CustomResourceDefinition to an BoundSchema. +func CRDToBoundSchema(crd *apiextensionsv1.CustomResourceDefinition, prefix string) (*kubebindv1alpha2.BoundSchema, error) { + name := crd.Name + if prefix != "" { + name = prefix + "." + crd.Name + } + // Derive informer scope from the CRD scope. + informerScope := kubebindv1alpha2.InformerScope(crd.Spec.Scope) + boundSchema := &kubebindv1alpha2.BoundSchema{ + TypeMeta: metav1.TypeMeta{ + APIVersion: kubebindv1alpha2.SchemeGroupVersion.String(), + Kind: "BoundSchema", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: kubebindv1alpha2.BoundSchemaSpec{ + InformerScope: informerScope, + APICRDSpec: kubebindv1alpha2.APICRDSpec{ + Group: crd.Spec.Group, + Names: crd.Spec.Names, + Scope: crd.Spec.Scope, + }, + }, + } + + if len(crd.Spec.Versions) == 0 { + return nil, fmt.Errorf("no versions specified for CRD %q", crd.Name) + } + if len(crd.Spec.Versions) > 1 && crd.Spec.Conversion == nil { + return nil, fmt.Errorf("multiple versions specified for CRD %q but no conversion strategy", crd.Name) + } + + if crd.Spec.Conversion != nil { + crConversion := &kubebindv1alpha2.CustomResourceConversion{ + Strategy: kubebindv1alpha2.ConversionStrategyType(crd.Spec.Conversion.Strategy), + } + + if crd.Spec.Conversion.Strategy == "Webhook" { + crConversion.Webhook = &kubebindv1alpha2.WebhookConversion{ + ConversionReviewVersions: crd.Spec.Conversion.Webhook.ConversionReviewVersions, + } + + if crd.Spec.Conversion.Webhook.ClientConfig != nil { + crConversion.Webhook.ClientConfig = &kubebindv1alpha2.WebhookClientConfig{ + URL: crd.Spec.Conversion.Webhook.ClientConfig.URL, + CABundle: crd.Spec.Conversion.Webhook.ClientConfig.CABundle, + } + } + } + + boundSchema.Spec.Conversion = crConversion + } + + for _, crdVersion := range crd.Spec.Versions { + apiResourceVersion := kubebindv1alpha2.APIResourceVersion{ + Name: crdVersion.Name, + Served: crdVersion.Served, + Storage: crdVersion.Storage, + Deprecated: crdVersion.Deprecated, + DeprecationWarning: crdVersion.DeprecationWarning, + AdditionalPrinterColumns: crdVersion.AdditionalPrinterColumns, + } + if crdVersion.Schema != nil && crdVersion.Schema.OpenAPIV3Schema != nil { + rawSchema, err := json.Marshal(crdVersion.Schema.OpenAPIV3Schema) + if err != nil { + return nil, fmt.Errorf("error converting schema for version %q: %w", crdVersion.Name, err) + } + apiResourceVersion.Schema = runtime.RawExtension{Raw: rawSchema} + } + + if crdVersion.Subresources != nil { + apiResourceVersion.Subresources = *crdVersion.Subresources + } + + boundSchema.Spec.Versions = append(boundSchema.Spec.Versions, apiResourceVersion) + } + + return boundSchema, nil +} + +func UnstructuredToBoundSchema(u unstructured.Unstructured) (*kubebindv1alpha2.BoundSchema, error) { + boundSchema := &kubebindv1alpha2.BoundSchema{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), boundSchema); err != nil { + return nil, err + } + return boundSchema, nil +} + +func BoundSchemasSpecHash(schemas []*kubebindv1alpha2.BoundSchema) (string, error) { + hash := sha256.New() + for _, schema := range schemas { + if err := json.NewEncoder(hash).Encode(schema); err != nil { + return "", fmt.Errorf("failed to encode schema %s: %w", schema.Name, err) + } + } + return fmt.Sprintf("%x", hash.Sum(nil)), nil +} + +func BoundSchemaToCRD(schema *kubebindv1alpha2.BoundSchema) *apiextensionsv1.CustomResourceDefinition { + crd := &apiextensionsv1.CustomResourceDefinition{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apiextensions.k8s.io/v1", + Kind: "CustomResourceDefinition", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: schema.Spec.Names.Plural + "." + schema.Spec.Group, + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: schema.Spec.Group, + Names: schema.Spec.Names, + Scope: schema.Spec.Scope, + }, + } + + if schema.Spec.Conversion != nil { + crConversion := &apiextensionsv1.CustomResourceConversion{ + Strategy: apiextensionsv1.ConversionStrategyType(schema.Spec.Conversion.Strategy), + } + + if apiextensionsv1.ConversionStrategyType(schema.Spec.Conversion.Strategy) == apiextensionsv1.WebhookConverter && schema.Spec.Conversion.Webhook != nil { + crConversion.Webhook = &apiextensionsv1.WebhookConversion{ + ConversionReviewVersions: schema.Spec.Conversion.Webhook.ConversionReviewVersions, + } + + if schema.Spec.Conversion.Webhook.ClientConfig != nil { + crConversion.Webhook.ClientConfig = &apiextensionsv1.WebhookClientConfig{ + URL: schema.Spec.Conversion.Webhook.ClientConfig.URL, + CABundle: schema.Spec.Conversion.Webhook.ClientConfig.CABundle, + } + } + } + + crd.Spec.Conversion = crConversion + } + + for _, version := range schema.Spec.Versions { + crdVersion := apiextensionsv1.CustomResourceDefinitionVersion{ + Name: version.Name, + Served: version.Served, + Storage: version.Storage, + Deprecated: version.Deprecated, + DeprecationWarning: version.DeprecationWarning, + AdditionalPrinterColumns: version.AdditionalPrinterColumns, + Subresources: &version.Subresources, + } + // Now schema can be openapiv3 or v2. + // we do some poor man checking: + if len(version.Schema.Raw) > 0 { + // Try to unmarshal as CustomResourceValidation first (contains openAPIV3Schema field) + var validation apiextensionsv1.CustomResourceValidation + if err := json.Unmarshal(version.Schema.Raw, &validation); err == nil && validation.OpenAPIV3Schema != nil { + crdVersion.Schema = &validation + } else { + // Fall back to direct JSONSchemaProps + var jsonSchema apiextensionsv1.JSONSchemaProps + if err := json.Unmarshal(version.Schema.Raw, &jsonSchema); err == nil { + crdVersion.Schema = &apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &jsonSchema, + } + } + } + } + + crd.Spec.Versions = append(crd.Spec.Versions, crdVersion) + } + + return crd +} diff --git a/sdk/apis/kubebind/v1alpha2/register.go b/sdk/apis/kubebind/v1alpha2/register.go index 0504c3814..990552c55 100644 --- a/sdk/apis/kubebind/v1alpha2/register.go +++ b/sdk/apis/kubebind/v1alpha2/register.go @@ -46,10 +46,8 @@ func Resource(resource string) schema.GroupResource { // Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &APIResourceSchema{}, - &APIResourceSchemaList{}, - &BoundAPIResourceSchema{}, - &BoundAPIResourceSchemaList{}, + &BoundSchema{}, + &BoundSchemaList{}, &APIServiceBinding{}, &APIServiceBindingList{}, &APIServiceExport{}, diff --git a/sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go b/sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go index 4e9a67533..0e3600f5d 100644 --- a/sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go +++ b/sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go @@ -29,34 +29,7 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIResourceSchema) DeepCopyInto(out *APIResourceSchema) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceSchema. -func (in *APIResourceSchema) DeepCopy() *APIResourceSchema { - if in == nil { - return nil - } - out := new(APIResourceSchema) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *APIResourceSchema) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIResourceSchemaCRDSpec) DeepCopyInto(out *APIResourceSchemaCRDSpec) { +func (in *APICRDSpec) DeepCopyInto(out *APICRDSpec) { *out = *in in.Names.DeepCopyInto(&out.Names) if in.Versions != nil { @@ -74,78 +47,12 @@ func (in *APIResourceSchemaCRDSpec) DeepCopyInto(out *APIResourceSchemaCRDSpec) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceSchemaCRDSpec. -func (in *APIResourceSchemaCRDSpec) DeepCopy() *APIResourceSchemaCRDSpec { - if in == nil { - return nil - } - out := new(APIResourceSchemaCRDSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIResourceSchemaList) DeepCopyInto(out *APIResourceSchemaList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]APIResourceSchema, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceSchemaList. -func (in *APIResourceSchemaList) DeepCopy() *APIResourceSchemaList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APICRDSpec. +func (in *APICRDSpec) DeepCopy() *APICRDSpec { if in == nil { return nil } - out := new(APIResourceSchemaList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *APIResourceSchemaList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIResourceSchemaReference) DeepCopyInto(out *APIResourceSchemaReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceSchemaReference. -func (in *APIResourceSchemaReference) DeepCopy() *APIResourceSchemaReference { - if in == nil { - return nil - } - out := new(APIResourceSchemaReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIResourceSchemaSpec) DeepCopyInto(out *APIResourceSchemaSpec) { - *out = *in - in.APIResourceSchemaCRDSpec.DeepCopyInto(&out.APIResourceSchemaCRDSpec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIResourceSchemaSpec. -func (in *APIResourceSchemaSpec) DeepCopy() *APIResourceSchemaSpec { - if in == nil { - return nil - } - out := new(APIResourceSchemaSpec) + out := new(APICRDSpec) in.DeepCopyInto(out) return out } @@ -512,8 +419,10 @@ func (in *APIServiceExportSpec) DeepCopyInto(out *APIServiceExportSpec) { *out = *in if in.Resources != nil { in, out := &in.Resources, &out.Resources - *out = make([]APIResourceSchemaReference, len(*in)) - copy(*out, *in) + *out = make([]APIServiceExportRequestResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } return } @@ -779,7 +688,7 @@ func (in *BindingResponseAuthenticationOAuth2CodeGrant) DeepCopy() *BindingRespo } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BoundAPIResourceSchema) DeepCopyInto(out *BoundAPIResourceSchema) { +func (in *BoundSchema) DeepCopyInto(out *BoundSchema) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -788,18 +697,18 @@ func (in *BoundAPIResourceSchema) DeepCopyInto(out *BoundAPIResourceSchema) { return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundAPIResourceSchema. -func (in *BoundAPIResourceSchema) DeepCopy() *BoundAPIResourceSchema { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundSchema. +func (in *BoundSchema) DeepCopy() *BoundSchema { if in == nil { return nil } - out := new(BoundAPIResourceSchema) + out := new(BoundSchema) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *BoundAPIResourceSchema) DeepCopyObject() runtime.Object { +func (in *BoundSchema) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -807,13 +716,13 @@ func (in *BoundAPIResourceSchema) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BoundAPIResourceSchemaList) DeepCopyInto(out *BoundAPIResourceSchemaList) { +func (in *BoundSchemaList) DeepCopyInto(out *BoundSchemaList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]BoundAPIResourceSchema, len(*in)) + *out = make([]BoundSchema, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -821,18 +730,18 @@ func (in *BoundAPIResourceSchemaList) DeepCopyInto(out *BoundAPIResourceSchemaLi return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundAPIResourceSchemaList. -func (in *BoundAPIResourceSchemaList) DeepCopy() *BoundAPIResourceSchemaList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundSchemaList. +func (in *BoundSchemaList) DeepCopy() *BoundSchemaList { if in == nil { return nil } - out := new(BoundAPIResourceSchemaList) + out := new(BoundSchemaList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *BoundAPIResourceSchemaList) DeepCopyObject() runtime.Object { +func (in *BoundSchemaList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -840,81 +749,64 @@ func (in *BoundAPIResourceSchemaList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BoundAPIResourceSchemaSpec) DeepCopyInto(out *BoundAPIResourceSchemaSpec) { +func (in *BoundSchemaReference) DeepCopyInto(out *BoundSchemaReference) { *out = *in - in.APIResourceSchemaCRDSpec.DeepCopyInto(&out.APIResourceSchemaCRDSpec) + out.GroupResource = in.GroupResource return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundAPIResourceSchemaSpec. -func (in *BoundAPIResourceSchemaSpec) DeepCopy() *BoundAPIResourceSchemaSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundSchemaReference. +func (in *BoundSchemaReference) DeepCopy() *BoundSchemaReference { if in == nil { return nil } - out := new(BoundAPIResourceSchemaSpec) + out := new(BoundSchemaReference) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BoundAPIResourceSchemaStatus) DeepCopyInto(out *BoundAPIResourceSchemaStatus) { +func (in *BoundSchemaSpec) DeepCopyInto(out *BoundSchemaSpec) { *out = *in - in.AcceptedNames.DeepCopyInto(&out.AcceptedNames) - if in.StoredVersions != nil { - in, out := &in.StoredVersions, &out.StoredVersions - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1alpha1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } + in.APICRDSpec.DeepCopyInto(&out.APICRDSpec) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundAPIResourceSchemaStatus. -func (in *BoundAPIResourceSchemaStatus) DeepCopy() *BoundAPIResourceSchemaStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundSchemaSpec. +func (in *BoundSchemaSpec) DeepCopy() *BoundSchemaSpec { if in == nil { return nil } - out := new(BoundAPIResourceSchemaStatus) + out := new(BoundSchemaSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BoundSchemaReference) DeepCopyInto(out *BoundSchemaReference) { +func (in *BoundSchemaStatus) DeepCopyInto(out *BoundSchemaStatus) { *out = *in - out.GroupResource = in.GroupResource - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundSchemaReference. -func (in *BoundSchemaReference) DeepCopy() *BoundSchemaReference { - if in == nil { - return nil + in.AcceptedNames.DeepCopyInto(&out.AcceptedNames) + if in.StoredVersions != nil { + in, out := &in.StoredVersions, &out.StoredVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(v1alpha1.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } - out := new(BoundSchemaReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CRDVersionSchema) DeepCopyInto(out *CRDVersionSchema) { - *out = *in - in.OpenAPIV3Schema.DeepCopyInto(&out.OpenAPIV3Schema) return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CRDVersionSchema. -func (in *CRDVersionSchema) DeepCopy() *CRDVersionSchema { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BoundSchemaStatus. +func (in *BoundSchemaStatus) DeepCopy() *BoundSchemaStatus { if in == nil { return nil } - out := new(CRDVersionSchema) + out := new(BoundSchemaStatus) in.DeepCopyInto(out) return out } diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/boundapiresourceschema.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/boundapiresourceschema.go deleted file mode 100644 index 5781d3925..000000000 --- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/boundapiresourceschema.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - context "context" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - gentype "k8s.io/client-go/gentype" - - kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" - scheme "github.com/kube-bind/kube-bind/sdk/client/clientset/versioned/scheme" -) - -// BoundAPIResourceSchemasGetter has a method to return a BoundAPIResourceSchemaInterface. -// A group's client should implement this interface. -type BoundAPIResourceSchemasGetter interface { - BoundAPIResourceSchemas(namespace string) BoundAPIResourceSchemaInterface -} - -// BoundAPIResourceSchemaInterface has methods to work with BoundAPIResourceSchema resources. -type BoundAPIResourceSchemaInterface interface { - Create(ctx context.Context, boundAPIResourceSchema *kubebindv1alpha2.BoundAPIResourceSchema, opts v1.CreateOptions) (*kubebindv1alpha2.BoundAPIResourceSchema, error) - Update(ctx context.Context, boundAPIResourceSchema *kubebindv1alpha2.BoundAPIResourceSchema, opts v1.UpdateOptions) (*kubebindv1alpha2.BoundAPIResourceSchema, error) - // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, boundAPIResourceSchema *kubebindv1alpha2.BoundAPIResourceSchema, opts v1.UpdateOptions) (*kubebindv1alpha2.BoundAPIResourceSchema, error) - Delete(ctx context.Context, name string, opts v1.DeleteOptions) error - DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*kubebindv1alpha2.BoundAPIResourceSchema, error) - List(ctx context.Context, opts v1.ListOptions) (*kubebindv1alpha2.BoundAPIResourceSchemaList, error) - Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *kubebindv1alpha2.BoundAPIResourceSchema, err error) - BoundAPIResourceSchemaExpansion -} - -// boundAPIResourceSchemas implements BoundAPIResourceSchemaInterface -type boundAPIResourceSchemas struct { - *gentype.ClientWithList[*kubebindv1alpha2.BoundAPIResourceSchema, *kubebindv1alpha2.BoundAPIResourceSchemaList] -} - -// newBoundAPIResourceSchemas returns a BoundAPIResourceSchemas -func newBoundAPIResourceSchemas(c *KubeBindV1alpha2Client, namespace string) *boundAPIResourceSchemas { - return &boundAPIResourceSchemas{ - gentype.NewClientWithList[*kubebindv1alpha2.BoundAPIResourceSchema, *kubebindv1alpha2.BoundAPIResourceSchemaList]( - "boundapiresourceschemas", - c.RESTClient(), - scheme.ParameterCodec, - namespace, - func() *kubebindv1alpha2.BoundAPIResourceSchema { return &kubebindv1alpha2.BoundAPIResourceSchema{} }, - func() *kubebindv1alpha2.BoundAPIResourceSchemaList { - return &kubebindv1alpha2.BoundAPIResourceSchemaList{} - }, - ), - } -} diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/apiresourceschema.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/boundschema.go similarity index 51% rename from sdk/client/clientset/versioned/typed/kubebind/v1alpha2/apiresourceschema.go rename to sdk/client/clientset/versioned/typed/kubebind/v1alpha2/boundschema.go index d7d6a01ab..2e73da6e4 100644 --- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/apiresourceschema.go +++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/boundschema.go @@ -30,40 +30,42 @@ import ( scheme "github.com/kube-bind/kube-bind/sdk/client/clientset/versioned/scheme" ) -// APIResourceSchemasGetter has a method to return a APIResourceSchemaInterface. +// BoundSchemasGetter has a method to return a BoundSchemaInterface. // A group's client should implement this interface. -type APIResourceSchemasGetter interface { - APIResourceSchemas() APIResourceSchemaInterface +type BoundSchemasGetter interface { + BoundSchemas(namespace string) BoundSchemaInterface } -// APIResourceSchemaInterface has methods to work with APIResourceSchema resources. -type APIResourceSchemaInterface interface { - Create(ctx context.Context, aPIResourceSchema *kubebindv1alpha2.APIResourceSchema, opts v1.CreateOptions) (*kubebindv1alpha2.APIResourceSchema, error) - Update(ctx context.Context, aPIResourceSchema *kubebindv1alpha2.APIResourceSchema, opts v1.UpdateOptions) (*kubebindv1alpha2.APIResourceSchema, error) +// BoundSchemaInterface has methods to work with BoundSchema resources. +type BoundSchemaInterface interface { + Create(ctx context.Context, boundSchema *kubebindv1alpha2.BoundSchema, opts v1.CreateOptions) (*kubebindv1alpha2.BoundSchema, error) + Update(ctx context.Context, boundSchema *kubebindv1alpha2.BoundSchema, opts v1.UpdateOptions) (*kubebindv1alpha2.BoundSchema, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, boundSchema *kubebindv1alpha2.BoundSchema, opts v1.UpdateOptions) (*kubebindv1alpha2.BoundSchema, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*kubebindv1alpha2.APIResourceSchema, error) - List(ctx context.Context, opts v1.ListOptions) (*kubebindv1alpha2.APIResourceSchemaList, error) + Get(ctx context.Context, name string, opts v1.GetOptions) (*kubebindv1alpha2.BoundSchema, error) + List(ctx context.Context, opts v1.ListOptions) (*kubebindv1alpha2.BoundSchemaList, error) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *kubebindv1alpha2.APIResourceSchema, err error) - APIResourceSchemaExpansion + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *kubebindv1alpha2.BoundSchema, err error) + BoundSchemaExpansion } -// aPIResourceSchemas implements APIResourceSchemaInterface -type aPIResourceSchemas struct { - *gentype.ClientWithList[*kubebindv1alpha2.APIResourceSchema, *kubebindv1alpha2.APIResourceSchemaList] +// boundSchemas implements BoundSchemaInterface +type boundSchemas struct { + *gentype.ClientWithList[*kubebindv1alpha2.BoundSchema, *kubebindv1alpha2.BoundSchemaList] } -// newAPIResourceSchemas returns a APIResourceSchemas -func newAPIResourceSchemas(c *KubeBindV1alpha2Client) *aPIResourceSchemas { - return &aPIResourceSchemas{ - gentype.NewClientWithList[*kubebindv1alpha2.APIResourceSchema, *kubebindv1alpha2.APIResourceSchemaList]( - "apiresourceschemas", +// newBoundSchemas returns a BoundSchemas +func newBoundSchemas(c *KubeBindV1alpha2Client, namespace string) *boundSchemas { + return &boundSchemas{ + gentype.NewClientWithList[*kubebindv1alpha2.BoundSchema, *kubebindv1alpha2.BoundSchemaList]( + "boundschemas", c.RESTClient(), scheme.ParameterCodec, - "", - func() *kubebindv1alpha2.APIResourceSchema { return &kubebindv1alpha2.APIResourceSchema{} }, - func() *kubebindv1alpha2.APIResourceSchemaList { return &kubebindv1alpha2.APIResourceSchemaList{} }, + namespace, + func() *kubebindv1alpha2.BoundSchema { return &kubebindv1alpha2.BoundSchema{} }, + func() *kubebindv1alpha2.BoundSchemaList { return &kubebindv1alpha2.BoundSchemaList{} }, ), } } diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_boundapiresourceschema.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_boundapiresourceschema.go deleted file mode 100644 index d91d8c8a6..000000000 --- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_boundapiresourceschema.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - gentype "k8s.io/client-go/gentype" - - v1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" - kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/client/clientset/versioned/typed/kubebind/v1alpha2" -) - -// fakeBoundAPIResourceSchemas implements BoundAPIResourceSchemaInterface -type fakeBoundAPIResourceSchemas struct { - *gentype.FakeClientWithList[*v1alpha2.BoundAPIResourceSchema, *v1alpha2.BoundAPIResourceSchemaList] - Fake *FakeKubeBindV1alpha2 -} - -func newFakeBoundAPIResourceSchemas(fake *FakeKubeBindV1alpha2, namespace string) kubebindv1alpha2.BoundAPIResourceSchemaInterface { - return &fakeBoundAPIResourceSchemas{ - gentype.NewFakeClientWithList[*v1alpha2.BoundAPIResourceSchema, *v1alpha2.BoundAPIResourceSchemaList]( - fake.Fake, - namespace, - v1alpha2.SchemeGroupVersion.WithResource("boundapiresourceschemas"), - v1alpha2.SchemeGroupVersion.WithKind("BoundAPIResourceSchema"), - func() *v1alpha2.BoundAPIResourceSchema { return &v1alpha2.BoundAPIResourceSchema{} }, - func() *v1alpha2.BoundAPIResourceSchemaList { return &v1alpha2.BoundAPIResourceSchemaList{} }, - func(dst, src *v1alpha2.BoundAPIResourceSchemaList) { dst.ListMeta = src.ListMeta }, - func(list *v1alpha2.BoundAPIResourceSchemaList) []*v1alpha2.BoundAPIResourceSchema { - return gentype.ToPointerSlice(list.Items) - }, - func(list *v1alpha2.BoundAPIResourceSchemaList, items []*v1alpha2.BoundAPIResourceSchema) { - list.Items = gentype.FromPointerSlice(items) - }, - ), - fake, - } -} diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_apiresourceschema.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_boundschema.go similarity index 51% rename from sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_apiresourceschema.go rename to sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_boundschema.go index 684c48354..19b73ab41 100644 --- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_apiresourceschema.go +++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_boundschema.go @@ -25,26 +25,26 @@ import ( kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/client/clientset/versioned/typed/kubebind/v1alpha2" ) -// fakeAPIResourceSchemas implements APIResourceSchemaInterface -type fakeAPIResourceSchemas struct { - *gentype.FakeClientWithList[*v1alpha2.APIResourceSchema, *v1alpha2.APIResourceSchemaList] +// fakeBoundSchemas implements BoundSchemaInterface +type fakeBoundSchemas struct { + *gentype.FakeClientWithList[*v1alpha2.BoundSchema, *v1alpha2.BoundSchemaList] Fake *FakeKubeBindV1alpha2 } -func newFakeAPIResourceSchemas(fake *FakeKubeBindV1alpha2) kubebindv1alpha2.APIResourceSchemaInterface { - return &fakeAPIResourceSchemas{ - gentype.NewFakeClientWithList[*v1alpha2.APIResourceSchema, *v1alpha2.APIResourceSchemaList]( +func newFakeBoundSchemas(fake *FakeKubeBindV1alpha2, namespace string) kubebindv1alpha2.BoundSchemaInterface { + return &fakeBoundSchemas{ + gentype.NewFakeClientWithList[*v1alpha2.BoundSchema, *v1alpha2.BoundSchemaList]( fake.Fake, - "", - v1alpha2.SchemeGroupVersion.WithResource("apiresourceschemas"), - v1alpha2.SchemeGroupVersion.WithKind("APIResourceSchema"), - func() *v1alpha2.APIResourceSchema { return &v1alpha2.APIResourceSchema{} }, - func() *v1alpha2.APIResourceSchemaList { return &v1alpha2.APIResourceSchemaList{} }, - func(dst, src *v1alpha2.APIResourceSchemaList) { dst.ListMeta = src.ListMeta }, - func(list *v1alpha2.APIResourceSchemaList) []*v1alpha2.APIResourceSchema { + namespace, + v1alpha2.SchemeGroupVersion.WithResource("boundschemas"), + v1alpha2.SchemeGroupVersion.WithKind("BoundSchema"), + func() *v1alpha2.BoundSchema { return &v1alpha2.BoundSchema{} }, + func() *v1alpha2.BoundSchemaList { return &v1alpha2.BoundSchemaList{} }, + func(dst, src *v1alpha2.BoundSchemaList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha2.BoundSchemaList) []*v1alpha2.BoundSchema { return gentype.ToPointerSlice(list.Items) }, - func(list *v1alpha2.APIResourceSchemaList, items []*v1alpha2.APIResourceSchema) { + func(list *v1alpha2.BoundSchemaList, items []*v1alpha2.BoundSchema) { list.Items = gentype.FromPointerSlice(items) }, ), diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_kubebind_client.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_kubebind_client.go index 17853649a..3a48e9c60 100644 --- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_kubebind_client.go +++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_kubebind_client.go @@ -29,10 +29,6 @@ type FakeKubeBindV1alpha2 struct { *testing.Fake } -func (c *FakeKubeBindV1alpha2) APIResourceSchemas() v1alpha2.APIResourceSchemaInterface { - return newFakeAPIResourceSchemas(c) -} - func (c *FakeKubeBindV1alpha2) APIServiceBindings() v1alpha2.APIServiceBindingInterface { return newFakeAPIServiceBindings(c) } @@ -49,8 +45,8 @@ func (c *FakeKubeBindV1alpha2) APIServiceNamespaces(namespace string) v1alpha2.A return newFakeAPIServiceNamespaces(c, namespace) } -func (c *FakeKubeBindV1alpha2) BoundAPIResourceSchemas(namespace string) v1alpha2.BoundAPIResourceSchemaInterface { - return newFakeBoundAPIResourceSchemas(c, namespace) +func (c *FakeKubeBindV1alpha2) BoundSchemas(namespace string) v1alpha2.BoundSchemaInterface { + return newFakeBoundSchemas(c, namespace) } func (c *FakeKubeBindV1alpha2) ClusterBindings(namespace string) v1alpha2.ClusterBindingInterface { diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/generated_expansion.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/generated_expansion.go index 5c2fd1ed5..d0968607d 100644 --- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/generated_expansion.go +++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/generated_expansion.go @@ -18,8 +18,6 @@ limitations under the License. package v1alpha2 -type APIResourceSchemaExpansion interface{} - type APIServiceBindingExpansion interface{} type APIServiceExportExpansion interface{} @@ -28,6 +26,6 @@ type APIServiceExportRequestExpansion interface{} type APIServiceNamespaceExpansion interface{} -type BoundAPIResourceSchemaExpansion interface{} +type BoundSchemaExpansion interface{} type ClusterBindingExpansion interface{} diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/kubebind_client.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/kubebind_client.go index 72842b593..f215f2faa 100644 --- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/kubebind_client.go +++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/kubebind_client.go @@ -29,12 +29,11 @@ import ( type KubeBindV1alpha2Interface interface { RESTClient() rest.Interface - APIResourceSchemasGetter APIServiceBindingsGetter APIServiceExportsGetter APIServiceExportRequestsGetter APIServiceNamespacesGetter - BoundAPIResourceSchemasGetter + BoundSchemasGetter ClusterBindingsGetter } @@ -43,10 +42,6 @@ type KubeBindV1alpha2Client struct { restClient rest.Interface } -func (c *KubeBindV1alpha2Client) APIResourceSchemas() APIResourceSchemaInterface { - return newAPIResourceSchemas(c) -} - func (c *KubeBindV1alpha2Client) APIServiceBindings() APIServiceBindingInterface { return newAPIServiceBindings(c) } @@ -63,8 +58,8 @@ func (c *KubeBindV1alpha2Client) APIServiceNamespaces(namespace string) APIServi return newAPIServiceNamespaces(c, namespace) } -func (c *KubeBindV1alpha2Client) BoundAPIResourceSchemas(namespace string) BoundAPIResourceSchemaInterface { - return newBoundAPIResourceSchemas(c, namespace) +func (c *KubeBindV1alpha2Client) BoundSchemas(namespace string) BoundSchemaInterface { + return newBoundSchemas(c, namespace) } func (c *KubeBindV1alpha2Client) ClusterBindings(namespace string) ClusterBindingInterface { diff --git a/sdk/client/informers/externalversions/generic.go b/sdk/client/informers/externalversions/generic.go index ec0f17da2..eb77273d5 100644 --- a/sdk/client/informers/externalversions/generic.go +++ b/sdk/client/informers/externalversions/generic.go @@ -67,8 +67,6 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().ClusterBindings().Informer()}, nil // Group=kube-bind.io, Version=v1alpha2 - case v1alpha2.SchemeGroupVersion.WithResource("apiresourceschemas"): - return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha2().APIResourceSchemas().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("apiservicebindings"): return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha2().APIServiceBindings().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("apiserviceexports"): @@ -77,8 +75,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha2().APIServiceExportRequests().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("apiservicenamespaces"): return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha2().APIServiceNamespaces().Informer()}, nil - case v1alpha2.SchemeGroupVersion.WithResource("boundapiresourceschemas"): - return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha2().BoundAPIResourceSchemas().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("boundschemas"): + return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha2().BoundSchemas().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("clusterbindings"): return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha2().ClusterBindings().Informer()}, nil diff --git a/sdk/client/informers/externalversions/kubebind/v1alpha2/boundapiresourceschema.go b/sdk/client/informers/externalversions/kubebind/v1alpha2/boundapiresourceschema.go deleted file mode 100644 index 4a0a451f6..000000000 --- a/sdk/client/informers/externalversions/kubebind/v1alpha2/boundapiresourceschema.go +++ /dev/null @@ -1,103 +0,0 @@ -/* -Copyright The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by informer-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - context "context" - time "time" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - cache "k8s.io/client-go/tools/cache" - - apiskubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" - versioned "github.com/kube-bind/kube-bind/sdk/client/clientset/versioned" - internalinterfaces "github.com/kube-bind/kube-bind/sdk/client/informers/externalversions/internalinterfaces" - kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/client/listers/kubebind/v1alpha2" -) - -// BoundAPIResourceSchemaInformer provides access to a shared informer and lister for -// BoundAPIResourceSchemas. -type BoundAPIResourceSchemaInformer interface { - Informer() cache.SharedIndexInformer - Lister() kubebindv1alpha2.BoundAPIResourceSchemaLister -} - -type boundAPIResourceSchemaInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc - namespace string -} - -// NewBoundAPIResourceSchemaInformer constructs a new informer for BoundAPIResourceSchema type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewBoundAPIResourceSchemaInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredBoundAPIResourceSchemaInformer(client, namespace, resyncPeriod, indexers, nil) -} - -// NewFilteredBoundAPIResourceSchemaInformer constructs a new informer for BoundAPIResourceSchema type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredBoundAPIResourceSchemaInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - &cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.KubeBindV1alpha2().BoundAPIResourceSchemas(namespace).List(context.Background(), options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.KubeBindV1alpha2().BoundAPIResourceSchemas(namespace).Watch(context.Background(), options) - }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.KubeBindV1alpha2().BoundAPIResourceSchemas(namespace).List(ctx, options) - }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.KubeBindV1alpha2().BoundAPIResourceSchemas(namespace).Watch(ctx, options) - }, - }, - &apiskubebindv1alpha2.BoundAPIResourceSchema{}, - resyncPeriod, - indexers, - ) -} - -func (f *boundAPIResourceSchemaInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredBoundAPIResourceSchemaInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *boundAPIResourceSchemaInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apiskubebindv1alpha2.BoundAPIResourceSchema{}, f.defaultInformer) -} - -func (f *boundAPIResourceSchemaInformer) Lister() kubebindv1alpha2.BoundAPIResourceSchemaLister { - return kubebindv1alpha2.NewBoundAPIResourceSchemaLister(f.Informer().GetIndexer()) -} diff --git a/sdk/client/informers/externalversions/kubebind/v1alpha2/apiresourceschema.go b/sdk/client/informers/externalversions/kubebind/v1alpha2/boundschema.go similarity index 57% rename from sdk/client/informers/externalversions/kubebind/v1alpha2/apiresourceschema.go rename to sdk/client/informers/externalversions/kubebind/v1alpha2/boundschema.go index 06ecc7db0..d56d2b0dd 100644 --- a/sdk/client/informers/externalversions/kubebind/v1alpha2/apiresourceschema.go +++ b/sdk/client/informers/externalversions/kubebind/v1alpha2/boundschema.go @@ -33,70 +33,71 @@ import ( kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/client/listers/kubebind/v1alpha2" ) -// APIResourceSchemaInformer provides access to a shared informer and lister for -// APIResourceSchemas. -type APIResourceSchemaInformer interface { +// BoundSchemaInformer provides access to a shared informer and lister for +// BoundSchemas. +type BoundSchemaInformer interface { Informer() cache.SharedIndexInformer - Lister() kubebindv1alpha2.APIResourceSchemaLister + Lister() kubebindv1alpha2.BoundSchemaLister } -type aPIResourceSchemaInformer struct { +type boundSchemaInformer struct { factory internalinterfaces.SharedInformerFactory tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string } -// NewAPIResourceSchemaInformer constructs a new informer for APIResourceSchema type. +// NewBoundSchemaInformer constructs a new informer for BoundSchema type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. -func NewAPIResourceSchemaInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredAPIResourceSchemaInformer(client, resyncPeriod, indexers, nil) +func NewBoundSchemaInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredBoundSchemaInformer(client, namespace, resyncPeriod, indexers, nil) } -// NewFilteredAPIResourceSchemaInformer constructs a new informer for APIResourceSchema type. +// NewFilteredBoundSchemaInformer constructs a new informer for BoundSchema type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. -func NewFilteredAPIResourceSchemaInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { +func NewFilteredBoundSchemaInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.KubeBindV1alpha2().APIResourceSchemas().List(context.Background(), options) + return client.KubeBindV1alpha2().BoundSchemas(namespace).List(context.Background(), options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.KubeBindV1alpha2().APIResourceSchemas().Watch(context.Background(), options) + return client.KubeBindV1alpha2().BoundSchemas(namespace).Watch(context.Background(), options) }, ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.KubeBindV1alpha2().APIResourceSchemas().List(ctx, options) + return client.KubeBindV1alpha2().BoundSchemas(namespace).List(ctx, options) }, WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.KubeBindV1alpha2().APIResourceSchemas().Watch(ctx, options) + return client.KubeBindV1alpha2().BoundSchemas(namespace).Watch(ctx, options) }, }, - &apiskubebindv1alpha2.APIResourceSchema{}, + &apiskubebindv1alpha2.BoundSchema{}, resyncPeriod, indexers, ) } -func (f *aPIResourceSchemaInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredAPIResourceSchemaInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +func (f *boundSchemaInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredBoundSchemaInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) } -func (f *aPIResourceSchemaInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apiskubebindv1alpha2.APIResourceSchema{}, f.defaultInformer) +func (f *boundSchemaInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiskubebindv1alpha2.BoundSchema{}, f.defaultInformer) } -func (f *aPIResourceSchemaInformer) Lister() kubebindv1alpha2.APIResourceSchemaLister { - return kubebindv1alpha2.NewAPIResourceSchemaLister(f.Informer().GetIndexer()) +func (f *boundSchemaInformer) Lister() kubebindv1alpha2.BoundSchemaLister { + return kubebindv1alpha2.NewBoundSchemaLister(f.Informer().GetIndexer()) } diff --git a/sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go b/sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go index 68bf0f176..e4abc8bb5 100644 --- a/sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go +++ b/sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go @@ -24,8 +24,6 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { - // APIResourceSchemas returns a APIResourceSchemaInformer. - APIResourceSchemas() APIResourceSchemaInformer // APIServiceBindings returns a APIServiceBindingInformer. APIServiceBindings() APIServiceBindingInformer // APIServiceExports returns a APIServiceExportInformer. @@ -34,8 +32,8 @@ type Interface interface { APIServiceExportRequests() APIServiceExportRequestInformer // APIServiceNamespaces returns a APIServiceNamespaceInformer. APIServiceNamespaces() APIServiceNamespaceInformer - // BoundAPIResourceSchemas returns a BoundAPIResourceSchemaInformer. - BoundAPIResourceSchemas() BoundAPIResourceSchemaInformer + // BoundSchemas returns a BoundSchemaInformer. + BoundSchemas() BoundSchemaInformer // ClusterBindings returns a ClusterBindingInformer. ClusterBindings() ClusterBindingInformer } @@ -51,11 +49,6 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } -// APIResourceSchemas returns a APIResourceSchemaInformer. -func (v *version) APIResourceSchemas() APIResourceSchemaInformer { - return &aPIResourceSchemaInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} -} - // APIServiceBindings returns a APIServiceBindingInformer. func (v *version) APIServiceBindings() APIServiceBindingInformer { return &aPIServiceBindingInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} @@ -76,9 +69,9 @@ func (v *version) APIServiceNamespaces() APIServiceNamespaceInformer { return &aPIServiceNamespaceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } -// BoundAPIResourceSchemas returns a BoundAPIResourceSchemaInformer. -func (v *version) BoundAPIResourceSchemas() BoundAPIResourceSchemaInformer { - return &boundAPIResourceSchemaInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +// BoundSchemas returns a BoundSchemaInformer. +func (v *version) BoundSchemas() BoundSchemaInformer { + return &boundSchemaInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } // ClusterBindings returns a ClusterBindingInformer. diff --git a/sdk/client/listers/kubebind/v1alpha2/apiresourceschema.go b/sdk/client/listers/kubebind/v1alpha2/apiresourceschema.go deleted file mode 100644 index 06d40f0d4..000000000 --- a/sdk/client/listers/kubebind/v1alpha2/apiresourceschema.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - labels "k8s.io/apimachinery/pkg/labels" - listers "k8s.io/client-go/listers" - cache "k8s.io/client-go/tools/cache" - - kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" -) - -// APIResourceSchemaLister helps list APIResourceSchemas. -// All objects returned here must be treated as read-only. -type APIResourceSchemaLister interface { - // List lists all APIResourceSchemas in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*kubebindv1alpha2.APIResourceSchema, err error) - // Get retrieves the APIResourceSchema from the index for a given name. - // Objects returned here must be treated as read-only. - Get(name string) (*kubebindv1alpha2.APIResourceSchema, error) - APIResourceSchemaListerExpansion -} - -// aPIResourceSchemaLister implements the APIResourceSchemaLister interface. -type aPIResourceSchemaLister struct { - listers.ResourceIndexer[*kubebindv1alpha2.APIResourceSchema] -} - -// NewAPIResourceSchemaLister returns a new APIResourceSchemaLister. -func NewAPIResourceSchemaLister(indexer cache.Indexer) APIResourceSchemaLister { - return &aPIResourceSchemaLister{listers.New[*kubebindv1alpha2.APIResourceSchema](indexer, kubebindv1alpha2.Resource("apiresourceschema"))} -} diff --git a/sdk/client/listers/kubebind/v1alpha2/boundapiresourceschema.go b/sdk/client/listers/kubebind/v1alpha2/boundapiresourceschema.go deleted file mode 100644 index 595e1536f..000000000 --- a/sdk/client/listers/kubebind/v1alpha2/boundapiresourceschema.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright The Kube Bind Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by lister-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - labels "k8s.io/apimachinery/pkg/labels" - listers "k8s.io/client-go/listers" - cache "k8s.io/client-go/tools/cache" - - kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" -) - -// BoundAPIResourceSchemaLister helps list BoundAPIResourceSchemas. -// All objects returned here must be treated as read-only. -type BoundAPIResourceSchemaLister interface { - // List lists all BoundAPIResourceSchemas in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*kubebindv1alpha2.BoundAPIResourceSchema, err error) - // BoundAPIResourceSchemas returns an object that can list and get BoundAPIResourceSchemas. - BoundAPIResourceSchemas(namespace string) BoundAPIResourceSchemaNamespaceLister - BoundAPIResourceSchemaListerExpansion -} - -// boundAPIResourceSchemaLister implements the BoundAPIResourceSchemaLister interface. -type boundAPIResourceSchemaLister struct { - listers.ResourceIndexer[*kubebindv1alpha2.BoundAPIResourceSchema] -} - -// NewBoundAPIResourceSchemaLister returns a new BoundAPIResourceSchemaLister. -func NewBoundAPIResourceSchemaLister(indexer cache.Indexer) BoundAPIResourceSchemaLister { - return &boundAPIResourceSchemaLister{listers.New[*kubebindv1alpha2.BoundAPIResourceSchema](indexer, kubebindv1alpha2.Resource("boundapiresourceschema"))} -} - -// BoundAPIResourceSchemas returns an object that can list and get BoundAPIResourceSchemas. -func (s *boundAPIResourceSchemaLister) BoundAPIResourceSchemas(namespace string) BoundAPIResourceSchemaNamespaceLister { - return boundAPIResourceSchemaNamespaceLister{listers.NewNamespaced[*kubebindv1alpha2.BoundAPIResourceSchema](s.ResourceIndexer, namespace)} -} - -// BoundAPIResourceSchemaNamespaceLister helps list and get BoundAPIResourceSchemas. -// All objects returned here must be treated as read-only. -type BoundAPIResourceSchemaNamespaceLister interface { - // List lists all BoundAPIResourceSchemas in the indexer for a given namespace. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*kubebindv1alpha2.BoundAPIResourceSchema, err error) - // Get retrieves the BoundAPIResourceSchema from the indexer for a given namespace and name. - // Objects returned here must be treated as read-only. - Get(name string) (*kubebindv1alpha2.BoundAPIResourceSchema, error) - BoundAPIResourceSchemaNamespaceListerExpansion -} - -// boundAPIResourceSchemaNamespaceLister implements the BoundAPIResourceSchemaNamespaceLister -// interface. -type boundAPIResourceSchemaNamespaceLister struct { - listers.ResourceIndexer[*kubebindv1alpha2.BoundAPIResourceSchema] -} diff --git a/sdk/client/listers/kubebind/v1alpha2/boundschema.go b/sdk/client/listers/kubebind/v1alpha2/boundschema.go new file mode 100644 index 000000000..819029d4d --- /dev/null +++ b/sdk/client/listers/kubebind/v1alpha2/boundschema.go @@ -0,0 +1,71 @@ +/* +Copyright The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" + + kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2" +) + +// BoundSchemaLister helps list BoundSchemas. +// All objects returned here must be treated as read-only. +type BoundSchemaLister interface { + // List lists all BoundSchemas in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*kubebindv1alpha2.BoundSchema, err error) + // BoundSchemas returns an object that can list and get BoundSchemas. + BoundSchemas(namespace string) BoundSchemaNamespaceLister + BoundSchemaListerExpansion +} + +// boundSchemaLister implements the BoundSchemaLister interface. +type boundSchemaLister struct { + listers.ResourceIndexer[*kubebindv1alpha2.BoundSchema] +} + +// NewBoundSchemaLister returns a new BoundSchemaLister. +func NewBoundSchemaLister(indexer cache.Indexer) BoundSchemaLister { + return &boundSchemaLister{listers.New[*kubebindv1alpha2.BoundSchema](indexer, kubebindv1alpha2.Resource("boundschema"))} +} + +// BoundSchemas returns an object that can list and get BoundSchemas. +func (s *boundSchemaLister) BoundSchemas(namespace string) BoundSchemaNamespaceLister { + return boundSchemaNamespaceLister{listers.NewNamespaced[*kubebindv1alpha2.BoundSchema](s.ResourceIndexer, namespace)} +} + +// BoundSchemaNamespaceLister helps list and get BoundSchemas. +// All objects returned here must be treated as read-only. +type BoundSchemaNamespaceLister interface { + // List lists all BoundSchemas in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*kubebindv1alpha2.BoundSchema, err error) + // Get retrieves the BoundSchema from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*kubebindv1alpha2.BoundSchema, error) + BoundSchemaNamespaceListerExpansion +} + +// boundSchemaNamespaceLister implements the BoundSchemaNamespaceLister +// interface. +type boundSchemaNamespaceLister struct { + listers.ResourceIndexer[*kubebindv1alpha2.BoundSchema] +} diff --git a/sdk/client/listers/kubebind/v1alpha2/expansion_generated.go b/sdk/client/listers/kubebind/v1alpha2/expansion_generated.go index a8959ba58..3aaa58aa1 100644 --- a/sdk/client/listers/kubebind/v1alpha2/expansion_generated.go +++ b/sdk/client/listers/kubebind/v1alpha2/expansion_generated.go @@ -18,10 +18,6 @@ limitations under the License. package v1alpha2 -// APIResourceSchemaListerExpansion allows custom methods to be added to -// APIResourceSchemaLister. -type APIResourceSchemaListerExpansion interface{} - // APIServiceBindingListerExpansion allows custom methods to be added to // APIServiceBindingLister. type APIServiceBindingListerExpansion interface{} @@ -50,13 +46,13 @@ type APIServiceNamespaceListerExpansion interface{} // APIServiceNamespaceNamespaceLister. type APIServiceNamespaceNamespaceListerExpansion interface{} -// BoundAPIResourceSchemaListerExpansion allows custom methods to be added to -// BoundAPIResourceSchemaLister. -type BoundAPIResourceSchemaListerExpansion interface{} +// BoundSchemaListerExpansion allows custom methods to be added to +// BoundSchemaLister. +type BoundSchemaListerExpansion interface{} -// BoundAPIResourceSchemaNamespaceListerExpansion allows custom methods to be added to -// BoundAPIResourceSchemaNamespaceLister. -type BoundAPIResourceSchemaNamespaceListerExpansion interface{} +// BoundSchemaNamespaceListerExpansion allows custom methods to be added to +// BoundSchemaNamespaceLister. +type BoundSchemaNamespaceListerExpansion interface{} // ClusterBindingListerExpansion allows custom methods to be added to // ClusterBindingLister. diff --git a/test/e2e/bind/fixtures/provider/apiresourceschema-foo.yaml b/test/e2e/bind/fixtures/provider/apiresourceschema-foo.yaml deleted file mode 100644 index 14f9cb50a..000000000 --- a/test/e2e/bind/fixtures/provider/apiresourceschema-foo.yaml +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: kube-bind.io/v1alpha2 -kind: APIResourceSchema -metadata: - name: foos.bar.io -spec: - group: bar.io - informerScope: Namespaced - names: - kind: Foo - plural: foos - scope: Cluster - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - spec: - properties: - deploymentName: - type: string - replicas: - maximum: 10 - minimum: 1 - type: integer - type: object - status: - properties: - availableReplicas: - type: integer - phase: - enum: - - Pending - - Running - - Succeeded - - Failed - - Unknown - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/test/e2e/bind/fixtures/provider/apiresourceschema-mangodb.yaml b/test/e2e/bind/fixtures/provider/apiresourceschema-mangodb.yaml deleted file mode 100644 index c16eb33a9..000000000 --- a/test/e2e/bind/fixtures/provider/apiresourceschema-mangodb.yaml +++ /dev/null @@ -1,57 +0,0 @@ -apiVersion: kube-bind.io/v1alpha2 -kind: APIResourceSchema -metadata: - name: mangodbs.mangodb.com -spec: - group: mangodb.com - informerScope: Namespaced - names: - kind: MangoDB - listKind: MangoDBList - plural: mangodbs - singular: mangodb - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - spec: - properties: - backup: - default: false - type: boolean - region: - default: us-east-1 - minLength: 1 - type: string - tier: - default: Shared - enum: - - Dedicated - - Shared - type: string - tokenSecret: - minLength: 1 - type: string - required: - - tokenSecret - type: object - status: - properties: - phase: - enum: - - Pending - - Running - - Succeeded - - Failed - - Unknown - type: string - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} diff --git a/test/e2e/bind/happy-case_test.go b/test/e2e/bind/happy-case_test.go index 3ee266312..97e03b76e 100644 --- a/test/e2e/bind/happy-case_test.go +++ b/test/e2e/bind/happy-case_test.go @@ -65,7 +65,7 @@ func testHappyCase(t *testing.T, resourceScope apiextensionsv1.ResourceScope, in t.Logf("Starting backend with random port") addr, _ := framework.StartBackend(t, providerConfig, "--kubeconfig="+providerKubeconfig, "--listen-address=:0", "--consumer-scope="+string(informerScope)) - t.Logf("Creating APIResourceSchemas on provider side") + t.Logf("Creating CRD on provider side") providerfixtures.Bootstrap(t, framework.DiscoveryClient(t, providerConfig), framework.DynamicClient(t, providerConfig), nil) t.Logf("Creating consumer workspace and starting konnector") @@ -130,6 +130,9 @@ spec: t.Logf("Waiting for %s CRD to be created on consumer side", serviceGVR.Resource) crdClient := framework.ApiextensionsClient(t, consumerConfig).ApiextensionsV1().CustomResourceDefinitions() require.Eventually(t, func() bool { + if serviceGVR.Group == "" { + serviceGVR.Group = "core" + } _, err := crdClient.Get(ctx, serviceGVR.Resource+"."+serviceGVR.Group, metav1.GetOptions{}) return err == nil }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for %s CRD to be created on consumer side", serviceGVR.Resource) diff --git a/test/e2e/framework/backend.go b/test/e2e/framework/backend.go index 0903c65ac..84b6e8c24 100644 --- a/test/e2e/framework/backend.go +++ b/test/e2e/framework/backend.go @@ -62,12 +62,11 @@ func StartBackendWithoutDefaultArgs(t *testing.T, clientConfig *rest.Config, arg require.NoError(t, err) err = crd.Create(ctx, crdClient.ApiextensionsV1().CustomResourceDefinitions(), - metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "apiresourceschemas"}, - metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "boundapiresourceschemas"}, metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "clusterbindings"}, metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "apiserviceexports"}, metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "apiservicenamespaces"}, metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "apiserviceexportrequests"}, + metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "boundschemas"}, ) require.NoError(t, err) diff --git a/test/e2e/framework/bind.go b/test/e2e/framework/bind.go index 28869467d..86c126f6f 100644 --- a/test/e2e/framework/bind.go +++ b/test/e2e/framework/bind.go @@ -53,7 +53,6 @@ func Bind(t *testing.T, iostreams genericclioptions.IOStreams, authURLCh chan<- require.NoError(t, err) err = opts.Validate() require.NoError(t, err) - opts.Runner = func(cmd *exec.Cmd) error { bs, err := io.ReadAll(cmd.Stdin) if err != nil { diff --git a/test/e2e/framework/browser.go b/test/e2e/framework/browser.go index 80d4c96ae..6772b22ab 100644 --- a/test/e2e/framework/browser.go +++ b/test/e2e/framework/browser.go @@ -17,6 +17,7 @@ limitations under the License. package framework import ( + "strings" "testing" "time" @@ -27,7 +28,7 @@ import ( func BrowserEventuallyAtPath(t *testing.T, browser *browser.Browser, path string) { require.Eventuallyf(t, func() bool { - if browser.Url().Path == path { + if strings.Contains(browser.Url().Path, path) { t.Logf("Browser is at %s, waiting for path %s", browser.Url(), path) return true } diff --git a/test/e2e/framework/clients.go b/test/e2e/framework/clients.go index d5cf9d9e1..ad8d18df9 100644 --- a/test/e2e/framework/clients.go +++ b/test/e2e/framework/clients.go @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" ) func DynamicClient(t *testing.T, config *rest.Config) dynamic.Interface { @@ -50,3 +51,9 @@ func DiscoveryClient(t *testing.T, config *rest.Config) discovery.DiscoveryInter require.NoError(t, err) return c } + +func NewRESTConfig(t *testing.T, kubeconfig string) *rest.Config { + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + require.NoError(t, err, "Failed to build config from kubeconfig file") + return config +}