-
Bind
+
+
+
+
+ {{range $moduleIdx, $schema := .Schemas}}
+
+ {{end}}
- {{end}}
diff --git a/cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go b/cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go
index f260abb1e..521f1b5e6 100644
--- a/cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go
+++ b/cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go
@@ -44,20 +44,20 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co
return nil, err
}
- var bindings []*kubebindv1alpha2.APIServiceBinding
- for _, resource := range request.Spec.Resources {
- name := resource.ResourceGroupName()
- existing, err := bindClient.KubeBindV1alpha2().APIServiceBindings().Get(ctx, name, metav1.GetOptions{})
- if err != nil && !apierrors.IsNotFound(err) {
- return nil, err
- } else if err == nil {
- if existing.Spec.KubeconfigSecretRef.Namespace != "kube-bind" || existing.Spec.KubeconfigSecretRef.Name != secretName {
- return nil, fmt.Errorf("found existing APIServiceBinding %s not from this service provider", name)
- }
- fmt.Fprintf(b.Options.IOStreams.ErrOut, "✅ Updating existing APIServiceBinding %s.\n", existing.Name)
- bindings = append(bindings, existing)
+ // Use request name as the single binding name
+ bindingName := request.ObjectMeta.Name
+ existing, err := bindClient.KubeBindV1alpha2().APIServiceBindings().Get(ctx, bindingName, metav1.GetOptions{})
+ if err != nil && !apierrors.IsNotFound(err) {
+ return nil, err
+ } else if err == nil {
+ // Validate existing binding
+ if existing.Spec.KubeconfigSecretRef.Namespace != "kube-bind" || existing.Spec.KubeconfigSecretRef.Name != secretName {
+ return nil, fmt.Errorf("found existing APIServiceBinding %s not from this service provider", bindingName)
+ }
+ fmt.Fprintf(b.Options.IOStreams.ErrOut, "✅ Reusing existing APIServiceBinding %s.\n", existing.Name)
- // checking CRD to match the binding
+ // Validate all CRDs are owned by this binding
+ for _, resource := range request.Spec.Resources {
crd, err := apiextensionsClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, resource.ResourceGroupName(), metav1.GetOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return nil, err
@@ -66,52 +66,46 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co
return nil, fmt.Errorf("CustomResourceDefinition %s exists, but is not owned by kube-bind", crd.Name)
}
}
- continue
}
- // create new APIServiceBinding.
- first := true
- if err := wait.PollUntilContextCancel(ctx, 1*time.Second, false, func(ctx context.Context) (bool, error) {
- if !first {
- first = false
- fmt.Fprint(b.Options.IOStreams.ErrOut, ".")
- }
- created, err := bindClient.KubeBindV1alpha2().APIServiceBindings().Create(ctx, &kubebindv1alpha2.APIServiceBinding{
- ObjectMeta: metav1.ObjectMeta{
- Name: resource.ResourceGroupName(),
- Namespace: "kube-bind",
- },
- Spec: kubebindv1alpha2.APIServiceBindingSpec{
- KubeconfigSecretRef: kubebindv1alpha2.ClusterSecretKeyRef{
- LocalSecretKeyRef: kubebindv1alpha2.LocalSecretKeyRef{
- Name: secretName,
- Key: "kubeconfig",
- },
- Namespace: "kube-bind",
+ return []*kubebindv1alpha2.APIServiceBinding{existing}, nil
+ }
+
+ // Create new APIServiceBinding
+ var created *kubebindv1alpha2.APIServiceBinding
+ if err := wait.PollUntilContextCancel(ctx, 1*time.Second, false, func(ctx context.Context) (bool, error) {
+ created, err = bindClient.KubeBindV1alpha2().APIServiceBindings().Create(ctx, &kubebindv1alpha2.APIServiceBinding{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: bindingName,
+ },
+ Spec: kubebindv1alpha2.APIServiceBindingSpec{
+ KubeconfigSecretRef: kubebindv1alpha2.ClusterSecretKeyRef{
+ LocalSecretKeyRef: kubebindv1alpha2.LocalSecretKeyRef{
+ Name: secretName,
+ Key: "kubeconfig",
},
+ Namespace: "kube-bind",
},
- }, metav1.CreateOptions{})
- if err != nil {
- return false, err
- }
+ },
+ }, metav1.CreateOptions{})
+ if err != nil {
+ return false, err
+ }
- // best effort status update to have "Pending" in the Ready condition
- conditions.MarkFalse(created,
- conditionsapi.ReadyCondition,
- "Pending",
- conditionsapi.ConditionSeverityInfo,
- "Pending",
- )
- _, _ = bindClient.KubeBindV1alpha2().APIServiceBindings().UpdateStatus(ctx, created, metav1.UpdateOptions{})
+ // Best effort status update to have "Pending" in the Ready condition
+ conditions.MarkFalse(created,
+ conditionsapi.ReadyCondition,
+ "Pending",
+ conditionsapi.ConditionSeverityInfo,
+ "Pending",
+ )
+ _, _ = bindClient.KubeBindV1alpha2().APIServiceBindings().UpdateStatus(ctx, created, metav1.UpdateOptions{})
- fmt.Fprintf(b.Options.IOStreams.ErrOut, "✅ Created APIServiceBinding %s.%s\n", resource.Resource, resource.Group)
- bindings = append(bindings, created)
- return true, nil
- }); err != nil {
- fmt.Fprintln(b.Options.IOStreams.ErrOut, "")
- return nil, err
- }
+ return true, nil
+ }); err != nil {
+ return nil, err
}
- return bindings, nil
+ fmt.Fprintf(b.Options.IOStreams.ErrOut, "✅ Created APIServiceBinding %s for %d resources\n", bindingName, len(request.Spec.Resources))
+ return []*kubebindv1alpha2.APIServiceBinding{created}, nil
}
diff --git a/contrib/kcp/README.md b/contrib/kcp/README.md
index 686437b46..d230716b6 100644
--- a/contrib/kcp/README.md
+++ b/contrib/kcp/README.md
@@ -93,10 +93,14 @@ kubectl kcp bind apiexport root:kube-bind:kube-bind.io \
7. Create CRD in provider:
```bash
-kubectl create -f contrib/kcp/deploy/examples/apiexport.yaml
-kubectl create -f contrib/kcp/deploy/examples/apiresourceschema-cowboys.yaml
-kubectl create -f contrib/kcp/deploy/examples/apiresourceschema-sheriffs.yaml
+kubectl apply -f contrib/kcp/deploy/examples/apiexport.yaml
+kubectl apply -f contrib/kcp/deploy/examples/apiresourceschema-cowboys.yaml
+kubectl apply -f contrib/kcp/deploy/examples/apiresourceschema-sheriffs.yaml
kubectl kcp bind apiexport root:provider:cowboys-stable
+
+kubectl apply -f contrib/kcp/deploy/examples/template-cowboys.yaml
+kubectl apply -f contrib/kcp/deploy/examples/template-sheriffs.yaml
+kubectl apply -f contrib/kcp/deploy/examples/collection-wildwest.yaml
```
8. Get LogicalCluster:
@@ -104,7 +108,7 @@ kubectl kcp bind apiexport root:provider:cowboys-stable
```bash
kubectl get logicalcluster
# NAME PHASE URL AGE
-# cluster Ready https://192.168.2.166:6443/clusters/1f4roigyt6meiaf8
+# cluster Ready https://192.168.2.166:6443/clusters/2cc89nxsuivawooq
```
## Consumer
@@ -120,7 +124,7 @@ kubectl ws create consumer --enter
10. Bind the thing:
```bash
-./bin/kubectl-bind http://127.0.0.1:8080/clusters/1f4roigyt6meiaf8/exports --dry-run -o yaml > apiserviceexport.yaml
+./bin/kubectl-bind http://127.0.0.1:8080/clusters/2cc89nxsuivawooq/exports --dry-run -o yaml > apiserviceexport.yaml
# Extract secret for binding process. Note that secret name is not the same as output from command above. Check secret
# name by running `kubectl get secret -n kube-bind`
diff --git a/contrib/kcp/deploy/examples/collection-wildwest.yaml b/contrib/kcp/deploy/examples/collection-wildwest.yaml
new file mode 100644
index 000000000..90e123a8d
--- /dev/null
+++ b/contrib/kcp/deploy/examples/collection-wildwest.yaml
@@ -0,0 +1,9 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: Collection
+metadata:
+ name: wildwest
+spec:
+ description: "A collection of Wild West service definitions including Cowboys and Sheriffs for frontier management"
+ templates:
+ - name: cowboys
+ - name: sheriffs
\ No newline at end of file
diff --git a/contrib/kcp/deploy/examples/template-cowboys.yaml b/contrib/kcp/deploy/examples/template-cowboys.yaml
new file mode 100644
index 000000000..dccca8910
--- /dev/null
+++ b/contrib/kcp/deploy/examples/template-cowboys.yaml
@@ -0,0 +1,32 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: APIServiceExportTemplate
+metadata:
+ name: cowboys
+spec:
+ scope: Namespaced
+ resources:
+ - group: wildwest.dev
+ versions:
+ - v1alpha1
+ resource: cowboys
+ permissionClaims:
+ - group: ""
+ resource: secrets
+ selector:
+ namedResources:
+ - name: cowboy-credentials
+ - name: cowboy-config
+ labelSelector:
+ matchLabels:
+ app: cowboy
+ env: production
+ - group: ""
+ resource: configmaps
+ selector:
+ namedResources:
+ - name: cowboy-settings
+ - name: cowboy-environment
+ labelSelector:
+ matchLabels:
+ app: cowboy
+ component: config
\ No newline at end of file
diff --git a/contrib/kcp/deploy/examples/template-sheriffs.yaml b/contrib/kcp/deploy/examples/template-sheriffs.yaml
new file mode 100644
index 000000000..bd3a8e117
--- /dev/null
+++ b/contrib/kcp/deploy/examples/template-sheriffs.yaml
@@ -0,0 +1,35 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: APIServiceExportTemplate
+metadata:
+ name: sheriffs
+spec:
+ scope: Cluster
+ resources:
+ - group: wildwest.dev
+ versions:
+ - v1alpha1
+ resource: sheriffs
+ permissionClaims:
+ - group: ""
+ resource: secrets
+ selector:
+ namedResources:
+ - name: sheriff-badge-credentials
+ - name: sheriff-jurisdiction-config
+ labelSelector:
+ matchLabels:
+ app: sheriff
+ security: high
+ - group: ""
+ resource: configmaps
+ selector:
+ namedResources:
+ - name: sheriff-jurisdiction-map
+ - name: sheriff-enforcement-rules
+ labelSelector:
+ matchLabels:
+ app: sheriff
+ component: enforcement
+ namespaces:
+ - name: sheriff-operations
+ - name: sheriff-intelligence
\ No newline at end of file
diff --git a/contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml b/contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml
index f6269bb0a..f32386106 100644
--- a/contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml
+++ b/contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml
@@ -49,7 +49,7 @@ spec:
crd: {}
- group: kube-bind.io
name: apiservicebindings
- schema: v250929-62fc678.apiservicebindings.kube-bind.io
+ schema: v251015-f561c7c.apiservicebindings.kube-bind.io
storage:
crd: {}
- group: kube-bind.io
@@ -59,7 +59,12 @@ spec:
crd: {}
- group: kube-bind.io
name: apiserviceexports
- schema: v250929-62fc678.apiserviceexports.kube-bind.io
+ schema: v251015-f561c7c.apiserviceexports.kube-bind.io
+ storage:
+ crd: {}
+ - group: kube-bind.io
+ name: apiserviceexporttemplates
+ schema: v251022-ca928ec.apiserviceexporttemplates.kube-bind.io
storage:
crd: {}
- group: kube-bind.io
@@ -77,4 +82,9 @@ spec:
schema: v250925-56669b8.clusterbindings.kube-bind.io
storage:
crd: {}
+ - group: kube-bind.io
+ name: collections
+ schema: v251022-ca928ec.collections.kube-bind.io
+ storage:
+ crd: {}
status: {}
diff --git a/contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml b/contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml
index 6a1d7c57a..d61cebf72 100644
--- a/contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml
+++ b/contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml
@@ -2,7 +2,7 @@ apiVersion: apis.kcp.io/v1alpha1
kind: APIResourceSchema
metadata:
creationTimestamp: null
- name: v250929-62fc678.apiservicebindings.kube-bind.io
+ name: v251015-f561c7c.apiservicebindings.kube-bind.io
spec:
conversion:
strategy: None
@@ -374,7 +374,7 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
- namedResource:
+ namedResources:
description: NamedResource is a shorthand for selecting a
single resource by name and namespace.
items:
diff --git a/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml b/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml
index d9a3f88f9..7dbb37bef 100644
--- a/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml
+++ b/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml
@@ -310,7 +310,7 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
- namedResource:
+ namedResources:
description: NamedResource is a shorthand for selecting a
single resource by name and namespace.
items:
diff --git a/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml b/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml
index 208a38641..ecaf8a31f 100644
--- a/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml
+++ b/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml
@@ -2,7 +2,7 @@ apiVersion: apis.kcp.io/v1alpha1
kind: APIResourceSchema
metadata:
creationTimestamp: null
- name: v250929-62fc678.apiserviceexports.kube-bind.io
+ name: v251015-f561c7c.apiserviceexports.kube-bind.io
spec:
conversion:
strategy: None
@@ -561,7 +561,7 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
- namedResource:
+ namedResources:
description: NamedResource is a shorthand for selecting a
single resource by name and namespace.
items:
diff --git a/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexporttemplates.kube-bind.io.yaml b/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexporttemplates.kube-bind.io.yaml
new file mode 100644
index 000000000..86d9b67b7
--- /dev/null
+++ b/contrib/kcp/deploy/resources/apiresourceschema-apiserviceexporttemplates.kube-bind.io.yaml
@@ -0,0 +1,268 @@
+apiVersion: apis.kcp.io/v1alpha1
+kind: APIResourceSchema
+metadata:
+ creationTimestamp: null
+ name: v251022-ca928ec.apiserviceexporttemplates.kube-bind.io
+spec:
+ group: kube-bind.io
+ names:
+ categories:
+ - kube-bind
+ kind: APIServiceExportTemplate
+ listKind: APIServiceExportTemplateList
+ plural: apiserviceexporttemplates
+ singular: apiserviceexporttemplate
+ scope: Cluster
+ versions:
+ - additionalPrinterColumns:
+ - jsonPath: .spec.resources[*].group
+ name: Resources
+ type: string
+ - jsonPath: .spec.permissionClaims[*].resource
+ name: PermissionClaims
+ type: string
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha2
+ schema:
+ description: APIServiceExportTemplate groups multiple CRDs with related resources
+ (permissionClaims) as a Service definition.
+ 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: spec specifies the template.
+ properties:
+ namespaces:
+ description: |-
+ namespaces specifies the namespaces that should be bootstrapped as part of this template.
+ When objects originate from provider side, consumer does not always know the necessary details
+ This field allows provider to pre-heat the necessary namespaces on provider side by creating
+ APIServiceNamespace objects attached to the APIServiceExport. More namespaces can be created later by the consumer.
+ items:
+ properties:
+ name:
+ description: name is the name of the namespace to create on provider
+ side.
+ type: string
+ required:
+ - name
+ type: object
+ type: array
+ permissionClaims:
+ description: permissionClaims defines the permission claims required
+ by this template.
+ items:
+ description: |-
+ PermissionClaim selects objects of a GVR that a service provider may
+ request and that a consumer may accept and allow the service provider access to.
+ properties:
+ 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
+ resource:
+ description: |-
+ 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
+ selector:
+ description: Selector is a resource selector that selects objects
+ of a GVR.
+ properties:
+ labelSelector:
+ description: LabelSelector is a label selector that selects
+ objects of a GVR.
+ properties:
+ matchExpressions:
+ description: matchExpressions is a list of label selector
+ requirements. The requirements are ANDed.
+ items:
+ description: |-
+ A label selector requirement is a selector that contains values, a key, and an operator that
+ relates the key and values.
+ properties:
+ key:
+ description: key is the label key that the selector
+ applies to.
+ type: string
+ operator:
+ description: |-
+ operator represents a key's relationship to a set of values.
+ Valid operators are In, NotIn, Exists and DoesNotExist.
+ type: string
+ values:
+ description: |-
+ values is an array of string values. If the operator is In or NotIn,
+ the values array must be non-empty. If the operator is Exists or DoesNotExist,
+ the values array must be empty. This array is replaced during a strategic
+ merge patch.
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ x-kubernetes-list-type: atomic
+ matchLabels:
+ additionalProperties:
+ type: string
+ description: |-
+ matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
+ map is equivalent to an element of matchExpressions, whose key field is "key", the
+ operator is "In", and the values array contains only "value". The requirements are ANDed.
+ type: object
+ type: object
+ x-kubernetes-map-type: atomic
+ namedResources:
+ description: NamedResource is a shorthand for selecting a
+ single resource by name and namespace.
+ items:
+ description: NamedResource selects a specific resource by
+ name and namespace.
+ properties:
+ name:
+ description: |-
+ Name is the name of the resource.
+ Name matches the metadata.name field of the underlying object.
+ type: string
+ namespace:
+ description: |-
+ Namespace represents namespace where an object of the given group/resource may be managed.
+ Namespaces matches against the metadata.namespace field. If not provided, the object is assumed to be cluster-scoped.
+ Namespaces field is ignored for namespaced isolation mode.
+ type: string
+ required:
+ - name
+ type: object
+ type: array
+ type: object
+ required:
+ - resource
+ - selector
+ type: object
+ type: array
+ resources:
+ description: resources defines the CRDs that are part of this template.
+ items:
+ properties:
+ 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
+ resource:
+ description: |-
+ 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:
+ - resource
+ type: object
+ minItems: 1
+ type: array
+ scope:
+ allOf:
+ - enum:
+ - Cluster
+ - Namespaced
+ - enum:
+ - Namespaced
+ - Cluster
+ description: scope defines the scope of the resources in this template.
+ type: string
+ required:
+ - resources
+ - scope
+ type: object
+ status:
+ description: status contains reconciliation information for the template.
+ properties:
+ conditions:
+ description: conditions is a list of conditions that apply to the APIServiceExportTemplate.
+ items:
+ description: Condition defines an observation of a object operational
+ state.
+ properties:
+ lastTransitionTime:
+ description: |-
+ Last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when
+ the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ A human readable message indicating details about the transition.
+ This field may be empty.
+ type: string
+ reason:
+ description: |-
+ The reason for the condition's last transition in CamelCase.
+ The specific API may choose whether or not this field is considered a guaranteed API.
+ This field may not be empty.
+ type: string
+ severity:
+ description: |-
+ Severity provides an explicit classification of Reason code, so the users or machines can immediately
+ understand the current situation and act accordingly.
+ The Severity field MUST be set only when Status=False.
+ type: string
+ status:
+ description: Status of the condition, one of True, False, Unknown.
+ type: string
+ type:
+ description: |-
+ Type of condition in CamelCase or in foo.example.com/CamelCase.
+ Many .condition.type values are consistent across resources like Available, but because arbitrary conditions
+ can be useful (see .node.status.conditions), the ability to deconflict is important.
+ type: string
+ required:
+ - lastTransitionTime
+ - status
+ - type
+ type: object
+ type: array
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/contrib/kcp/deploy/resources/apiresourceschema-collections.kube-bind.io.yaml b/contrib/kcp/deploy/resources/apiresourceschema-collections.kube-bind.io.yaml
new file mode 100644
index 000000000..8c4127e16
--- /dev/null
+++ b/contrib/kcp/deploy/resources/apiresourceschema-collections.kube-bind.io.yaml
@@ -0,0 +1,128 @@
+apiVersion: apis.kcp.io/v1alpha1
+kind: APIResourceSchema
+metadata:
+ creationTimestamp: null
+ name: v251022-ca928ec.collections.kube-bind.io
+spec:
+ group: kube-bind.io
+ names:
+ categories:
+ - kube-bind
+ kind: Collection
+ listKind: CollectionList
+ plural: collections
+ singular: collection
+ scope: Cluster
+ versions:
+ - additionalPrinterColumns:
+ - jsonPath: .spec.description
+ name: Description
+ type: string
+ - jsonPath: .spec.templates[*].name
+ name: Templates
+ type: string
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha2
+ schema:
+ description: Collection groups multiple APIServiceExportTemplates into a logical
+ group. This functions as a folder in the UI.
+ 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: spec specifies the collection.
+ properties:
+ description:
+ description: description is a human readable description of this collection.
+ type: string
+ templates:
+ description: templates is a list of template references that are part
+ of this collection.
+ items:
+ description: APIServiceExportTemplateReference references an APIServiceExportTemplate
+ by name.
+ properties:
+ name:
+ description: name is the name of the template.
+ type: string
+ required:
+ - name
+ type: object
+ minItems: 1
+ type: array
+ required:
+ - templates
+ type: object
+ status:
+ description: status contains reconciliation information for the collection.
+ properties:
+ conditions:
+ description: conditions is a list of conditions that apply to the Collection.
+ items:
+ description: Condition defines an observation of a object operational
+ state.
+ properties:
+ lastTransitionTime:
+ description: |-
+ Last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when
+ the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ A human readable message indicating details about the transition.
+ This field may be empty.
+ type: string
+ reason:
+ description: |-
+ The reason for the condition's last transition in CamelCase.
+ The specific API may choose whether or not this field is considered a guaranteed API.
+ This field may not be empty.
+ type: string
+ severity:
+ description: |-
+ Severity provides an explicit classification of Reason code, so the users or machines can immediately
+ understand the current situation and act accordingly.
+ The Severity field MUST be set only when Status=False.
+ type: string
+ status:
+ description: Status of the condition, one of True, False, Unknown.
+ type: string
+ type:
+ description: |-
+ Type of condition in CamelCase or in foo.example.com/CamelCase.
+ Many .condition.type values are consistent across resources like Available, but because arbitrary conditions
+ can be useful (see .node.status.conditions), the ability to deconflict is important.
+ type: string
+ required:
+ - lastTransitionTime
+ - status
+ - type
+ type: object
+ type: array
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/contrib/kcp/test/e2e/browser.go b/contrib/kcp/test/e2e/browser.go
index b3d09749e..750d417e0 100644
--- a/contrib/kcp/test/e2e/browser.go
+++ b/contrib/kcp/test/e2e/browser.go
@@ -33,14 +33,14 @@ import (
"github.com/kube-bind/kube-bind/test/e2e/framework"
)
-func performBindingWithBrowser(t *testing.T, backendAddr string, clusterID string, consumerCfg *rest.Config, consumerKubeconfig, resource string) {
+func performBindingWithBrowser(t *testing.T, backendAddr string, clusterID string, consumerCfg *rest.Config, consumerKubeconfig, resource, template string) {
bindURL := fmt.Sprintf("http://%s/clusters/%s/exports", backendAddr, clusterID)
t.Logf("Bind URL: %s", bindURL)
// Test binding dry run first (similar to happy-case test)
t.Run("Service is bound dry run", func(t *testing.T) {
authURLDryRunCh := make(chan string, 1)
- go simulateKCPBrowser(t, authURLDryRunCh, resource)
+ go simulateKCPBrowser(t, authURLDryRunCh, template)
iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams()
framework.Bind(t, iostreams, authURLDryRunCh, nil, bindURL, "--kubeconfig", consumerKubeconfig, "--dry-run")
@@ -51,7 +51,7 @@ func performBindingWithBrowser(t *testing.T, backendAddr string, clusterID strin
// Perform actual binding (similar to happy-case test)
t.Run("Service is bound", func(t *testing.T) {
authURLCh := make(chan string, 1)
- go simulateKCPBrowser(t, authURLCh, resource)
+ go simulateKCPBrowser(t, authURLCh, template)
iostreams, _, _, _ := genericclioptions.NewTestIOStreams()
invocations := make(chan framework.SubCommandInvocation, 1)
@@ -78,8 +78,8 @@ func performBindingWithBrowser(t *testing.T, backendAddr string, clusterID strin
})
}
-// simulateKCPBrowser simulates browser interaction for KCP binding.
-func simulateKCPBrowser(t *testing.T, authURLCh chan string, resource string) {
+// simulateKCPBrowser simulates browser interaction for KCP binding using templates.
+func simulateKCPBrowser(t *testing.T, authURLCh chan string, template string) {
browser := surf.NewBrowser()
authURL := <-authURLCh
@@ -90,9 +90,9 @@ func simulateKCPBrowser(t *testing.T, authURLCh chan string, resource string) {
t.Logf("Waiting for browser to be at /resources")
framework.BrowserEventuallyAtPath(t, browser, "/resources")
- t.Logf("Clicking %s resource", resource)
- err = browser.Click("a." + resource)
- require.NoError(t, err, "Failed to click resource link")
+ t.Logf("Clicking %s template", template)
+ err = browser.Click("a." + template)
+ require.NoError(t, err, "Failed to click template link")
t.Logf("Waiting for browser to be forwarded to client")
framework.BrowserEventuallyAtPath(t, browser, "/callback")
diff --git a/contrib/kcp/test/e2e/kcp_test.go b/contrib/kcp/test/e2e/kcp_test.go
index 4fc6aa363..96d2495f3 100644
--- a/contrib/kcp/test/e2e/kcp_test.go
+++ b/contrib/kcp/test/e2e/kcp_test.go
@@ -101,12 +101,15 @@ func testKcpIntegration(t *testing.T, scope kubebindv1alpha2.InformerScope) {
),
)
- t.Log("Applying example APIExport and APIResourceSchemas to provider workspace")
+ t.Log("Applying example APIExport, APIResourceSchemas and templates to provider workspace")
framework.ApplyFiles(t,
providerCfg,
"../../deploy/examples/apiexport.yaml",
"../../deploy/examples/apiresourceschema-cowboys.yaml", // namespaced
"../../deploy/examples/apiresourceschema-sheriffs.yaml", // cluster scoped
+ "../../deploy/examples/template-cowboys.yaml", // template for cowboys
+ "../../deploy/examples/template-sheriffs.yaml", // template for sheriffs
+ "../../deploy/examples/collection-wildwest.yaml",
)
t.Log("Bind the APIExport locally")
@@ -138,18 +141,20 @@ func testKcpIntegration(t *testing.T, scope kubebindv1alpha2.InformerScope) {
// kube-bind process
t.Log("Perform binding process with browser")
- var kind, resource string
+ var kind, resource, template string
switch scope {
case kubebindv1alpha2.ClusterScope:
kind = "Sheriff"
resource = "sheriffs"
+ template = "sheriffs"
case kubebindv1alpha2.NamespacedScope:
kind = "Cowboy"
resource = "cowboys"
+ template = "cowboys"
default:
require.Fail(t, "unhandled scope %q", scope)
}
- performBindingWithBrowser(t, backendAddr, providerClusterID, consumerCfg, consumerKubeconfig, resource)
+ performBindingWithBrowser(t, backendAddr, providerClusterID, consumerCfg, consumerKubeconfig, resource, template)
t.Log("Testing resource creation and synchronization...")
testKCPResourceSync(t, consumerCfg, providerCfg, scope, kind, resource)
diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml
index d94c50a99..e4fb14485 100644
--- a/deploy/crd/kube-bind.io_apiservicebindings.yaml
+++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml
@@ -381,7 +381,7 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
- namedResource:
+ namedResources:
description: NamedResource is a shorthand for selecting
a single resource by name and namespace.
items:
diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml
index 8a9c3eea0..dfa03f749 100644
--- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml
+++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml
@@ -314,7 +314,7 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
- namedResource:
+ namedResources:
description: NamedResource is a shorthand for selecting
a single resource by name and namespace.
items:
diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml
index 2aaf068b9..3ba6270ff 100644
--- a/deploy/crd/kube-bind.io_apiserviceexports.yaml
+++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml
@@ -564,7 +564,7 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
- namedResource:
+ namedResources:
description: NamedResource is a shorthand for selecting
a single resource by name and namespace.
items:
diff --git a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml
new file mode 100644
index 000000000..5ac00f14d
--- /dev/null
+++ b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml
@@ -0,0 +1,272 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.17.3
+ name: apiserviceexporttemplates.kube-bind.io
+spec:
+ group: kube-bind.io
+ names:
+ categories:
+ - kube-bind
+ kind: APIServiceExportTemplate
+ listKind: APIServiceExportTemplateList
+ plural: apiserviceexporttemplates
+ singular: apiserviceexporttemplate
+ scope: Cluster
+ versions:
+ - additionalPrinterColumns:
+ - jsonPath: .spec.resources[*].group
+ name: Resources
+ type: string
+ - jsonPath: .spec.permissionClaims[*].resource
+ name: PermissionClaims
+ type: string
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha2
+ schema:
+ openAPIV3Schema:
+ description: APIServiceExportTemplate groups multiple CRDs with related resources
+ (permissionClaims) as a Service definition.
+ 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: spec specifies the template.
+ properties:
+ namespaces:
+ description: |-
+ namespaces specifies the namespaces that should be bootstrapped as part of this template.
+ When objects originate from provider side, consumer does not always know the necessary details
+ This field allows provider to pre-heat the necessary namespaces on provider side by creating
+ APIServiceNamespace objects attached to the APIServiceExport. More namespaces can be created later by the consumer.
+ items:
+ properties:
+ name:
+ description: name is the name of the namespace to create on
+ provider side.
+ type: string
+ required:
+ - name
+ type: object
+ type: array
+ permissionClaims:
+ description: permissionClaims defines the permission claims required
+ by this template.
+ items:
+ description: |-
+ PermissionClaim selects objects of a GVR that a service provider may
+ request and that a consumer may accept and allow the service provider access to.
+ properties:
+ 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
+ resource:
+ description: |-
+ 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
+ selector:
+ description: Selector is a resource selector that selects objects
+ of a GVR.
+ properties:
+ labelSelector:
+ description: LabelSelector is a label selector that selects
+ objects of a GVR.
+ properties:
+ matchExpressions:
+ description: matchExpressions is a list of label selector
+ requirements. The requirements are ANDed.
+ items:
+ description: |-
+ A label selector requirement is a selector that contains values, a key, and an operator that
+ relates the key and values.
+ properties:
+ key:
+ description: key is the label key that the selector
+ applies to.
+ type: string
+ operator:
+ description: |-
+ operator represents a key's relationship to a set of values.
+ Valid operators are In, NotIn, Exists and DoesNotExist.
+ type: string
+ values:
+ description: |-
+ values is an array of string values. If the operator is In or NotIn,
+ the values array must be non-empty. If the operator is Exists or DoesNotExist,
+ the values array must be empty. This array is replaced during a strategic
+ merge patch.
+ items:
+ type: string
+ type: array
+ x-kubernetes-list-type: atomic
+ required:
+ - key
+ - operator
+ type: object
+ type: array
+ x-kubernetes-list-type: atomic
+ matchLabels:
+ additionalProperties:
+ type: string
+ description: |-
+ matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
+ map is equivalent to an element of matchExpressions, whose key field is "key", the
+ operator is "In", and the values array contains only "value". The requirements are ANDed.
+ type: object
+ type: object
+ x-kubernetes-map-type: atomic
+ namedResources:
+ description: NamedResource is a shorthand for selecting
+ a single resource by name and namespace.
+ items:
+ description: NamedResource selects a specific resource
+ by name and namespace.
+ properties:
+ name:
+ description: |-
+ Name is the name of the resource.
+ Name matches the metadata.name field of the underlying object.
+ type: string
+ namespace:
+ description: |-
+ Namespace represents namespace where an object of the given group/resource may be managed.
+ Namespaces matches against the metadata.namespace field. If not provided, the object is assumed to be cluster-scoped.
+ Namespaces field is ignored for namespaced isolation mode.
+ type: string
+ required:
+ - name
+ type: object
+ type: array
+ type: object
+ required:
+ - resource
+ - selector
+ type: object
+ type: array
+ resources:
+ description: resources defines the CRDs that are part of this template.
+ items:
+ properties:
+ 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
+ resource:
+ description: |-
+ 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:
+ - resource
+ type: object
+ minItems: 1
+ type: array
+ scope:
+ allOf:
+ - enum:
+ - Cluster
+ - Namespaced
+ - enum:
+ - Namespaced
+ - Cluster
+ description: scope defines the scope of the resources in this template.
+ type: string
+ required:
+ - resources
+ - scope
+ type: object
+ status:
+ description: status contains reconciliation information for the template.
+ properties:
+ conditions:
+ description: conditions is a list of conditions that apply to the
+ APIServiceExportTemplate.
+ items:
+ description: Condition defines an observation of a object operational
+ state.
+ properties:
+ lastTransitionTime:
+ description: |-
+ Last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when
+ the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ A human readable message indicating details about the transition.
+ This field may be empty.
+ type: string
+ reason:
+ description: |-
+ The reason for the condition's last transition in CamelCase.
+ The specific API may choose whether or not this field is considered a guaranteed API.
+ This field may not be empty.
+ type: string
+ severity:
+ description: |-
+ Severity provides an explicit classification of Reason code, so the users or machines can immediately
+ understand the current situation and act accordingly.
+ The Severity field MUST be set only when Status=False.
+ type: string
+ status:
+ description: Status of the condition, one of True, False, Unknown.
+ type: string
+ type:
+ description: |-
+ Type of condition in CamelCase or in foo.example.com/CamelCase.
+ Many .condition.type values are consistent across resources like Available, but because arbitrary conditions
+ can be useful (see .node.status.conditions), the ability to deconflict is important.
+ type: string
+ required:
+ - lastTransitionTime
+ - status
+ - type
+ type: object
+ type: array
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/deploy/crd/kube-bind.io_collections.yaml b/deploy/crd/kube-bind.io_collections.yaml
new file mode 100644
index 000000000..b95bc0f89
--- /dev/null
+++ b/deploy/crd/kube-bind.io_collections.yaml
@@ -0,0 +1,132 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.17.3
+ name: collections.kube-bind.io
+spec:
+ group: kube-bind.io
+ names:
+ categories:
+ - kube-bind
+ kind: Collection
+ listKind: CollectionList
+ plural: collections
+ singular: collection
+ scope: Cluster
+ versions:
+ - additionalPrinterColumns:
+ - jsonPath: .spec.description
+ name: Description
+ type: string
+ - jsonPath: .spec.templates[*].name
+ name: Templates
+ type: string
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha2
+ schema:
+ openAPIV3Schema:
+ description: Collection groups multiple APIServiceExportTemplates into a logical
+ group. This functions as a folder in the UI.
+ 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: spec specifies the collection.
+ properties:
+ description:
+ description: description is a human readable description of this collection.
+ type: string
+ templates:
+ description: templates is a list of template references that are part
+ of this collection.
+ items:
+ description: APIServiceExportTemplateReference references an APIServiceExportTemplate
+ by name.
+ properties:
+ name:
+ description: name is the name of the template.
+ type: string
+ required:
+ - name
+ type: object
+ minItems: 1
+ type: array
+ required:
+ - templates
+ type: object
+ status:
+ description: status contains reconciliation information for the collection.
+ properties:
+ conditions:
+ description: conditions is a list of conditions that apply to the
+ Collection.
+ items:
+ description: Condition defines an observation of a object operational
+ state.
+ properties:
+ lastTransitionTime:
+ description: |-
+ Last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when
+ the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ A human readable message indicating details about the transition.
+ This field may be empty.
+ type: string
+ reason:
+ description: |-
+ The reason for the condition's last transition in CamelCase.
+ The specific API may choose whether or not this field is considered a guaranteed API.
+ This field may not be empty.
+ type: string
+ severity:
+ description: |-
+ Severity provides an explicit classification of Reason code, so the users or machines can immediately
+ understand the current situation and act accordingly.
+ The Severity field MUST be set only when Status=False.
+ type: string
+ status:
+ description: Status of the condition, one of True, False, Unknown.
+ type: string
+ type:
+ description: |-
+ Type of condition in CamelCase or in foo.example.com/CamelCase.
+ Many .condition.type values are consistent across resources like Available, but because arbitrary conditions
+ can be useful (see .node.status.conditions), the ability to deconflict is important.
+ type: string
+ required:
+ - lastTransitionTime
+ - status
+ - type
+ type: object
+ type: array
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/deploy/examples/collection.yaml b/deploy/examples/collection.yaml
new file mode 100644
index 000000000..a456e1a06
--- /dev/null
+++ b/deploy/examples/collection.yaml
@@ -0,0 +1,8 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: Collection
+metadata:
+ name: all
+spec:
+ templates:
+ - name: mangodb
+ - name: foo
\ No newline at end of file
diff --git a/deploy/examples/template-foo.yaml b/deploy/examples/template-foo.yaml
new file mode 100644
index 000000000..80cefc53c
--- /dev/null
+++ b/deploy/examples/template-foo.yaml
@@ -0,0 +1,33 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: APIServiceExportTemplate
+metadata:
+ name: foo
+spec:
+ scope: Cluster
+ resources:
+ - group: bar.io
+ versions:
+ - v1alpha1
+ resource: foos
+ permissionClaims:
+ - group: ""
+ resource: configmaps
+ selector:
+ namedResources:
+ - name: named-configmap-only
+ namespace: foo-secrets-ns
+ - group: ""
+ resource: secrets
+ selector:
+ labelSelector:
+ matchLabels:
+ app: secrets
+ namedResources:
+ - name: test-secret
+ namespace: foo-secrets-ns
+ - name: named-secret-1
+ namespace: foo-secrets-ns
+ - name: named-secret-2
+ namespace: foo-secrets-ns
+ namespaces:
+ - name: foo-secrets-ns
\ No newline at end of file
diff --git a/deploy/examples/template-mangodb.yaml b/deploy/examples/template-mangodb.yaml
new file mode 100644
index 000000000..51e182394
--- /dev/null
+++ b/deploy/examples/template-mangodb.yaml
@@ -0,0 +1,23 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: APIServiceExportTemplate
+metadata:
+ name: mangodb
+spec:
+ scope: Namespaced
+ resources:
+ - group: mangodb.com
+ versions:
+ - v1alpha1
+ resource: mangodbs
+ permissionClaims:
+ - group: ""
+ resource: "secrets"
+ selector:
+ labelSelector:
+ matchLabels:
+ app: "mangodb"
+ namedResources:
+ - name: "mangodb-credentials"
+ namespace: "mangodb-namespace"
+ namespaces:
+ - name: mangodb-namespace
\ No newline at end of file
diff --git a/docs/content/.pages b/docs/content/.pages
index 786980b11..38cfd704f 100644
--- a/docs/content/.pages
+++ b/docs/content/.pages
@@ -2,6 +2,7 @@ nav:
- Home:
- index.md
- Setup: setup
+ - Usage: usage
- Contributing: contributing
- Developers: developers
- Reference: reference
diff --git a/docs/content/index.md b/docs/content/index.md
index e84bc0bf1..8facfcb1a 100644
--- a/docs/content/index.md
+++ b/docs/content/index.md
@@ -11,6 +11,12 @@ kube-bind is a prototype project that aims to provide better support for service
- The service provider does not inject controllers/operators into the service consumer's cluster.
- A single vendor-neutral, OpenSource agent per consumer cluster connects it with the requested services.
+## Key Features
+- **Enhanced Permission Claims**: Granular resource access with both label selectors and named resource support
+- **Multi-backend Support**: Works with standard Kubernetes and kcp backends through multicluster-runtime
+- **Secure Communication**: TLS-encrypted channels between service consumers and providers
+- **Flexible UI**: Web-based dashboard for managing services and permissions when binding services
+
## Quickstart
To get started with trying out kube-bind on your local system, check out our [Quickstart](./setup/quickstart.md) instructions.
diff --git a/docs/content/setup/index.md b/docs/content/setup/index.md
index b6adf12c3..b4afa0077 100644
--- a/docs/content/setup/index.md
+++ b/docs/content/setup/index.md
@@ -29,4 +29,12 @@ Choose the setup that best fits your use case:
- Use **Helm Deployment** for production environments with standard Kubernetes
- Use **KCP Integration** for advanced multi-tenant scenarios with workspace isolation
+## Next Steps
+
+After completing your setup, explore these guides:
+
+- **[Usage Guide](../usage/index.md)**: Learn common workflows and the new Catalog API
+- **[Migration Guide](../usage/migration.md)**: Upgrade from previous versions
+- **[Developer Documentation](../developers/index.md)**: Understand the architecture and contribute
+
{% include "partials/section-overview.html" %}
diff --git a/docs/content/setup/quickstart.md b/docs/content/setup/quickstart.md
index 8a252db91..8cfd68cf4 100644
--- a/docs/content/setup/quickstart.md
+++ b/docs/content/setup/quickstart.md
@@ -73,6 +73,9 @@ kubectl ws create provider --enter
```shell
kubectl apply -f deploy/examples/crd-mangodb.yaml
kubectl apply -f deploy/examples/crd-foo.yaml
+ kubectl apply -f deploy/examples/template-mangodb.yaml
+ kubectl apply -f deploy/examples/template-foo.yaml
+ kubectl apply -f deploy/examples/collection.yaml
```
* start the backend binary with the right flags:
diff --git a/docs/content/usage/.pages b/docs/content/usage/.pages
new file mode 100644
index 000000000..33aedbeff
--- /dev/null
+++ b/docs/content/usage/.pages
@@ -0,0 +1,4 @@
+title: Usage
+nav:
+ - index.md
+ - migration.md
\ No newline at end of file
diff --git a/docs/content/usage/index.md b/docs/content/usage/index.md
new file mode 100644
index 000000000..3078d7aa2
--- /dev/null
+++ b/docs/content/usage/index.md
@@ -0,0 +1,16 @@
+# Usage Guide
+
+This guide covers common usage patterns and workflows for kube-bind, including the new Catalog API features introduced in recent versions.
+
+## Table of Contents
+
+- [Basic Service Binding](#basic-service-binding)
+- [Using the Catalog API](#using-the-catalog-api)
+- [Permission Claims](#permission-claims)
+- [Provider-side Namespace Management](#provider-side-namespace-management)
+- [Advanced Workflows](#advanced-workflows)
+
+## Basic Service Binding
+
+
+TODO
\ No newline at end of file
diff --git a/docs/content/usage/migration.md b/docs/content/usage/migration.md
new file mode 100644
index 000000000..e4cffa217
--- /dev/null
+++ b/docs/content/usage/migration.md
@@ -0,0 +1,24 @@
+# Migration Guide
+
+This guide helps you migrate from older versions of kube-bind to the latest version with new features and API changes.
+
+## Migration Timeline
+
+### From v0.5.x to v0.6.x+
+
+The v0.6.x release introduces several significant improvements:
+
+- **Catalog API**: New `Collection` and `APIServiceExportTemplate` CRDs for better service organization
+- **Enhanced Permission Claims**: Support for `NamedResources` alongside label selectors
+- **Provider-side Namespace Management**: Automatic RBAC and namespace provisioning
+- **Improved KCP Integration**: Better workspace and APIExport handling
+
+## API Changes
+
+TODO
+
+If you encounter issues during migration:
+
+1. **Check GitHub Issues**: [kube-bind issues](https://github.com/kube-bind/kube-bind/issues)
+2. **Slack Channel**: [`#kube-bind` on Kubernetes Slack](https://kubernetes.slack.com/archives/C046PRXNJ4W)
+3. **Mailing List**: [kube-bind-dev](https://groups.google.com/g/kube-bind-dev)
\ No newline at end of file
diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
index d49f483ff..b77fe3d09 100644
--- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
+++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
@@ -374,7 +374,7 @@ func (c *controller) enqueueServiceNamespace(logger klog.Logger, obj interface{}
// We need to list all the object which might not got synced at consumer side too:
var sel labels.Selector
switch v := c.claim.Selector; {
- case v.LabelSelector == nil && len(v.NamedResource) == 0:
+ case v.LabelSelector == nil && len(v.NamedResources) == 0:
sel = labels.Everything()
case v.LabelSelector != nil:
var err error
@@ -383,9 +383,9 @@ func (c *controller) enqueueServiceNamespace(logger klog.Logger, obj interface{}
runtime.HandleError(err)
return
}
- case len(v.NamedResource) > 0:
+ case len(v.NamedResources) > 0:
// namedResource-only: fetch specific objects from cache and enqueue
- for _, nr := range v.NamedResource {
+ for _, nr := range v.NamedResources {
// Build consumer cache key; empty namespace implies cluster-scoped
key := nr.Name
if nr.Namespace != "" {
diff --git a/pkg/konnector/controllers/cluster/namespacelifecycle/namespacelifecycle_controller.go b/pkg/konnector/controllers/cluster/namespacelifecycle/namespacelifecycle_controller.go
index 7edc6172b..f0b7090ab 100644
--- a/pkg/konnector/controllers/cluster/namespacelifecycle/namespacelifecycle_controller.go
+++ b/pkg/konnector/controllers/cluster/namespacelifecycle/namespacelifecycle_controller.go
@@ -291,7 +291,7 @@ func (c *controller) handleNamespaceLifecycle(ctx context.Context, current *kube
}
// isConsumerOwned checks if the APIServiceNamespace is owned by consumer.
-// If the label is missing, it is considered consumer owned.
+// to prevent the controller from creating namespaces unless explicitly requested).
func isConsumerOwned(sns *kubebindv1alpha2.APIServiceNamespace) bool {
return sns.Labels[kubebindv1alpha2.ObjectOwnerLabel] != kubebindv1alpha2.OwnerProvider.String()
}
diff --git a/pkg/resources/resources.go b/pkg/resources/resources.go
index d37e524c3..dfe8ca561 100644
--- a/pkg/resources/resources.go
+++ b/pkg/resources/resources.go
@@ -30,7 +30,7 @@ func IsClaimed(selector kubebindv1alpha2.Selector, obj *unstructured.Unstructure
return false
}
// Empty selector selects everything
- if selector.LabelSelector == nil && len(selector.NamedResource) == 0 {
+ if selector.LabelSelector == nil && len(selector.NamedResources) == 0 {
return true
}
@@ -52,9 +52,9 @@ func IsClaimed(selector kubebindv1alpha2.Selector, obj *unstructured.Unstructure
}
// Check named resources if specified
- if len(selector.NamedResource) > 0 {
+ if len(selector.NamedResources) > 0 {
namedResourceMatches = false // Default to false, must match at least one
- for _, nr := range selector.NamedResource {
+ for _, nr := range selector.NamedResources {
if nr.Namespace != "" && nr.Namespace != obj.GetNamespace() {
continue
}
diff --git a/pkg/resources/resources_test.go b/pkg/resources/resources_test.go
index a5d0a465c..a1a2a6c8c 100644
--- a/pkg/resources/resources_test.go
+++ b/pkg/resources/resources_test.go
@@ -95,7 +95,7 @@ func TestSelector_IsClaimed(t *testing.T) {
{
name: "named resource selector should match exact name and namespace",
selector: kubebindv1alpha2.Selector{
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "test-obj",
Namespace: "test-ns",
@@ -115,7 +115,7 @@ func TestSelector_IsClaimed(t *testing.T) {
{
name: "named resource selector should match name when namespace is empty",
selector: kubebindv1alpha2.Selector{
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "test-obj",
Namespace: "",
@@ -135,7 +135,7 @@ func TestSelector_IsClaimed(t *testing.T) {
{
name: "named resource selector should not match different name",
selector: kubebindv1alpha2.Selector{
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "other-obj",
Namespace: "test-ns",
@@ -155,7 +155,7 @@ func TestSelector_IsClaimed(t *testing.T) {
{
name: "named resource selector should not match different namespace",
selector: kubebindv1alpha2.Selector{
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "test-obj",
Namespace: "other-ns",
@@ -175,7 +175,7 @@ func TestSelector_IsClaimed(t *testing.T) {
{
name: "named resource selector should match one of multiple resources",
selector: kubebindv1alpha2.Selector{
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "other-obj",
Namespace: "test-ns",
@@ -223,7 +223,7 @@ func TestSelector_IsClaimed(t *testing.T) {
"app": "test",
},
},
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "test-obj",
Namespace: "test-ns",
@@ -251,7 +251,7 @@ func TestSelector_IsClaimed(t *testing.T) {
"app": "different",
},
},
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "test-obj",
Namespace: "test-ns",
@@ -279,7 +279,7 @@ func TestSelector_IsClaimed(t *testing.T) {
"app": "test",
},
},
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "other-obj",
Namespace: "test-ns",
@@ -307,7 +307,7 @@ func TestSelector_IsClaimed(t *testing.T) {
"app": "secrets",
},
},
- NamedResource: []kubebindv1alpha2.NamedResource{
+ NamedResources: []kubebindv1alpha2.NamedResource{
{
Name: "test-secret",
Namespace: "default",
diff --git a/sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go b/sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go
index d95e589e0..068134e07 100644
--- a/sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go
+++ b/sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go
@@ -154,7 +154,7 @@ func (r APIServiceExportRequestResource) ResourceGroupName() string {
type Selector struct {
// NamedResource is a shorthand for selecting a single resource by name and namespace.
// +optional
- NamedResource []NamedResource `json:"namedResource,omitempty"`
+ NamedResources []NamedResource `json:"namedResources,omitempty"`
// LabelSelector is a label selector that selects objects of a GVR.
// +optional
diff --git a/sdk/apis/kubebind/v1alpha2/apiserviceexporttemplate_types.go b/sdk/apis/kubebind/v1alpha2/apiserviceexporttemplate_types.go
new file mode 100644
index 000000000..94623c66e
--- /dev/null
+++ b/sdk/apis/kubebind/v1alpha2/apiserviceexporttemplate_types.go
@@ -0,0 +1,98 @@
+/*
+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 (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ conditionsapi "github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/apis/conditions/v1alpha1"
+)
+
+// APIServiceExportTemplate groups multiple CRDs with related resources (permissionClaims) as a Service definition.
+//
+// +crd
+// +genclient:nonNamespaced
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:resource:scope=Cluster,categories=kube-bind
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="Resources",type="string",JSONPath=`.spec.resources[*].group`
+// +kubebuilder:printcolumn:name="PermissionClaims",type="string",JSONPath=`.spec.permissionClaims[*].resource`
+// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=`.metadata.creationTimestamp`,priority=0
+type APIServiceExportTemplate struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ // spec specifies the template.
+ // +required
+ // +kubebuilder:validation:Required
+ Spec APIServiceExportTemplateSpec `json:"spec"`
+
+ // status contains reconciliation information for the template.
+ Status APIServiceExportTemplateStatus `json:"status,omitempty"`
+}
+
+func (in *APIServiceExportTemplate) GetConditions() conditionsapi.Conditions {
+ return in.Status.Conditions
+}
+
+func (in *APIServiceExportTemplate) SetConditions(conditions conditionsapi.Conditions) {
+ in.Status.Conditions = conditions
+}
+
+// APIServiceExportTemplateSpec defines the desired state of APIServiceExportTemplate.
+type APIServiceExportTemplateSpec struct {
+ // scope defines the scope of the resources in this template.
+ // +required
+ // +kubebuilder:validation:Required
+ // +kubebuilder:validation:Enum=Namespaced;Cluster
+ Scope InformerScope `json:"scope,omitempty"`
+ // resources defines the CRDs that are part of this template.
+ //
+ // +required
+ // +kubebuilder:validation:Required
+ // +kubebuilder:validation:MinItems=1
+ Resources []APIServiceExportResource `json:"resources"`
+
+ // permissionClaims defines the permission claims required by this template.
+ //
+ // +optional
+ PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`
+
+ // namespaces specifies the namespaces that should be bootstrapped as part of this template.
+ // When objects originate from provider side, consumer does not always know the necessary details
+ // This field allows provider to pre-heat the necessary namespaces on provider side by creating
+ // APIServiceNamespace objects attached to the APIServiceExport. More namespaces can be created later by the consumer.
+ //
+ // +optional
+ Namespaces []Namespaces `json:"namespaces,omitempty"`
+}
+
+// APIServiceExportTemplateStatus stores status information about an APIServiceExportTemplate.
+type APIServiceExportTemplateStatus struct {
+ // conditions is a list of conditions that apply to the APIServiceExportTemplate.
+ Conditions conditionsapi.Conditions `json:"conditions,omitempty"`
+}
+
+// APIServiceExportTemplateList is the list of APIServiceExportTemplate.
+//
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+type APIServiceExportTemplateList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata"`
+
+ Items []APIServiceExportTemplate `json:"items"`
+}
diff --git a/sdk/apis/kubebind/v1alpha2/collection_types.go b/sdk/apis/kubebind/v1alpha2/collection_types.go
new file mode 100644
index 000000000..060f16da1
--- /dev/null
+++ b/sdk/apis/kubebind/v1alpha2/collection_types.go
@@ -0,0 +1,94 @@
+/*
+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 (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ conditionsapi "github.com/kube-bind/kube-bind/sdk/apis/third_party/conditions/apis/conditions/v1alpha1"
+)
+
+// Collection groups multiple APIServiceExportTemplates into a logical group. This functions as a folder in the UI.
+//
+// +crd
+// +genclient
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+// +kubebuilder:resource:scope=Cluster,categories=kube-bind
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="Description",type="string",JSONPath=`.spec.description`
+// +kubebuilder:printcolumn:name="Templates",type="string",JSONPath=`.spec.templates[*].name`
+// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=`.metadata.creationTimestamp`,priority=0
+type Collection struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ // spec specifies the collection.
+ // +required
+ // +kubebuilder:validation:Required
+ Spec CollectionSpec `json:"spec"`
+
+ // status contains reconciliation information for the collection.
+ Status CollectionStatus `json:"status,omitempty"`
+}
+
+func (in *Collection) GetConditions() conditionsapi.Conditions {
+ return in.Status.Conditions
+}
+
+func (in *Collection) SetConditions(conditions conditionsapi.Conditions) {
+ in.Status.Conditions = conditions
+}
+
+// CollectionSpec defines the desired state of Collection.
+type CollectionSpec struct {
+ // description is a human readable description of this collection.
+ //
+ // +optional
+ Description string `json:"description,omitempty"`
+
+ // templates is a list of template references that are part of this collection.
+ //
+ // +required
+ // +kubebuilder:validation:Required
+ // +kubebuilder:validation:MinItems=1
+ Templates []APIServiceExportTemplateReference `json:"templates"`
+}
+
+// APIServiceExportTemplateReference references an APIServiceExportTemplate by name.
+type APIServiceExportTemplateReference struct {
+ // name is the name of the template.
+ //
+ // +required
+ // +kubebuilder:validation:Required
+ Name string `json:"name"`
+}
+
+// CollectionStatus stores status information about a Collection.
+type CollectionStatus struct {
+ // conditions is a list of conditions that apply to the Collection.
+ Conditions conditionsapi.Conditions `json:"conditions,omitempty"`
+}
+
+// CollectionList is the list of Collection.
+//
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+type CollectionList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata"`
+
+ Items []Collection `json:"items"`
+}
diff --git a/sdk/apis/kubebind/v1alpha2/register.go b/sdk/apis/kubebind/v1alpha2/register.go
index 90f0e283e..a23947385 100644
--- a/sdk/apis/kubebind/v1alpha2/register.go
+++ b/sdk/apis/kubebind/v1alpha2/register.go
@@ -60,6 +60,10 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&ClusterBindingList{},
&BindingProvider{},
&BindingResponse{},
+ &APIServiceExportTemplate{},
+ &APIServiceExportTemplateList{},
+ &Collection{},
+ &CollectionList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
diff --git a/sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go b/sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go
index eee7f0729..7ac1c67a0 100644
--- a/sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go
+++ b/sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go
@@ -486,6 +486,141 @@ func (in *APIServiceExportStatus) DeepCopy() *APIServiceExportStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *APIServiceExportTemplate) DeepCopyInto(out *APIServiceExportTemplate) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceExportTemplate.
+func (in *APIServiceExportTemplate) DeepCopy() *APIServiceExportTemplate {
+ if in == nil {
+ return nil
+ }
+ out := new(APIServiceExportTemplate)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *APIServiceExportTemplate) 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 *APIServiceExportTemplateList) DeepCopyInto(out *APIServiceExportTemplateList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]APIServiceExportTemplate, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceExportTemplateList.
+func (in *APIServiceExportTemplateList) DeepCopy() *APIServiceExportTemplateList {
+ if in == nil {
+ return nil
+ }
+ out := new(APIServiceExportTemplateList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *APIServiceExportTemplateList) 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 *APIServiceExportTemplateReference) DeepCopyInto(out *APIServiceExportTemplateReference) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceExportTemplateReference.
+func (in *APIServiceExportTemplateReference) DeepCopy() *APIServiceExportTemplateReference {
+ if in == nil {
+ return nil
+ }
+ out := new(APIServiceExportTemplateReference)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *APIServiceExportTemplateSpec) DeepCopyInto(out *APIServiceExportTemplateSpec) {
+ *out = *in
+ if in.Resources != nil {
+ in, out := &in.Resources, &out.Resources
+ *out = make([]APIServiceExportRequestResource, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ if in.PermissionClaims != nil {
+ in, out := &in.PermissionClaims, &out.PermissionClaims
+ *out = make([]PermissionClaim, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ if in.Namespaces != nil {
+ in, out := &in.Namespaces, &out.Namespaces
+ *out = make([]Namespaces, len(*in))
+ copy(*out, *in)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceExportTemplateSpec.
+func (in *APIServiceExportTemplateSpec) DeepCopy() *APIServiceExportTemplateSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(APIServiceExportTemplateSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *APIServiceExportTemplateStatus) DeepCopyInto(out *APIServiceExportTemplateStatus) {
+ *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])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceExportTemplateStatus.
+func (in *APIServiceExportTemplateStatus) DeepCopy() *APIServiceExportTemplateStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(APIServiceExportTemplateStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *APIServiceNamespace) DeepCopyInto(out *APIServiceNamespace) {
*out = *in
@@ -952,6 +1087,111 @@ func (in *ClusterSecretKeyRef) DeepCopy() *ClusterSecretKeyRef {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Collection) DeepCopyInto(out *Collection) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Collection.
+func (in *Collection) DeepCopy() *Collection {
+ if in == nil {
+ return nil
+ }
+ out := new(Collection)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Collection) 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 *CollectionList) DeepCopyInto(out *CollectionList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]Collection, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectionList.
+func (in *CollectionList) DeepCopy() *CollectionList {
+ if in == nil {
+ return nil
+ }
+ out := new(CollectionList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *CollectionList) 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 *CollectionSpec) DeepCopyInto(out *CollectionSpec) {
+ *out = *in
+ if in.Templates != nil {
+ in, out := &in.Templates, &out.Templates
+ *out = make([]APIServiceExportTemplateReference, len(*in))
+ copy(*out, *in)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectionSpec.
+func (in *CollectionSpec) DeepCopy() *CollectionSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(CollectionSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CollectionStatus) DeepCopyInto(out *CollectionStatus) {
+ *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])
+ }
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CollectionStatus.
+func (in *CollectionStatus) DeepCopy() *CollectionStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(CollectionStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomResourceConversion) DeepCopyInto(out *CustomResourceConversion) {
*out = *in
@@ -1141,8 +1381,8 @@ func (in *PermissionClaim) DeepCopy() *PermissionClaim {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Selector) DeepCopyInto(out *Selector) {
*out = *in
- if in.NamedResource != nil {
- in, out := &in.NamedResource, &out.NamedResource
+ if in.NamedResources != nil {
+ in, out := &in.NamedResources, &out.NamedResources
*out = make([]NamedResource, len(*in))
copy(*out, *in)
}
diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/collection.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/collection.go
new file mode 100644
index 000000000..4da65a827
--- /dev/null
+++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/collection.go
@@ -0,0 +1,70 @@
+/*
+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"
+
+ kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
+ scheme "github.com/kube-bind/kube-bind/sdk/client/clientset/versioned/scheme"
+ 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"
+)
+
+// CollectionsGetter has a method to return a CollectionInterface.
+// A group's client should implement this interface.
+type CollectionsGetter interface {
+ Collections(namespace string) CollectionInterface
+}
+
+// CollectionInterface has methods to work with Collection resources.
+type CollectionInterface interface {
+ Create(ctx context.Context, collection *kubebindv1alpha2.Collection, opts v1.CreateOptions) (*kubebindv1alpha2.Collection, error)
+ Update(ctx context.Context, collection *kubebindv1alpha2.Collection, opts v1.UpdateOptions) (*kubebindv1alpha2.Collection, error)
+ // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
+ UpdateStatus(ctx context.Context, collection *kubebindv1alpha2.Collection, opts v1.UpdateOptions) (*kubebindv1alpha2.Collection, 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.Collection, error)
+ List(ctx context.Context, opts v1.ListOptions) (*kubebindv1alpha2.CollectionList, 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.Collection, err error)
+ CollectionExpansion
+}
+
+// collections implements CollectionInterface
+type collections struct {
+ *gentype.ClientWithList[*kubebindv1alpha2.Collection, *kubebindv1alpha2.CollectionList]
+}
+
+// newCollections returns a Collections
+func newCollections(c *KubeBindV1alpha2Client, namespace string) *collections {
+ return &collections{
+ gentype.NewClientWithList[*kubebindv1alpha2.Collection, *kubebindv1alpha2.CollectionList](
+ "collections",
+ c.RESTClient(),
+ scheme.ParameterCodec,
+ namespace,
+ func() *kubebindv1alpha2.Collection { return &kubebindv1alpha2.Collection{} },
+ func() *kubebindv1alpha2.CollectionList { return &kubebindv1alpha2.CollectionList{} },
+ ),
+ }
+}
diff --git a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_collection.go b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_collection.go
new file mode 100644
index 000000000..eb75906c7
--- /dev/null
+++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/fake/fake_collection.go
@@ -0,0 +1,50 @@
+/*
+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 (
+ 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"
+ gentype "k8s.io/client-go/gentype"
+)
+
+// fakeCollections implements CollectionInterface
+type fakeCollections struct {
+ *gentype.FakeClientWithList[*v1alpha2.Collection, *v1alpha2.CollectionList]
+ Fake *FakeKubeBindV1alpha2
+}
+
+func newFakeCollections(fake *FakeKubeBindV1alpha2, namespace string) kubebindv1alpha2.CollectionInterface {
+ return &fakeCollections{
+ gentype.NewFakeClientWithList[*v1alpha2.Collection, *v1alpha2.CollectionList](
+ fake.Fake,
+ namespace,
+ v1alpha2.SchemeGroupVersion.WithResource("collections"),
+ v1alpha2.SchemeGroupVersion.WithKind("Collection"),
+ func() *v1alpha2.Collection { return &v1alpha2.Collection{} },
+ func() *v1alpha2.CollectionList { return &v1alpha2.CollectionList{} },
+ func(dst, src *v1alpha2.CollectionList) { dst.ListMeta = src.ListMeta },
+ func(list *v1alpha2.CollectionList) []*v1alpha2.Collection { return gentype.ToPointerSlice(list.Items) },
+ func(list *v1alpha2.CollectionList, items []*v1alpha2.Collection) {
+ list.Items = gentype.FromPointerSlice(items)
+ },
+ ),
+ fake,
+ }
+}
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 d1661b376..17d65407f 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
@@ -52,6 +52,10 @@ func (c *FakeKubeBindV1alpha2) ClusterBindings(namespace string) v1alpha2.Cluste
return newFakeClusterBindings(c, namespace)
}
+func (c *FakeKubeBindV1alpha2) Collections(namespace string) v1alpha2.CollectionInterface {
+ return newFakeCollections(c, namespace)
+}
+
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeKubeBindV1alpha2) RESTClient() rest.Interface {
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 d0968607d..3be2b1782 100644
--- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/generated_expansion.go
+++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/generated_expansion.go
@@ -29,3 +29,5 @@ type APIServiceNamespaceExpansion interface{}
type BoundSchemaExpansion interface{}
type ClusterBindingExpansion interface{}
+
+type CollectionExpansion 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 6d26561d6..d31e864bb 100644
--- a/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/kubebind_client.go
+++ b/sdk/client/clientset/versioned/typed/kubebind/v1alpha2/kubebind_client.go
@@ -34,6 +34,7 @@ type KubeBindV1alpha2Interface interface {
APIServiceNamespacesGetter
BoundSchemasGetter
ClusterBindingsGetter
+ CollectionsGetter
}
// KubeBindV1alpha2Client is used to interact with features provided by the kube-bind.io group.
@@ -65,6 +66,10 @@ func (c *KubeBindV1alpha2Client) ClusterBindings(namespace string) ClusterBindin
return newClusterBindings(c, namespace)
}
+func (c *KubeBindV1alpha2Client) Collections(namespace string) CollectionInterface {
+ return newCollections(c, namespace)
+}
+
// NewForConfig creates a new KubeBindV1alpha2Client for the given config.
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
// where httpClient was generated with rest.HTTPClientFor(c).
diff --git a/sdk/client/informers/externalversions/generic.go b/sdk/client/informers/externalversions/generic.go
index 27b112efb..fa39a9633 100644
--- a/sdk/client/informers/externalversions/generic.go
+++ b/sdk/client/informers/externalversions/generic.go
@@ -78,6 +78,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
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
+ case v1alpha2.SchemeGroupVersion.WithResource("collections"):
+ return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha2().Collections().Informer()}, nil
}
diff --git a/sdk/client/informers/externalversions/kubebind/v1alpha2/collection.go b/sdk/client/informers/externalversions/kubebind/v1alpha2/collection.go
new file mode 100644
index 000000000..3f5932ac8
--- /dev/null
+++ b/sdk/client/informers/externalversions/kubebind/v1alpha2/collection.go
@@ -0,0 +1,102 @@
+/*
+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"
+
+ 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"
+ 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"
+)
+
+// CollectionInformer provides access to a shared informer and lister for
+// Collections.
+type CollectionInformer interface {
+ Informer() cache.SharedIndexInformer
+ Lister() kubebindv1alpha2.CollectionLister
+}
+
+type collectionInformer struct {
+ factory internalinterfaces.SharedInformerFactory
+ tweakListOptions internalinterfaces.TweakListOptionsFunc
+ namespace string
+}
+
+// NewCollectionInformer constructs a new informer for Collection 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 NewCollectionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
+ return NewFilteredCollectionInformer(client, namespace, resyncPeriod, indexers, nil)
+}
+
+// NewFilteredCollectionInformer constructs a new informer for Collection 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 NewFilteredCollectionInformer(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().Collections(namespace).List(context.Background(), options)
+ },
+ WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.KubeBindV1alpha2().Collections(namespace).Watch(context.Background(), options)
+ },
+ ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.KubeBindV1alpha2().Collections(namespace).List(ctx, options)
+ },
+ WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) {
+ if tweakListOptions != nil {
+ tweakListOptions(&options)
+ }
+ return client.KubeBindV1alpha2().Collections(namespace).Watch(ctx, options)
+ },
+ },
+ &apiskubebindv1alpha2.Collection{},
+ resyncPeriod,
+ indexers,
+ )
+}
+
+func (f *collectionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
+ return NewFilteredCollectionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
+}
+
+func (f *collectionInformer) Informer() cache.SharedIndexInformer {
+ return f.factory.InformerFor(&apiskubebindv1alpha2.Collection{}, f.defaultInformer)
+}
+
+func (f *collectionInformer) Lister() kubebindv1alpha2.CollectionLister {
+ return kubebindv1alpha2.NewCollectionLister(f.Informer().GetIndexer())
+}
diff --git a/sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go b/sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go
index e4abc8bb5..1e7187ba9 100644
--- a/sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go
+++ b/sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go
@@ -36,6 +36,8 @@ type Interface interface {
BoundSchemas() BoundSchemaInformer
// ClusterBindings returns a ClusterBindingInformer.
ClusterBindings() ClusterBindingInformer
+ // Collections returns a CollectionInformer.
+ Collections() CollectionInformer
}
type version struct {
@@ -78,3 +80,8 @@ func (v *version) BoundSchemas() BoundSchemaInformer {
func (v *version) ClusterBindings() ClusterBindingInformer {
return &clusterBindingInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
+
+// Collections returns a CollectionInformer.
+func (v *version) Collections() CollectionInformer {
+ return &collectionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
+}
diff --git a/sdk/client/listers/kubebind/v1alpha2/collection.go b/sdk/client/listers/kubebind/v1alpha2/collection.go
new file mode 100644
index 000000000..6ff8440be
--- /dev/null
+++ b/sdk/client/listers/kubebind/v1alpha2/collection.go
@@ -0,0 +1,70 @@
+/*
+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 (
+ kubebindv1alpha2 "github.com/kube-bind/kube-bind/sdk/apis/kubebind/v1alpha2"
+ labels "k8s.io/apimachinery/pkg/labels"
+ listers "k8s.io/client-go/listers"
+ cache "k8s.io/client-go/tools/cache"
+)
+
+// CollectionLister helps list Collections.
+// All objects returned here must be treated as read-only.
+type CollectionLister interface {
+ // List lists all Collections in the indexer.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*kubebindv1alpha2.Collection, err error)
+ // Collections returns an object that can list and get Collections.
+ Collections(namespace string) CollectionNamespaceLister
+ CollectionListerExpansion
+}
+
+// collectionLister implements the CollectionLister interface.
+type collectionLister struct {
+ listers.ResourceIndexer[*kubebindv1alpha2.Collection]
+}
+
+// NewCollectionLister returns a new CollectionLister.
+func NewCollectionLister(indexer cache.Indexer) CollectionLister {
+ return &collectionLister{listers.New[*kubebindv1alpha2.Collection](indexer, kubebindv1alpha2.Resource("collection"))}
+}
+
+// Collections returns an object that can list and get Collections.
+func (s *collectionLister) Collections(namespace string) CollectionNamespaceLister {
+ return collectionNamespaceLister{listers.NewNamespaced[*kubebindv1alpha2.Collection](s.ResourceIndexer, namespace)}
+}
+
+// CollectionNamespaceLister helps list and get Collections.
+// All objects returned here must be treated as read-only.
+type CollectionNamespaceLister interface {
+ // List lists all Collections in the indexer for a given namespace.
+ // Objects returned here must be treated as read-only.
+ List(selector labels.Selector) (ret []*kubebindv1alpha2.Collection, err error)
+ // Get retrieves the Collection from the indexer for a given namespace and name.
+ // Objects returned here must be treated as read-only.
+ Get(name string) (*kubebindv1alpha2.Collection, error)
+ CollectionNamespaceListerExpansion
+}
+
+// collectionNamespaceLister implements the CollectionNamespaceLister
+// interface.
+type collectionNamespaceLister struct {
+ listers.ResourceIndexer[*kubebindv1alpha2.Collection]
+}
diff --git a/sdk/client/listers/kubebind/v1alpha2/expansion_generated.go b/sdk/client/listers/kubebind/v1alpha2/expansion_generated.go
index 3aaa58aa1..fc8aa4060 100644
--- a/sdk/client/listers/kubebind/v1alpha2/expansion_generated.go
+++ b/sdk/client/listers/kubebind/v1alpha2/expansion_generated.go
@@ -61,3 +61,11 @@ type ClusterBindingListerExpansion interface{}
// ClusterBindingNamespaceListerExpansion allows custom methods to be added to
// ClusterBindingNamespaceLister.
type ClusterBindingNamespaceListerExpansion interface{}
+
+// CollectionListerExpansion allows custom methods to be added to
+// CollectionLister.
+type CollectionListerExpansion interface{}
+
+// CollectionNamespaceListerExpansion allows custom methods to be added to
+// CollectionNamespaceLister.
+type CollectionNamespaceListerExpansion interface{}
diff --git a/test/e2e/bind/fixtures/provider/collection.yaml b/test/e2e/bind/fixtures/provider/collection.yaml
new file mode 100644
index 000000000..a456e1a06
--- /dev/null
+++ b/test/e2e/bind/fixtures/provider/collection.yaml
@@ -0,0 +1,8 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: Collection
+metadata:
+ name: all
+spec:
+ templates:
+ - name: mangodb
+ - name: foo
\ No newline at end of file
diff --git a/test/e2e/bind/fixtures/provider/template-foo.yaml b/test/e2e/bind/fixtures/provider/template-foo.yaml
new file mode 100644
index 000000000..bc2d87393
--- /dev/null
+++ b/test/e2e/bind/fixtures/provider/template-foo.yaml
@@ -0,0 +1,33 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: APIServiceExportTemplate
+metadata:
+ name: foo
+spec:
+ scope: Cluster
+ resources:
+ - group: bar.io
+ versions:
+ - v1alpha1
+ resource: foos
+ permissionClaims:
+ - group: ""
+ resource: configmaps
+ selector:
+ namedResources:
+ - name: named-configmap-only
+ namespace: default
+ - group: ""
+ resource: secrets
+ selector:
+ labelSelector:
+ matchLabels:
+ app: secrets
+ namedResources:
+ - name: test-secret
+ namespace: default
+ - name: named-secret-1
+ namespace: default
+ - name: named-secret-2
+ namespace: default
+ namespaces:
+ - name: consumer-configuration-ns
\ No newline at end of file
diff --git a/test/e2e/bind/fixtures/provider/template-mangodb.yaml b/test/e2e/bind/fixtures/provider/template-mangodb.yaml
new file mode 100644
index 000000000..888d8ffdc
--- /dev/null
+++ b/test/e2e/bind/fixtures/provider/template-mangodb.yaml
@@ -0,0 +1,33 @@
+apiVersion: kube-bind.io/v1alpha2
+kind: APIServiceExportTemplate
+metadata:
+ name: mangodb
+spec:
+ scope: Namespaced
+ resources:
+ - group: mangodb.com
+ versions:
+ - v1alpha1
+ resource: mangodbs
+ permissionClaims:
+ - group: ""
+ resource: configmaps
+ selector:
+ namedResources:
+ - name: named-configmap-only
+ namespace: default
+ - group: ""
+ resource: secrets
+ selector:
+ labelSelector:
+ matchLabels:
+ app: secrets
+ namedResources:
+ - name: test-secret
+ namespace: default
+ - name: named-secret-1
+ namespace: default
+ - name: named-secret-2
+ namespace: default
+ namespaces:
+ - name: consumer-configuration-ns
\ No newline at end of file
diff --git a/test/e2e/bind/happy-case_test.go b/test/e2e/bind/happy-case_test.go
index 780937e55..06ded2270 100644
--- a/test/e2e/bind/happy-case_test.go
+++ b/test/e2e/bind/happy-case_test.go
@@ -18,7 +18,6 @@ package bind
import (
"context"
- "encoding/json"
"fmt"
"strings"
"testing"
@@ -144,65 +143,6 @@ spec:
inv := <-invocations
requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "-", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args)
- var request kubebindv1alpha2.APIServiceExportRequest
- err := json.Unmarshal(inv.Stdin, &request)
- require.NoError(t, err)
- // Pre-seed provider side namespaces for secret management
- request.Spec.Namespaces = []kubebindv1alpha2.Namespaces{
- {
- Name: "consumer-secrets-ns",
- },
- }
-
- // If we are in permissions claims mode - add configmaps & secrets
- request.Spec.PermissionClaims = []kubebindv1alpha2.PermissionClaim{
- {
- GroupResource: kubebindv1alpha2.GroupResource{
- Group: "",
- Resource: "configmaps",
- },
- Selector: kubebindv1alpha2.Selector{
- NamedResource: []kubebindv1alpha2.NamedResource{
- {
- Name: "named-configmap-only",
- Namespace: consumerNS,
- },
- },
- },
- },
- {
- GroupResource: kubebindv1alpha2.GroupResource{
- Group: "",
- Resource: "secrets",
- },
- Selector: kubebindv1alpha2.Selector{
- LabelSelector: &metav1.LabelSelector{
- MatchLabels: map[string]string{
- "app": "secrets",
- },
- },
- NamedResource: []kubebindv1alpha2.NamedResource{
- {
- Name: "test-secret",
- Namespace: consumerNS,
- },
- {
- Name: "named-secret-1",
- Namespace: consumerNS,
- },
- {
- Name: "named-secret-2",
- Namespace: consumerNS,
- },
- },
- },
- },
- }
-
- payload, err := json.Marshal(request)
- require.NoError(t, err)
- inv.Stdin = payload
-
framework.BindAPIService(t, inv.Stdin, "", inv.Args...)
t.Logf("Waiting for %s CRD to be created on consumer side", serviceGVR.Resource)
@@ -262,7 +202,7 @@ spec:
}
for _, ns := range namespaces.Items {
- if ns.Name == "consumer-secrets-ns" && ns.Status.Namespace != "" {
+ if ns.Name == "consumer-configuration-ns" && ns.Status.Namespace != "" {
actualProviderNamespace = ns.Status.Namespace
foundPreSeededNamespace = true
return true
@@ -270,7 +210,7 @@ spec:
}
return false
}, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for pre-seeded APIServiceNamespace to be created on provider side")
- require.True(t, foundPreSeededNamespace, "Pre-seeded namespace 'consumer-secrets-ns' should be created via APIServiceExportRequest.Spec.Namespaces")
+ require.True(t, foundPreSeededNamespace, "Pre-seeded namespace 'consumer-configuration-ns' should be created via APIServiceExportRequest.Spec.Namespaces")
providerCoreClient := framework.KubeClient(t, providerConfig).CoreV1()
_, err := providerCoreClient.Namespaces().Get(ctx, actualProviderNamespace, metav1.GetOptions{})
@@ -286,7 +226,7 @@ spec:
var foundSecretClusterRole bool
for _, cr := range clusterRoles.Items {
- if strings.Contains(cr.Name, "kube-binder-") && strings.Contains(cr.Name, "-export-") {
+ if strings.Contains(cr.Name, "kube-binder-export") {
for _, rule := range cr.Rules {
for _, resource := range rule.Resources {
if resource == "secrets" {
@@ -808,8 +748,17 @@ func simulateBrowser(t *testing.T, authURLCh chan string, resource string) {
t.Logf("Waiting for browser to be at /resources")
framework.BrowserEventuallyAtPath(t, browser, "/resources")
- t.Logf("Clicking %s", resource)
- err = browser.Click("a." + resource)
+ // Convert resource name to template name
+ var templateName string
+ switch resource {
+ case "mangodbs":
+ templateName = "mangodb"
+ case "foos":
+ templateName = "foo"
+ }
+
+ t.Logf("Clicking template %s", templateName)
+ err = browser.Click("a." + templateName)
require.NoError(t, err)
t.Logf("Waiting for browser to be forwarded to client")
diff --git a/test/e2e/framework/backend.go b/test/e2e/framework/backend.go
index a29d26cce..2e0f4fc6b 100644
--- a/test/e2e/framework/backend.go
+++ b/test/e2e/framework/backend.go
@@ -45,6 +45,8 @@ func InstallKubebindCRDs(t testing.TB, clientConfig *rest.Config) {
metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "apiservicenamespaces"},
metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "apiserviceexportrequests"},
metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "boundschemas"},
+ metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "apiserviceexporttemplates"},
+ metav1.GroupResource{Group: kubebindv1alpha2.GroupName, Resource: "collections"},
)
require.NoError(t, err)
}