From aa5bf7001b0cf457a4d31dfbcf479d8b08a9da80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 23 Feb 2023 10:09:24 +0100 Subject: [PATCH 01/26] Add fields for claimed resources Add new type APIServiceExportTemplate which provides a template for new APIServiceExports the type associates the claimed resources with the CRD Add claimed resource information to APIBinding and APIExport Current Limitation: Validation is commented out because the e2e testing infrastructure rejected the validation for being too computationally expensive --- .../crd/kube-bind.io_apiservicebindings.yaml | 72 ++++++ ...kube-bind.io_apiserviceexportrequests.yaml | 71 ++++++ .../crd/kube-bind.io_apiserviceexports.yaml | 73 ++++++ ...ube-bind.io_apiserviceexporttemplates.yaml | 140 +++++++++++ .../v1alpha1/apiservicebinding_types.go | 81 +++++++ .../v1alpha1/apiserviceexport_types.go | 7 + .../v1alpha1/apiserviceexportrequest_types.go | 3 +- .../apiserviceexporttemplate_types.go | 70 ++++++ .../v1alpha1/helpers/serviceexport.go | 4 + pkg/apis/kubebind/v1alpha1/register.go | 2 + .../v1alpha1/zz_generated.deepcopy.go | 227 +++++++++++++++++- .../v1alpha1/apiserviceexporttemplate.go | 196 +++++++++++++++ .../fake/fake_apiserviceexporttemplate.go | 143 +++++++++++ .../v1alpha1/fake/fake_kubebind_client.go | 4 + .../kubebind/v1alpha1/generated_expansion.go | 2 + .../kubebind/v1alpha1/kubebind_client.go | 5 + .../informers/externalversions/generic.go | 2 + .../v1alpha1/apiserviceexporttemplate.go | 91 +++++++ .../kubebind/v1alpha1/interface.go | 7 + .../v1alpha1/apiserviceexporttemplate.go | 100 ++++++++ .../kubebind/v1alpha1/expansion_generated.go | 8 + 21 files changed, 1306 insertions(+), 2 deletions(-) create mode 100644 deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml create mode 100644 pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go create mode 100644 pkg/client/clientset/versioned/typed/kubebind/v1alpha1/apiserviceexporttemplate.go create mode 100644 pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_apiserviceexporttemplate.go create mode 100644 pkg/client/informers/externalversions/kubebind/v1alpha1/apiserviceexporttemplate.go create mode 100644 pkg/client/listers/kubebind/v1alpha1/apiserviceexporttemplate.go diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index 132432963..b510e0b7c 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -84,6 +84,78 @@ spec: x-kubernetes-validations: - message: kubeconfigSecretRef is immutable rule: self == oldSelf + permissionClaims: + description: permissionClaims records decisions about permission claims + requested by the API service provider. Individual claims can be + accepted or rejected. If accepted, the API service provider gets + the requested access to the specified resources in this workspace. + Access is granted per GroupResource, identity, and other properties. + items: + properties: + all: + description: all claims all resources for the given group/resource. + This is mutually exclusive with resourceSelector. + type: boolean + 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 + resourceSelector: + description: resourceSelector is a list of claimed resource + selectors. + items: + properties: + name: + description: name of an object within a claimed group/resource. + It matches the metadata.name field of the underlying + object. If namespace is unset, all objects matching + that name will be claimed. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ + type: string + namespace: + description: namespace containing the named object. Matches + metadata.namespace field. If "name" is unset, all objects + from the namespace are being claimed. + minLength: 1 + type: string + type: object + x-kubernetes-validations: + - message: at least one field must be set + rule: has(self.__namespace__) || has(self.name) + type: array + state: + enum: + - Accepted + - Rejected + type: string + verbs: + properties: + consumer: + items: + type: string + type: array + provider: + items: + type: string + type: array + type: object + version: + type: string + required: + - resource + - state + type: object + type: array required: - kubeconfigSecretRef type: object diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 7a38c14f2..20e473677 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -63,6 +63,77 @@ spec: 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 + permissionClaims: + items: + description: "PermissionClaim identifies an object by GR and + identity hash. Its purpose is to determine the added permissions + that a service provider may request and that a consumer + may accept and allow the service provider access to. \n + TODO fix validation kubebuilder:validation:XValidation:rule=\"(has(self.all) + && self.all) != (has(self.resourceSelector) && size(self.resourceSelector) + > 0)\",message=\"either \\\"all\\\" or \\\"resourceSelector\\\" + must be set\"" + properties: + all: + description: all claims all resources for the given group/resource. + This is mutually exclusive with resourceSelector. + type: boolean + 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 + resourceSelector: + description: resourceSelector is a list of claimed resource + selectors. + items: + properties: + name: + description: name of an object within a claimed + group/resource. It matches the metadata.name field + of the underlying object. If namespace is unset, + all objects matching that name will be claimed. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ + type: string + namespace: + description: namespace containing the named object. + Matches metadata.namespace field. If "name" is + unset, all objects from the namespace are being + claimed. + minLength: 1 + type: string + type: object + x-kubernetes-validations: + - message: at least one field must be set + rule: has(self.__namespace__) || has(self.name) + type: array + verbs: + properties: + consumer: + items: + type: string + type: array + provider: + items: + type: string + type: array + type: object + version: + type: string + required: + - resource + type: object + type: array resource: description: 'resource is the name of the resource. Note: it is worth noting that you can not ask for permissions for resource diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index 6b5a75827..90020b6b9 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -108,6 +108,79 @@ spec: - kind - plural type: object + permissionClaims: + items: + description: "PermissionClaim identifies an object by GR and identity + hash. Its purpose is to determine the added permissions that a + service provider may request and that a consumer may accept and + allow the service provider access to. \n TODO fix validation kubebuilder:validation:XValidation:rule=\"(has(self.all) + && self.all) != (has(self.resourceSelector) && size(self.resourceSelector) + > 0)\",message=\"either \\\"all\\\" or \\\"resourceSelector\\\" + must be set\"" + properties: + all: + description: all claims all resources for the given group/resource. + This is mutually exclusive with resourceSelector. + type: boolean + 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 + resourceSelector: + description: resourceSelector is a list of claimed resource + selectors. + items: + properties: + name: + description: name of an object within a claimed group/resource. + It matches the metadata.name field of the underlying + object. If namespace is unset, all objects matching + that name will be claimed. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ + type: string + namespace: + description: namespace containing the named object. Matches + metadata.namespace field. If "name" is unset, all objects + from the namespace are being claimed. + minLength: 1 + type: string + type: object + x-kubernetes-validations: + - message: at least one field must be set + rule: has(self.__namespace__) || has(self.name) + type: array + verbs: + properties: + consumer: + items: + type: string + type: array + provider: + items: + type: string + type: array + type: object + version: + type: string + required: + - resource + type: object + maxItems: 2 + type: array + x-kubernetes-list-map-keys: + - group + - resource + x-kubernetes-list-type: map scope: description: scope indicates whether the defined custom resource is cluster- or namespace-scoped. Allowed values are `Cluster` and `Namespaced`. diff --git a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml new file mode 100644 index 000000000..73c159490 --- /dev/null +++ b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml @@ -0,0 +1,140 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: apiserviceexporttemplates.kube-bind.io +spec: + group: kube-bind.io + names: + categories: + - kube-bindings + kind: APIServiceExportTemplate + listKind: APIServiceExportTemplateList + plural: apiserviceexporttemplates + singular: apiserviceexporttemplate + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Established")].status + name: Established + priority: 5 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: APIServiceExportTemplate specifies the resource to be exported. + It references the CRD to be exported along with additional resources that + are synchronized from and to the consumer cluster. + 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 resource. + properties: + APIServiceSelector: + properties: + group: + type: string + resource: + type: string + versions: + items: + type: string + type: array + type: object + claimedResources: + items: + description: "PermissionClaim identifies an object by GR and identity + hash. Its purpose is to determine the added permissions that a + service provider may request and that a consumer may accept and + allow the service provider access to. \n TODO fix validation kubebuilder:validation:XValidation:rule=\"(has(self.all) + && self.all) != (has(self.resourceSelector) && size(self.resourceSelector) + > 0)\",message=\"either \\\"all\\\" or \\\"resourceSelector\\\" + must be set\"" + properties: + all: + description: all claims all resources for the given group/resource. + This is mutually exclusive with resourceSelector. + type: boolean + 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 + resourceSelector: + description: resourceSelector is a list of claimed resource + selectors. + items: + properties: + name: + description: name of an object within a claimed group/resource. + It matches the metadata.name field of the underlying + object. If namespace is unset, all objects matching + that name will be claimed. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ + type: string + namespace: + description: namespace containing the named object. Matches + metadata.namespace field. If "name" is unset, all objects + from the namespace are being claimed. + minLength: 1 + type: string + type: object + x-kubernetes-validations: + - message: at least one field must be set + rule: has(self.__namespace__) || has(self.name) + type: array + verbs: + properties: + consumer: + items: + type: string + type: array + provider: + items: + type: string + type: array + type: object + version: + type: string + required: + - resource + type: object + maxItems: 2 + type: array + type: object + status: + description: status contains reconciliation information for the resource. + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 4a664a7af..1d8827558 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -87,8 +87,89 @@ type APIServiceBindingSpec struct { // +kubebuilder:validation:Required // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="kubeconfigSecretRef is immutable" KubeconfigSecretRef ClusterSecretKeyRef `json:"kubeconfigSecretRef"` + + // permissionClaims records decisions about permission claims requested by the API service provider. + // Individual claims can be accepted or rejected. If accepted, the API service provider gets the + // requested access to the specified resources in this workspace. Access is granted per + // GroupResource, identity, and other properties. + // + // +optional + PermissionClaims []AcceptablePermissionClaim `json:"permissionClaims,omitempty"` } +type AcceptablePermissionClaim struct { + PermissionClaim `json:",inline"` + + // state indicates if the claim is accepted or rejected. + + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=Accepted;Rejected + State AcceptablePermissionClaimState `json:"state"` +} + +type AcceptablePermissionClaimState string + +const ( + ClaimAccepted AcceptablePermissionClaimState = "Accepted" + ClaimRejected AcceptablePermissionClaimState = "Rejected" +) + +// PermissionClaim identifies an object by GR and identity hash. +// Its purpose is to determine the added permissions that a service provider may +// request and that a consumer may accept and allow the service provider access to. +// +// TODO fix validation +// kubebuilder:validation:XValidation:rule="(has(self.all) && self.all) != (has(self.resourceSelector) && size(self.resourceSelector) > 0)",message="either \"all\" or \"resourceSelector\" must be set" +type PermissionClaim struct { + GroupResource `json:","` + + Version string `json:"version"` + + // all claims all resources for the given group/resource. + // This is mutually exclusive with resourceSelector. + // +optional + All bool `json:"all,omitempty"` + + Verbs ClaimVerbs `json:"verbs"` + + // resourceSelector is a list of claimed resource selectors. + // + // +optional + ResourceSelector []ResourceSelector `json:"resourceSelector,omitempty"` +} + +// +kubebuilder:validation:XValidation:rule="has(self.__namespace__) || has(self.name)",message="at least one field must be set" +type ResourceSelector struct { + // name of an object within a claimed group/resource. + // It matches the metadata.name field of the underlying object. + // If namespace is unset, all objects matching that name will be claimed. + // + // +optional + // +kubebuilder:validation:Pattern="^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$" + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:MinLength=1 + Name string `json:"name,omitempty"` + + // namespace containing the named object. Matches metadata.namespace field. + // If "name" is unset, all objects from the namespace are being claimed. + // + // +optional + // +kubebuilder:validation:MinLength=1 + Namespace string `json:"namespace,omitempty"` + + // + // WARNING: If adding new fields, add them to the XValidation check! + // +} + +type ClaimVerbs struct { + Provider []Verb `json:"provider"` + Consumer []Verb `json:"consumer"` +} + +type Verb string + type APIServiceBindingStatus struct { // providerPrettyName is the pretty name of the service provider cluster. This // can be shared among different APIServiceBindings. diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexport_types.go b/pkg/apis/kubebind/v1alpha1/apiserviceexport_types.go index a802ad167..ffa0a1e7b 100644 --- a/pkg/apis/kubebind/v1alpha1/apiserviceexport_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiserviceexport_types.go @@ -79,6 +79,13 @@ func (in *APIServiceExport) SetConditions(conditions conditionsapi.Conditions) { type APIServiceExportSpec struct { APIServiceExportCRDSpec `json:",inline"` + // +optional + // +listType=map + // +listMapKey=group + // +listMapKey=resource + // +kubebuilder:validation:MaxItems=2 + PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` + // informerScope is the scope of the APIServiceExport. It can be either Cluster or Namespace. // // Cluster: The konnector has permission to watch all namespaces at once and cluster-scoped resources. diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go b/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go index f5234bb05..5200eb8e7 100644 --- a/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go @@ -109,7 +109,8 @@ type APIServiceExportRequestResource struct { // versions is a list of versions that should be exported. If this is empty // a sensible default is chosen by the service provider. - Versions []string `json:"versions,omitempty"` + Versions []string `json:"versions,omitempty"` + PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` } // GroupResource identifies a resource. diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go b/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go new file mode 100644 index 000000000..1070cbef3 --- /dev/null +++ b/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go @@ -0,0 +1,70 @@ +/* +Copyright 2022 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// APIServiceExportTemplate specifies the resource to be exported. +// It references the CRD to be exported along with additional resources that +// are synchronized from and to the consumer cluster. +// +// +crd +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:resource:scope=Namespaced,categories=kube-bindings +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Established",type="string",JSONPath=`.status.conditions[?(@.type=="Established")].status`,priority=5 +// +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 resource. + // +required + // +kubebuilder:validation:Required + Spec APIServiceExportTemplateSpec `json:"spec"` + + // status contains reconciliation information for the resource. + Status APIServiceExportTemplateStatus `json:"status,omitempty"` +} + +type APIServiceExportTemplateSpec struct { + APIServiceSelector APIServiceSelector `json:"APIServiceSelector"` + // +optional + // +kubebuilder:validation:MaxItems=2 + ClaimedResources []PermissionClaim `json:"claimedResources,omitempty"` +} + +type APIServiceExportTemplateStatus struct{} + +type APIServiceSelector struct { + Group string `json:"group"` + Resource string `json:"resource"` + Versions []string `json:"versions"` +} + +// APIServiceExportRequestList is the list of APIServiceExportRequest. +// +// +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/pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go b/pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go index 8b7cade28..4524ede7f 100644 --- a/pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go +++ b/pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go @@ -90,6 +90,10 @@ func CRDToServiceExport(crd *apiextensionsv1.CustomResourceDefinition) (*kubebin if !crdVersion.Served { continue } + // TODO + if onlyFirstServingVersion && !crdVersion.Storage { + continue + } apiResourceVersion := kubebindv1alpha1.APIServiceExportVersion{ Name: crdVersion.Name, diff --git a/pkg/apis/kubebind/v1alpha1/register.go b/pkg/apis/kubebind/v1alpha1/register.go index 40cc76723..1db69012d 100644 --- a/pkg/apis/kubebind/v1alpha1/register.go +++ b/pkg/apis/kubebind/v1alpha1/register.go @@ -48,6 +48,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &APIServiceExport{}, &APIServiceExportList{}, + &APIServiceExportTemplate{}, + &APIServiceExportTemplateList{}, &APIServiceBinding{}, &APIServiceBindingList{}, &APIServiceExportRequest{}, diff --git a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go index f16e3d1d0..c77bc27c6 100644 --- a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go @@ -33,7 +33,7 @@ func (in *APIServiceBinding) DeepCopyInto(out *APIServiceBinding) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) return } @@ -93,6 +93,13 @@ func (in *APIServiceBindingList) DeepCopyObject() runtime.Object { func (in *APIServiceBindingSpec) DeepCopyInto(out *APIServiceBindingSpec) { *out = *in out.KubeconfigSecretRef = in.KubeconfigSecretRef + if in.PermissionClaims != nil { + in, out := &in.PermissionClaims, &out.PermissionClaims + *out = make([]AcceptablePermissionClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -284,6 +291,13 @@ func (in *APIServiceExportRequestResource) DeepCopyInto(out *APIServiceExportReq *out = make([]string, len(*in)) copy(*out, *in) } + if in.PermissionClaims != nil { + in, out := &in.PermissionClaims, &out.PermissionClaims + *out = make([]PermissionClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -397,6 +411,13 @@ func (in *APIServiceExportSchema) DeepCopy() *APIServiceExportSchema { func (in *APIServiceExportSpec) DeepCopyInto(out *APIServiceExportSpec) { *out = *in in.APIServiceExportCRDSpec.DeepCopyInto(&out.APIServiceExportCRDSpec) + if in.PermissionClaims != nil { + in, out := &in.PermissionClaims, &out.PermissionClaims + *out = make([]PermissionClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -439,6 +460,107 @@ 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) + out.Status = in.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 *APIServiceExportTemplateSpec) DeepCopyInto(out *APIServiceExportTemplateSpec) { + *out = *in + in.APIServiceSelector.DeepCopyInto(&out.APIServiceSelector) + if in.ClaimedResources != nil { + in, out := &in.ClaimedResources, &out.ClaimedResources + *out = make([]PermissionClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + 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 + 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 *APIServiceExportVersion) DeepCopyInto(out *APIServiceExportVersion) { *out = *in @@ -560,6 +682,44 @@ func (in *APIServiceNamespaceStatus) DeepCopy() *APIServiceNamespaceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIServiceSelector) DeepCopyInto(out *APIServiceSelector) { + *out = *in + if in.Versions != nil { + in, out := &in.Versions, &out.Versions + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceSelector. +func (in *APIServiceSelector) DeepCopy() *APIServiceSelector { + if in == nil { + return nil + } + out := new(APIServiceSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AcceptablePermissionClaim) DeepCopyInto(out *AcceptablePermissionClaim) { + *out = *in + in.PermissionClaim.DeepCopyInto(&out.PermissionClaim) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AcceptablePermissionClaim. +func (in *AcceptablePermissionClaim) DeepCopy() *AcceptablePermissionClaim { + if in == nil { + return nil + } + out := new(AcceptablePermissionClaim) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuthenticationMethod) DeepCopyInto(out *AuthenticationMethod) { *out = *in @@ -688,6 +848,32 @@ func (in *BindingResponseAuthenticationOAuth2CodeGrant) DeepCopy() *BindingRespo return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClaimVerbs) DeepCopyInto(out *ClaimVerbs) { + *out = *in + if in.Provider != nil { + in, out := &in.Provider, &out.Provider + *out = make([]Verb, len(*in)) + copy(*out, *in) + } + if in.Consumer != nil { + in, out := &in.Consumer, &out.Consumer + *out = make([]Verb, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimVerbs. +func (in *ClaimVerbs) DeepCopy() *ClaimVerbs { + if in == nil { + return nil + } + out := new(ClaimVerbs) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterBinding) DeepCopyInto(out *ClusterBinding) { *out = *in @@ -872,3 +1058,42 @@ func (in *OAuth2CodeGrant) DeepCopy() *OAuth2CodeGrant { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PermissionClaim) DeepCopyInto(out *PermissionClaim) { + *out = *in + out.GroupResource = in.GroupResource + in.Verbs.DeepCopyInto(&out.Verbs) + if in.ResourceSelector != nil { + in, out := &in.ResourceSelector, &out.ResourceSelector + *out = make([]ResourceSelector, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PermissionClaim. +func (in *PermissionClaim) DeepCopy() *PermissionClaim { + if in == nil { + return nil + } + out := new(PermissionClaim) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceSelector) DeepCopyInto(out *ResourceSelector) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSelector. +func (in *ResourceSelector) DeepCopy() *ResourceSelector { + if in == nil { + return nil + } + out := new(ResourceSelector) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/apiserviceexporttemplate.go b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/apiserviceexporttemplate.go new file mode 100644 index 000000000..62a0172a4 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/apiserviceexporttemplate.go @@ -0,0 +1,196 @@ +/* +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 v1alpha1 + +import ( + "context" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + + v1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" + scheme "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned/scheme" +) + +// APIServiceExportTemplatesGetter has a method to return a APIServiceExportTemplateInterface. +// A group's client should implement this interface. +type APIServiceExportTemplatesGetter interface { + APIServiceExportTemplates(namespace string) APIServiceExportTemplateInterface +} + +// APIServiceExportTemplateInterface has methods to work with APIServiceExportTemplate resources. +type APIServiceExportTemplateInterface interface { + Create(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.CreateOptions) (*v1alpha1.APIServiceExportTemplate, error) + Update(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.UpdateOptions) (*v1alpha1.APIServiceExportTemplate, error) + UpdateStatus(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.UpdateOptions) (*v1alpha1.APIServiceExportTemplate, 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) (*v1alpha1.APIServiceExportTemplate, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.APIServiceExportTemplateList, 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 *v1alpha1.APIServiceExportTemplate, err error) + APIServiceExportTemplateExpansion +} + +// aPIServiceExportTemplates implements APIServiceExportTemplateInterface +type aPIServiceExportTemplates struct { + client rest.Interface + ns string +} + +// newAPIServiceExportTemplates returns a APIServiceExportTemplates +func newAPIServiceExportTemplates(c *KubeBindV1alpha1Client, namespace string) *aPIServiceExportTemplates { + return &aPIServiceExportTemplates{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the aPIServiceExportTemplate, and returns the corresponding aPIServiceExportTemplate object, and an error if there is any. +func (c *aPIServiceExportTemplates) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { + result = &v1alpha1.APIServiceExportTemplate{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of APIServiceExportTemplates that match those selectors. +func (c *aPIServiceExportTemplates) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.APIServiceExportTemplateList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.APIServiceExportTemplateList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested aPIServiceExportTemplates. +func (c *aPIServiceExportTemplates) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a aPIServiceExportTemplate and creates it. Returns the server's representation of the aPIServiceExportTemplate, and an error, if there is any. +func (c *aPIServiceExportTemplates) Create(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.CreateOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { + result = &v1alpha1.APIServiceExportTemplate{} + err = c.client.Post(). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIServiceExportTemplate). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a aPIServiceExportTemplate and updates it. Returns the server's representation of the aPIServiceExportTemplate, and an error, if there is any. +func (c *aPIServiceExportTemplates) Update(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.UpdateOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { + result = &v1alpha1.APIServiceExportTemplate{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + Name(aPIServiceExportTemplate.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIServiceExportTemplate). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *aPIServiceExportTemplates) UpdateStatus(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.UpdateOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { + result = &v1alpha1.APIServiceExportTemplate{} + err = c.client.Put(). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + Name(aPIServiceExportTemplate.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(aPIServiceExportTemplate). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the aPIServiceExportTemplate and deletes it. Returns an error if one occurs. +func (c *aPIServiceExportTemplates) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *aPIServiceExportTemplates) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched aPIServiceExportTemplate. +func (c *aPIServiceExportTemplates) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.APIServiceExportTemplate, err error) { + result = &v1alpha1.APIServiceExportTemplate{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("apiserviceexporttemplates"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_apiserviceexporttemplate.go b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_apiserviceexporttemplate.go new file mode 100644 index 000000000..3e8daa7ec --- /dev/null +++ b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_apiserviceexporttemplate.go @@ -0,0 +1,143 @@ +/* +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 ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + + v1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" +) + +// FakeAPIServiceExportTemplates implements APIServiceExportTemplateInterface +type FakeAPIServiceExportTemplates struct { + Fake *FakeKubeBindV1alpha1 + ns string +} + +var apiserviceexporttemplatesResource = schema.GroupVersionResource{Group: "kube-bind.io", Version: "v1alpha1", Resource: "apiserviceexporttemplates"} + +var apiserviceexporttemplatesKind = schema.GroupVersionKind{Group: "kube-bind.io", Version: "v1alpha1", Kind: "APIServiceExportTemplate"} + +// Get takes name of the aPIServiceExportTemplate, and returns the corresponding aPIServiceExportTemplate object, and an error if there is any. +func (c *FakeAPIServiceExportTemplates) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(apiserviceexporttemplatesResource, c.ns, name), &v1alpha1.APIServiceExportTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIServiceExportTemplate), err +} + +// List takes label and field selectors, and returns the list of APIServiceExportTemplates that match those selectors. +func (c *FakeAPIServiceExportTemplates) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.APIServiceExportTemplateList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(apiserviceexporttemplatesResource, apiserviceexporttemplatesKind, c.ns, opts), &v1alpha1.APIServiceExportTemplateList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.APIServiceExportTemplateList{ListMeta: obj.(*v1alpha1.APIServiceExportTemplateList).ListMeta} + for _, item := range obj.(*v1alpha1.APIServiceExportTemplateList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested aPIServiceExportTemplates. +func (c *FakeAPIServiceExportTemplates) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(apiserviceexporttemplatesResource, c.ns, opts)) + +} + +// Create takes the representation of a aPIServiceExportTemplate and creates it. Returns the server's representation of the aPIServiceExportTemplate, and an error, if there is any. +func (c *FakeAPIServiceExportTemplates) Create(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.CreateOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(apiserviceexporttemplatesResource, c.ns, aPIServiceExportTemplate), &v1alpha1.APIServiceExportTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIServiceExportTemplate), err +} + +// Update takes the representation of a aPIServiceExportTemplate and updates it. Returns the server's representation of the aPIServiceExportTemplate, and an error, if there is any. +func (c *FakeAPIServiceExportTemplates) Update(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.UpdateOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(apiserviceexporttemplatesResource, c.ns, aPIServiceExportTemplate), &v1alpha1.APIServiceExportTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIServiceExportTemplate), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeAPIServiceExportTemplates) UpdateStatus(ctx context.Context, aPIServiceExportTemplate *v1alpha1.APIServiceExportTemplate, opts v1.UpdateOptions) (*v1alpha1.APIServiceExportTemplate, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(apiserviceexporttemplatesResource, "status", c.ns, aPIServiceExportTemplate), &v1alpha1.APIServiceExportTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIServiceExportTemplate), err +} + +// Delete takes name of the aPIServiceExportTemplate and deletes it. Returns an error if one occurs. +func (c *FakeAPIServiceExportTemplates) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(apiserviceexporttemplatesResource, c.ns, name, opts), &v1alpha1.APIServiceExportTemplate{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeAPIServiceExportTemplates) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(apiserviceexporttemplatesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.APIServiceExportTemplateList{}) + return err +} + +// Patch applies the patch and returns the patched aPIServiceExportTemplate. +func (c *FakeAPIServiceExportTemplates) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.APIServiceExportTemplate, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(apiserviceexporttemplatesResource, c.ns, name, pt, data, subresources...), &v1alpha1.APIServiceExportTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.APIServiceExportTemplate), err +} diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_kubebind_client.go b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_kubebind_client.go index c90a8d66f..80d3a4713 100644 --- a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_kubebind_client.go +++ b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_kubebind_client.go @@ -41,6 +41,10 @@ func (c *FakeKubeBindV1alpha1) APIServiceExportRequests(namespace string) v1alph return &FakeAPIServiceExportRequests{c, namespace} } +func (c *FakeKubeBindV1alpha1) APIServiceExportTemplates(namespace string) v1alpha1.APIServiceExportTemplateInterface { + return &FakeAPIServiceExportTemplates{c, namespace} +} + func (c *FakeKubeBindV1alpha1) APIServiceNamespaces(namespace string) v1alpha1.APIServiceNamespaceInterface { return &FakeAPIServiceNamespaces{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/generated_expansion.go index 3b60d85b6..cc6d3f06d 100644 --- a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/generated_expansion.go @@ -24,6 +24,8 @@ type APIServiceExportExpansion interface{} type APIServiceExportRequestExpansion interface{} +type APIServiceExportTemplateExpansion interface{} + type APIServiceNamespaceExpansion interface{} type ClusterBindingExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/kubebind_client.go b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/kubebind_client.go index 93bdcc882..8bf1c6a14 100644 --- a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/kubebind_client.go +++ b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/kubebind_client.go @@ -32,6 +32,7 @@ type KubeBindV1alpha1Interface interface { APIServiceBindingsGetter APIServiceExportsGetter APIServiceExportRequestsGetter + APIServiceExportTemplatesGetter APIServiceNamespacesGetter ClusterBindingsGetter } @@ -53,6 +54,10 @@ func (c *KubeBindV1alpha1Client) APIServiceExportRequests(namespace string) APIS return newAPIServiceExportRequests(c, namespace) } +func (c *KubeBindV1alpha1Client) APIServiceExportTemplates(namespace string) APIServiceExportTemplateInterface { + return newAPIServiceExportTemplates(c, namespace) +} + func (c *KubeBindV1alpha1Client) APIServiceNamespaces(namespace string) APIServiceNamespaceInterface { return newAPIServiceNamespaces(c, namespace) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 0a4c24562..2e7f684e0 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -60,6 +60,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().APIServiceExports().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("apiserviceexportrequests"): return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().APIServiceExportRequests().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("apiserviceexporttemplates"): + return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().APIServiceExportTemplates().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("apiservicenamespaces"): return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().APIServiceNamespaces().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("clusterbindings"): diff --git a/pkg/client/informers/externalversions/kubebind/v1alpha1/apiserviceexporttemplate.go b/pkg/client/informers/externalversions/kubebind/v1alpha1/apiserviceexporttemplate.go new file mode 100644 index 000000000..83c7b410b --- /dev/null +++ b/pkg/client/informers/externalversions/kubebind/v1alpha1/apiserviceexporttemplate.go @@ -0,0 +1,91 @@ +/* +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 v1alpha1 + +import ( + "context" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + + kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" + versioned "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned" + internalinterfaces "github.com/kube-bind/kube-bind/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/kube-bind/kube-bind/pkg/client/listers/kubebind/v1alpha1" +) + +// APIServiceExportTemplateInformer provides access to a shared informer and lister for +// APIServiceExportTemplates. +type APIServiceExportTemplateInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.APIServiceExportTemplateLister +} + +type aPIServiceExportTemplateInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewAPIServiceExportTemplateInformer constructs a new informer for APIServiceExportTemplate 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 NewAPIServiceExportTemplateInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredAPIServiceExportTemplateInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredAPIServiceExportTemplateInformer constructs a new informer for APIServiceExportTemplate 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 NewFilteredAPIServiceExportTemplateInformer(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.KubeBindV1alpha1().APIServiceExportTemplates(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KubeBindV1alpha1().APIServiceExportTemplates(namespace).Watch(context.TODO(), options) + }, + }, + &kubebindv1alpha1.APIServiceExportTemplate{}, + resyncPeriod, + indexers, + ) +} + +func (f *aPIServiceExportTemplateInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredAPIServiceExportTemplateInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *aPIServiceExportTemplateInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&kubebindv1alpha1.APIServiceExportTemplate{}, f.defaultInformer) +} + +func (f *aPIServiceExportTemplateInformer) Lister() v1alpha1.APIServiceExportTemplateLister { + return v1alpha1.NewAPIServiceExportTemplateLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/kubebind/v1alpha1/interface.go b/pkg/client/informers/externalversions/kubebind/v1alpha1/interface.go index 22ed7eeda..b0ef4dc95 100644 --- a/pkg/client/informers/externalversions/kubebind/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/kubebind/v1alpha1/interface.go @@ -30,6 +30,8 @@ type Interface interface { APIServiceExports() APIServiceExportInformer // APIServiceExportRequests returns a APIServiceExportRequestInformer. APIServiceExportRequests() APIServiceExportRequestInformer + // APIServiceExportTemplates returns a APIServiceExportTemplateInformer. + APIServiceExportTemplates() APIServiceExportTemplateInformer // APIServiceNamespaces returns a APIServiceNamespaceInformer. APIServiceNamespaces() APIServiceNamespaceInformer // ClusterBindings returns a ClusterBindingInformer. @@ -62,6 +64,11 @@ func (v *version) APIServiceExportRequests() APIServiceExportRequestInformer { return &aPIServiceExportRequestInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// APIServiceExportTemplates returns a APIServiceExportTemplateInformer. +func (v *version) APIServiceExportTemplates() APIServiceExportTemplateInformer { + return &aPIServiceExportTemplateInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // APIServiceNamespaces returns a APIServiceNamespaceInformer. func (v *version) APIServiceNamespaces() APIServiceNamespaceInformer { return &aPIServiceNamespaceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/listers/kubebind/v1alpha1/apiserviceexporttemplate.go b/pkg/client/listers/kubebind/v1alpha1/apiserviceexporttemplate.go new file mode 100644 index 000000000..0abe5c922 --- /dev/null +++ b/pkg/client/listers/kubebind/v1alpha1/apiserviceexporttemplate.go @@ -0,0 +1,100 @@ +/* +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 v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + + v1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" +) + +// APIServiceExportTemplateLister helps list APIServiceExportTemplates. +// All objects returned here must be treated as read-only. +type APIServiceExportTemplateLister interface { + // List lists all APIServiceExportTemplates in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.APIServiceExportTemplate, err error) + // APIServiceExportTemplates returns an object that can list and get APIServiceExportTemplates. + APIServiceExportTemplates(namespace string) APIServiceExportTemplateNamespaceLister + APIServiceExportTemplateListerExpansion +} + +// aPIServiceExportTemplateLister implements the APIServiceExportTemplateLister interface. +type aPIServiceExportTemplateLister struct { + indexer cache.Indexer +} + +// NewAPIServiceExportTemplateLister returns a new APIServiceExportTemplateLister. +func NewAPIServiceExportTemplateLister(indexer cache.Indexer) APIServiceExportTemplateLister { + return &aPIServiceExportTemplateLister{indexer: indexer} +} + +// List lists all APIServiceExportTemplates in the indexer. +func (s *aPIServiceExportTemplateLister) List(selector labels.Selector) (ret []*v1alpha1.APIServiceExportTemplate, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.APIServiceExportTemplate)) + }) + return ret, err +} + +// APIServiceExportTemplates returns an object that can list and get APIServiceExportTemplates. +func (s *aPIServiceExportTemplateLister) APIServiceExportTemplates(namespace string) APIServiceExportTemplateNamespaceLister { + return aPIServiceExportTemplateNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// APIServiceExportTemplateNamespaceLister helps list and get APIServiceExportTemplates. +// All objects returned here must be treated as read-only. +type APIServiceExportTemplateNamespaceLister interface { + // List lists all APIServiceExportTemplates in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.APIServiceExportTemplate, err error) + // Get retrieves the APIServiceExportTemplate from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.APIServiceExportTemplate, error) + APIServiceExportTemplateNamespaceListerExpansion +} + +// aPIServiceExportTemplateNamespaceLister implements the APIServiceExportTemplateNamespaceLister +// interface. +type aPIServiceExportTemplateNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all APIServiceExportTemplates in the indexer for a given namespace. +func (s aPIServiceExportTemplateNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.APIServiceExportTemplate, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.APIServiceExportTemplate)) + }) + return ret, err +} + +// Get retrieves the APIServiceExportTemplate from the indexer for a given namespace and name. +func (s aPIServiceExportTemplateNamespaceLister) Get(name string) (*v1alpha1.APIServiceExportTemplate, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("apiserviceexporttemplate"), name) + } + return obj.(*v1alpha1.APIServiceExportTemplate), nil +} diff --git a/pkg/client/listers/kubebind/v1alpha1/expansion_generated.go b/pkg/client/listers/kubebind/v1alpha1/expansion_generated.go index 3cdf7aed8..d17725677 100644 --- a/pkg/client/listers/kubebind/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/kubebind/v1alpha1/expansion_generated.go @@ -38,6 +38,14 @@ type APIServiceExportRequestListerExpansion interface{} // APIServiceExportRequestNamespaceLister. type APIServiceExportRequestNamespaceListerExpansion interface{} +// APIServiceExportTemplateListerExpansion allows custom methods to be added to +// APIServiceExportTemplateLister. +type APIServiceExportTemplateListerExpansion interface{} + +// APIServiceExportTemplateNamespaceListerExpansion allows custom methods to be added to +// APIServiceExportTemplateNamespaceLister. +type APIServiceExportTemplateNamespaceListerExpansion interface{} + // APIServiceNamespaceListerExpansion allows custom methods to be added to // APIServiceNamespaceLister. type APIServiceNamespaceListerExpansion interface{} From cc895360fe353b61c272f1c15809c4197e6c530c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 23 Feb 2023 10:20:09 +0100 Subject: [PATCH 02/26] Integrate APIServiceExportTemplate in example backend CRDs to be displayed are now based on APIServiceExportTemplates and not on a label Claimed resources are added to the APIServiceExport Claimed resources are sent to the cli plugin --- .../clusterbinding_reconcile.go | 13 +++ .../serviceexportrequest_controller.go | 2 + .../serviceexportrequest_reconcile.go | 8 ++ .../example-backend/exporttemplate/index.go | 77 +++++++++++++++ .../exporttemplate/index_test.go | 94 +++++++++++++++++++ contrib/example-backend/http/handler.go | 35 ++++--- contrib/example-backend/server.go | 4 +- .../provider/exporttemplate-mangodb.yaml | 9 ++ test/e2e/bind/happy-case_test.go | 6 +- test/e2e/framework/backend.go | 1 + 10 files changed, 231 insertions(+), 18 deletions(-) create mode 100644 contrib/example-backend/exporttemplate/index.go create mode 100644 contrib/example-backend/exporttemplate/index_test.go create mode 100644 test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml diff --git a/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go b/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go index ea6a9790d..97d1d89c6 100644 --- a/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go +++ b/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go @@ -153,6 +153,19 @@ func (r *reconciler) ensureRBACClusterRole(ctx context.Context, clusterBinding * Resources: []string{export.Spec.Names.Plural}, Verbs: []string{"get", "list", "watch", "update", "patch", "delete", "create"}, }) + for _, e := range export.Spec.PermissionClaims { + var verbs []string + + for _, v := range e.Verbs.Provider { + verbs = append(verbs, string(v)) + } + + expected.Rules = append(expected.Rules, rbacv1.PolicyRule{ + APIGroups: []string{e.Group}, + Resources: []string{e.Resource}, + Verbs: verbs, + }) + } } if role == nil { diff --git a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go index 852b00f8b..385a269c4 100644 --- a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go +++ b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go @@ -35,6 +35,7 @@ import ( "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" + "github.com/kube-bind/kube-bind/contrib/example-backend/exporttemplate" kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" bindclient "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned" bindinformers "github.com/kube-bind/kube-bind/pkg/client/informers/externalversions/kubebind/v1alpha1" @@ -101,6 +102,7 @@ func NewController( deleteServiceExportRequest: func(ctx context.Context, ns, name string) error { return bindClient.KubeBindV1alpha1().APIServiceExportRequests(ns).Delete(ctx, name, metav1.DeleteOptions{}) }, + crds: exporttemplate.NewCatalogue(config), }, commit: committer.NewCommitter[*kubebindv1alpha1.APIServiceExportRequest, *kubebindv1alpha1.APIServiceExportRequestSpec, *kubebindv1alpha1.APIServiceExportRequestStatus]( diff --git a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go index 46c2be164..b133f9a7e 100644 --- a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go +++ b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go @@ -26,6 +26,7 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/klog/v2" + "github.com/kube-bind/kube-bind/contrib/example-backend/exporttemplate" kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1/helpers" conditionsapi "github.com/kube-bind/kube-bind/pkg/apis/third_party/conditions/apis/conditions/v1alpha1" @@ -40,6 +41,7 @@ type reconciler struct { createServiceExport func(ctx context.Context, resource *kubebindv1alpha1.APIServiceExport) (*kubebindv1alpha1.APIServiceExport, error) deleteServiceExportRequest func(ctx context.Context, namespace, name string) error + crds exporttemplate.Index } func (r *reconciler) reconcile(ctx context.Context, req *kubebindv1alpha1.APIServiceExportRequest) error { @@ -84,6 +86,11 @@ func (r *reconciler) ensureExports(ctx context.Context, req *kubebindv1alpha1.AP continue } + template, err := r.crds.TemplateFor(ctx, res.Group, res.Resource) + if err != nil { + return err + } + exportSpec, err := helpers.CRDToServiceExport(crd) if err != nil { conditions.MarkFalse( @@ -110,6 +117,7 @@ func (r *reconciler) ensureExports(ctx context.Context, req *kubebindv1alpha1.AP Spec: kubebindv1alpha1.APIServiceExportSpec{ APIServiceExportCRDSpec: *exportSpec, InformerScope: r.informerScope, + PermissionClaims: template.Spec.ClaimedResources, }, } diff --git a/contrib/example-backend/exporttemplate/index.go b/contrib/example-backend/exporttemplate/index.go new file mode 100644 index 000000000..ef8f8ad87 --- /dev/null +++ b/contrib/example-backend/exporttemplate/index.go @@ -0,0 +1,77 @@ +package exporttemplate + +// TODO by namespace +// TODO cached client +// TODO find a better name or reorganize into different packages +import ( + "context" + "fmt" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + crd "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + + kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" + templates "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned" +) + +type Index struct { + templates templates.Interface + crds crd.Interface + clusterNs string +} + +func NewCatalogue(r *rest.Config) Index { + crdClient := crd.NewForConfigOrDie(r) + + templateClient := templates.NewForConfigOrDie(r) + + return Index{ + templates: templateClient, + crds: crdClient, + } +} + +func (i Index) GetExported(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error) { + list, err := i.crds.ApiextensionsV1().CustomResourceDefinitions().List(ctx, v1.ListOptions{}) + if err != nil { + return nil, err + } + exports, err := i.templates.KubeBindV1alpha1().APIServiceExportTemplates(i.clusterNs).List(ctx, v1.ListOptions{}) + if err != nil { + return nil, err + } + + exported := []apiextensionsv1.CustomResourceDefinition{} + + for _, ex := range exports.Items { + for _, c := range list.Items { + s := ex.Spec.APIServiceSelector + if s.Group == c.Spec.Group && s.Resource == c.Spec.Names.Plural { + exported = append(exported, c) + } + } + } + + if exported == nil { + return nil, fmt.Errorf("no exported resources") + } + + return exported, nil +} + +func (i Index) TemplateFor(ctx context.Context, group, resource string) (kubebindv1alpha1.APIServiceExportTemplate, error) { + exports, err := i.templates.KubeBindV1alpha1().APIServiceExportTemplates(i.clusterNs).List(ctx, v1.ListOptions{}) + if err != nil { + return kubebindv1alpha1.APIServiceExportTemplate{}, nil + } + + for _, e := range exports.Items { + if e.Spec.APIServiceSelector.Resource == resource && e.Spec.APIServiceSelector.Group == group { + return e, nil + } + } + + return kubebindv1alpha1.APIServiceExportTemplate{}, fmt.Errorf("not found: %s/%s", group, resource) +} diff --git a/contrib/example-backend/exporttemplate/index_test.go b/contrib/example-backend/exporttemplate/index_test.go new file mode 100644 index 000000000..b2679a9e3 --- /dev/null +++ b/contrib/example-backend/exporttemplate/index_test.go @@ -0,0 +1,94 @@ +package exporttemplate + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + crd "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" + templates "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned/fake" +) + +var mangodb = apiextensions.CustomResourceDefinition{ + ObjectMeta: v1.ObjectMeta{ + Name: "mangodbs.mangodb.com", + }, + Spec: apiextensions.CustomResourceDefinitionSpec{ + Group: "mangodb.com", + Scope: apiextensions.NamespaceScoped, + Names: apiextensions.CustomResourceDefinitionNames{ + Plural: "mangodbs", + Kind: "MangoDB", + }, + }, +} + +var dummy = apiextensions.CustomResourceDefinition{ + ObjectMeta: v1.ObjectMeta{ + Name: "dummies.example.com", + }, + Spec: apiextensions.CustomResourceDefinitionSpec{ + Group: "example.com", + Scope: apiextensions.NamespaceScoped, + Names: apiextensions.CustomResourceDefinitionNames{ + Plural: "dummies", + Kind: "dummies", + }, + }, +} + +var export = v1alpha1.APIServiceExportTemplate{ + Spec: v1alpha1.APIServiceExportTemplateSpec{ + APIServiceSelector: v1alpha1.APIServiceSelector{ + Resource: "mangodbs", + Group: "mangodb.com", + }, + }, + ObjectMeta: v1.ObjectMeta{ + Name: "mangodb.com", + Namespace: "cluster-x", + }, +} + +func TestListCRDsForAPIServiceExport(t *testing.T) { + t.Parallel() + + c := crd.NewSimpleClientset(&mangodb, &dummy) + templatesClient := templates.NewSimpleClientset(&export) + + ix := Index{ + templates: templatesClient, + crds: c, + } + + crdList, err := ix.GetExported(context.TODO()) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, []apiextensions.CustomResourceDefinition{mangodb}, crdList) +} + +func TestGetAPIServiceExportTemplates(t *testing.T) { + t.Parallel() + + c := crd.NewSimpleClientset(&mangodb, &dummy) + templatesClient := templates.NewSimpleClientset(&export) + + ix := Index{ + templates: templatesClient, + crds: c, + } + + exported, err := ix.TemplateFor(context.TODO(), mangodb.Spec.Group, mangodb.Spec.Names.Plural) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, export, exported) +} diff --git a/contrib/example-backend/http/handler.go b/contrib/example-backend/http/handler.go index e79bdf1b9..1897398b4 100644 --- a/contrib/example-backend/http/handler.go +++ b/contrib/example-backend/http/handler.go @@ -18,6 +18,7 @@ package http import ( "bytes" + "context" "encoding/base64" "encoding/json" "errors" @@ -33,16 +34,14 @@ import ( "github.com/gorilla/securecookie" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionslisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" componentbaseversion "k8s.io/component-base/version" "k8s.io/klog/v2" "github.com/kube-bind/kube-bind/contrib/example-backend/cookie" + "github.com/kube-bind/kube-bind/contrib/example-backend/exporttemplate" "github.com/kube-bind/kube-bind/contrib/example-backend/kubernetes" - "github.com/kube-bind/kube-bind/contrib/example-backend/kubernetes/resources" "github.com/kube-bind/kube-bind/contrib/example-backend/template" kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" bindversion "github.com/kube-bind/kube-bind/pkg/version" @@ -71,9 +70,9 @@ type handler struct { cookieEncryptionKey []byte cookieSigningKey []byte - client *http.Client - apiextensionsLister apiextensionslisters.CustomResourceDefinitionLister - kubeManager *kubernetes.Manager + client *http.Client + templateIndex exporttemplate.Index + kubeManager *kubernetes.Manager } func NewHandler( @@ -82,7 +81,7 @@ func NewHandler( cookieSigningKey, cookieEncryptionKey []byte, scope kubebindv1alpha1.Scope, mgr *kubernetes.Manager, - apiextensionsLister apiextensionslisters.CustomResourceDefinitionLister, + apiextensionsLister exporttemplate.Index, ) (*handler, error) { return &handler{ oidc: provider, @@ -93,7 +92,7 @@ func NewHandler( scope: scope, client: http.DefaultClient, kubeManager: mgr, - apiextensionsLister: apiextensionsLister, + templateIndex: apiextensionsLister, cookieSigningKey: cookieSigningKey, cookieEncryptionKey: cookieEncryptionKey, }, nil @@ -292,10 +291,7 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) { return } - labelSelector := labels.Set{ - resources.ExportedCRDsLabel: "true", - } - crds, err := h.apiextensionsLister.List(labelSelector.AsSelector()) + crds, err := h.templateIndex.GetExported(r.Context()) if err != nil { logger.Error(err, "failed to list crds") http.Error(w, "internal error", http.StatusInternalServerError) @@ -306,8 +302,9 @@ func (h *handler) handleResources(w http.ResponseWriter, r *http.Request) { }) rightScopedCRDs := []*apiextensionsv1.CustomResourceDefinition{} for _, crd := range crds { + crd := crd if h.scope == kubebindv1alpha1.ClusterScope || crd.Spec.Scope == apiextensionsv1.NamespaceScoped { - rightScopedCRDs = append(rightScopedCRDs, crd) + rightScopedCRDs = append(rightScopedCRDs, &crd) } } @@ -368,6 +365,13 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) { return } + exportTemplate, err := h.templateIndex.TemplateFor(context.Background(), group, resource) + if err != nil { + logger.Error(err, "failed to get export template", "group", group, "resource", resource) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + request := kubebindv1alpha1.APIServiceExportRequestResponse{ TypeMeta: metav1.TypeMeta{ APIVersion: kubebindv1alpha1.SchemeGroupVersion.String(), @@ -381,7 +385,10 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) { }, Spec: kubebindv1alpha1.APIServiceExportRequestSpec{ Resources: []kubebindv1alpha1.APIServiceExportRequestResource{ - {GroupResource: kubebindv1alpha1.GroupResource{Group: group, Resource: resource}}, + { + GroupResource: kubebindv1alpha1.GroupResource{Group: group, Resource: resource}, + PermissionClaims: exportTemplate.Spec.ClaimedResources, + }, }, }, } diff --git a/contrib/example-backend/server.go b/contrib/example-backend/server.go index 5d7fe481f..830909c11 100644 --- a/contrib/example-backend/server.go +++ b/contrib/example-backend/server.go @@ -31,6 +31,7 @@ import ( "github.com/kube-bind/kube-bind/contrib/example-backend/controllers/serviceexportrequest" "github.com/kube-bind/kube-bind/contrib/example-backend/controllers/servicenamespace" "github.com/kube-bind/kube-bind/contrib/example-backend/deploy" + "github.com/kube-bind/kube-bind/contrib/example-backend/exporttemplate" examplehttp "github.com/kube-bind/kube-bind/contrib/example-backend/http" examplekube "github.com/kube-bind/kube-bind/contrib/example-backend/kubernetes" kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" @@ -106,6 +107,7 @@ func NewServer(config *Config) (*Server, error) { } } + ind := exporttemplate.NewCatalogue(config.ClientConfig) handler, err := examplehttp.NewHandler( s.OIDC, config.Options.OIDC.AuthorizeURL, @@ -116,7 +118,7 @@ func NewServer(config *Config) (*Server, error) { encryptionKey, kubebindv1alpha1.Scope(config.Options.ConsumerScope), s.Kubernetes, - config.ApiextensionsInformers.Apiextensions().V1().CustomResourceDefinitions().Lister(), + ind, ) if err != nil { return nil, fmt.Errorf("error setting up HTTP Handler: %w", err) diff --git a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml new file mode 100644 index 000000000..97b20db77 --- /dev/null +++ b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml @@ -0,0 +1,9 @@ +kind: APIServiceExportTemplate +apiVersion: kube-bind.io/v1alpha1 +metadata: + name: "mangodbs" + namespace: default +spec: + APIServiceSelector: + resource: mangodbs + group: mangodb.com diff --git a/test/e2e/bind/happy-case_test.go b/test/e2e/bind/happy-case_test.go index 4a220912a..f3d67e4ac 100644 --- a/test/e2e/bind/happy-case_test.go +++ b/test/e2e/bind/happy-case_test.go @@ -59,12 +59,12 @@ func testHappyCase(t *testing.T, scope kubebindv1alpha1.Scope) { t.Logf("Creating provider workspace") providerConfig, providerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-happy-case-provider")) - t.Logf("Creating MangoDB CRD on provider side") - providerfixtures.Bootstrap(t, framework.DiscoveryClient(t, providerConfig), framework.DynamicClient(t, providerConfig), nil) - t.Logf("Starting backend with random port") addr, _ := framework.StartBackend(t, providerConfig, "--kubeconfig="+providerKubeconfig, "--listen-port=0", "--consumer-scope="+string(scope)) + t.Logf("Creating MangoDB CRD on provider side") + providerfixtures.Bootstrap(t, framework.DiscoveryClient(t, providerConfig), framework.DynamicClient(t, providerConfig), nil) + t.Logf("Creating consumer workspace and starting konnector") consumerConfig, consumerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-happy-case-consumer")) framework.StartKonnector(t, consumerConfig, "--kubeconfig="+consumerKubeconfig) diff --git a/test/e2e/framework/backend.go b/test/e2e/framework/backend.go index d2cab8fcb..f7b932aff 100644 --- a/test/e2e/framework/backend.go +++ b/test/e2e/framework/backend.go @@ -67,6 +67,7 @@ func StartBackendWithoutDefaultArgs(t *testing.T, clientConfig *rest.Config, arg metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiserviceexports"}, metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiservicenamespaces"}, metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiserviceexportrequests"}, + metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiserviceexporttemplates"}, ) require.NoError(t, err) From 49cf4257b10cdedb2e5d915176dae6aac5a4a279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 23 Feb 2023 10:24:16 +0100 Subject: [PATCH 03/26] Add claimed resources to cli Add prompt for permission claim requests Add permissionclaims including accepted state to APIBinding --- .../bind-apiservice/plugin/servicebindings.go | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go index 431ed7f14..673152067 100644 --- a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go +++ b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go @@ -17,8 +17,11 @@ limitations under the License. package plugin import ( + "bufio" "context" "fmt" + "os" + "strings" "time" apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" @@ -69,6 +72,26 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co continue } + var permissionClaims []kubebindv1alpha1.AcceptablePermissionClaim + for _, c := range resource.PermissionClaims { + accepted, err := promptYesNo(c) + if err != nil { + return nil, err + } + + var state kubebindv1alpha1.AcceptablePermissionClaimState + if accepted { + state = kubebindv1alpha1.ClaimAccepted + } else { + state = kubebindv1alpha1.ClaimRejected + } + + permissionClaims = append(permissionClaims, kubebindv1alpha1.AcceptablePermissionClaim{ + PermissionClaim: c, + State: state, + }) + } + // create new APIServiceBinding. first := true if err := wait.PollInfinite(1*time.Second, func() (bool, error) { @@ -89,6 +112,7 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co }, Namespace: "kube-bind", }, + PermissionClaims: permissionClaims, }, }, metav1.CreateOptions{}) if err != nil { @@ -115,3 +139,23 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co return bindings, nil } + +func promptYesNo(p kubebindv1alpha1.PermissionClaim) (bool, error) { + reader := bufio.NewReader(os.Stdin) + + for { + fmt.Printf("binding wants permission\n%+v\n[Y/N]", p) + + response, err := reader.ReadString('\n') + if err != nil { + return false, err + } + + response = strings.ToLower(strings.TrimSpace(response)) + if response == "y" || response == "yes" { + return true, nil + } else if response == "n" || response == "no" { + return false, nil + } + } +} From a14e889a972ee084d7cbccf56fd1a62b7f7aa57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 23 Feb 2023 10:25:16 +0100 Subject: [PATCH 04/26] Add reconciler for Downsync Add a new controller and reconciler for downsyncing claimed resources --- .../claimedresources_controller.go | 378 ++++++++++++++++++ .../claimedresources_reconciler.go | 125 ++++++ .../claimedresources_reconciler_test.go | 240 +++++++++++ .../serviceexport/serviceexport_reconcile.go | 67 ++++ 4 files changed, 810 insertions(+) create mode 100644 pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go create mode 100644 pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go create mode 100644 pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go new file mode 100644 index 000000000..3f35d5c6d --- /dev/null +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go @@ -0,0 +1,378 @@ +/* +Copyright 2022 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 claimedresources + +import ( + "context" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + dynamicclient "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/dynamiclister" + "k8s.io/client-go/informers" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + + kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" + bindlisters "github.com/kube-bind/kube-bind/pkg/client/listers/kubebind/v1alpha1" + "github.com/kube-bind/kube-bind/pkg/indexers" + "github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/serviceexport/multinsinformer" + "github.com/kube-bind/kube-bind/pkg/konnector/controllers/dynamic" +) + +const ( + controllerName = "kube-bind-konnector-claimed-object" +) + +// NewController returns a new controller reconciling downstream objects to upstream. +func NewController( + gvr schema.GroupVersionResource, + providerNamespace string, + consumerConfig, providerConfig *rest.Config, + consumerDynamicInformer informers.GenericInformer, + providerDynamicInformer multinsinformer.GetterInformer, + serviceNamespaceInformer dynamic.Informer[bindlisters.APIServiceNamespaceLister], +) (*controller, error) { + queue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerName) + + logger := klog.Background().WithValues("controller", controllerName) + + providerConfig = rest.CopyConfig(providerConfig) + providerConfig = rest.AddUserAgent(providerConfig, controllerName) + + providerClient, err := dynamicclient.NewForConfig(providerConfig) + if err != nil { + return nil, err + } + consumerClient, err := dynamicclient.NewForConfig(consumerConfig) + if err != nil { + return nil, err + } + + dynamicConsumerLister := dynamiclister.New(consumerDynamicInformer.Informer().GetIndexer(), gvr) + c := &controller{ + queue: queue, + + consumerClient: consumerClient, + providerClient: providerClient, + + consumerDynamicLister: dynamicConsumerLister, + consumerDynamicIndexer: consumerDynamicInformer.Informer().GetIndexer(), + + providerDynamicInformer: providerDynamicInformer, + + serviceNamespaceInformer: serviceNamespaceInformer, + + providerNamespace: providerNamespace, + + readReconciler: readReconciler{ + getServiceNamespace: func(upstreamNamespace string) (*kubebindv1alpha1.APIServiceNamespace, error) { + sns, err := serviceNamespaceInformer.Informer().GetIndexer().ByIndex(indexers.ServiceNamespaceByNamespace, upstreamNamespace) + if err != nil { + return nil, err + } + if len(sns) == 0 { + return nil, errors.NewNotFound(kubebindv1alpha1.SchemeGroupVersion.WithResource("APIServiceNamespace").GroupResource(), upstreamNamespace) + } + return sns[0].(*kubebindv1alpha1.APIServiceNamespace), nil + + }, + getConsumerObject: func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) { + return consumerClient.Resource(gvr).Namespace(ns).Get(ctx, name, metav1.GetOptions{}) + }, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + obj, err := providerDynamicInformer.Get(ns, name) + if err != nil { + return nil, err + } + return obj.(*unstructured.Unstructured), nil + }, + deleteConsumerObject: func(ctx context.Context, ns, name string) error { + return consumerClient.Resource(gvr).Namespace(ns).Delete(ctx, name, metav1.DeleteOptions{}) + }, + updateConsumerObject: func(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return consumerClient.Resource(gvr).Namespace(obj.GetNamespace()).Update(ctx, obj, metav1.UpdateOptions{}) + }, + createConsumerObject: func(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + return consumerClient.Resource(gvr).Namespace(obj.GetNamespace()).Create(ctx, obj, metav1.CreateOptions{}) + }, + // requeue: func(obj *unstructured.Unstructured, after time.Duration) error { + // key, err := cache.MetaNamespaceKeyFunc(obj) + // if err != nil { + // return err + // } + // queue.AddAfter(key, after) + // return nil + // }, + }, + } + + consumerDynamicInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + c.enqueueConsumer(logger, obj) + }, + UpdateFunc: func(_, newObj interface{}) { + c.enqueueConsumer(logger, newObj) + }, + DeleteFunc: func(obj interface{}) { + c.enqueueConsumer(logger, obj) + }, + }) + + providerDynamicInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + c.enqueueProvider(logger, obj) + }, + UpdateFunc: func(_, newObj interface{}) { + c.enqueueProvider(logger, newObj) + }, + DeleteFunc: func(obj interface{}) { + c.enqueueProvider(logger, obj) + }, + }) + + return c, nil +} + +// controller reconciles upstream objects to downstream. +type controller struct { + queue workqueue.RateLimitingInterface + + consumerClient dynamicclient.Interface + providerClient dynamicclient.Interface + + consumerDynamicLister dynamiclister.Lister + consumerDynamicIndexer cache.Indexer + + providerDynamicInformer multinsinformer.GetterInformer + + serviceNamespaceInformer dynamic.Informer[bindlisters.APIServiceNamespaceLister] + + providerNamespace string + + readReconciler +} + +func isClaimed(obj *unstructured.Unstructured) bool { + for k, v := range obj.GetAnnotations() { + if k == annotation && v == "true" { + return true + } + } + return false +} + +func (c *controller) enqueueConsumer(logger klog.Logger, obj interface{}) { + o := obj.(*unstructured.Unstructured) + if !isClaimed(o) { + return + } + + key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + if err != nil { + runtime.HandleError(err) + return + } + ns, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + runtime.HandleError(err) + return + } + + if ns != "" { + sn, err := c.serviceNamespaceInformer.Lister().APIServiceNamespaces(c.providerNamespace).Get(ns) + if err != nil { + if !errors.IsNotFound(err) { + runtime.HandleError(err) + } + return + } + if sn.Namespace == c.providerNamespace && sn.Status.Namespace != "" { + key := fmt.Sprintf("%s/%s", sn.Status.Namespace, name) + logger.V(2).Info("queueing Unstructured", "key", key) + c.queue.Add(key) + return + } + return + } + + logger.V(2).Info("queueing Unstructured", "key", key) + c.queue.Add(key) +} + +func (c *controller) enqueueProvider(logger klog.Logger, obj interface{}) { + upstreamKey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + if err != nil { + runtime.HandleError(err) + return + } + ns, name, err := cache.SplitMetaNamespaceKey(upstreamKey) + if err != nil { + runtime.HandleError(err) + return + } + + if ns != "" { + sns, err := c.serviceNamespaceInformer.Informer().GetIndexer().ByIndex(indexers.ServiceNamespaceByNamespace, ns) + if err != nil { + if !errors.IsNotFound(err) { + runtime.HandleError(err) + } + return + } + for _, obj := range sns { + sn := obj.(*kubebindv1alpha1.APIServiceNamespace) + if sn.Namespace == c.providerNamespace { + key := fmt.Sprintf("%s/%s", sn.Name, name) + logger.V(2).Info("queueing Unstructured", "key", key) + c.queue.Add(upstreamKey) + return + } + } + return + } + + logger.V(2).Info("queueing Unstructured", "key", upstreamKey) + c.queue.Add(upstreamKey) +} + +func (c *controller) enqueueServiceNamespace(logger klog.Logger, obj interface{}) { + snKey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + if err != nil { + runtime.HandleError(err) + return + } + ns, name, err := cache.SplitMetaNamespaceKey(snKey) + if err != nil { + runtime.HandleError(err) + return + } + if ns != c.providerNamespace { + return // not for us + } + + sn, err := c.serviceNamespaceInformer.Lister().APIServiceNamespaces(ns).Get(name) + if err != nil { + logger.Error(err, "\n\ncould not list") + runtime.HandleError(err) + return + } + + if sn.Namespace == "" { + return // not ready + } + + logger.Info("enqueueing service namespace", "name", sn.Status.Namespace) + objs, err := c.providerDynamicInformer.List(sn.Status.Namespace) + if err != nil { + runtime.HandleError(err) + return + } + for _, obj := range objs { + + logger.Info("enqueueing provider object", "obj", obj) + + key, err := cache.MetaNamespaceKeyFunc(obj) + if err != nil { + runtime.HandleError(err) + continue + } + logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "ServiceNamespaceKey", key) + c.queue.Add(key) + } +} + +// Start starts the controller, which stops when ctx.Done() is closed. +func (c *controller) Start(ctx context.Context, numThreads int) { + defer runtime.HandleCrash() + defer c.queue.ShutDown() + + logger := klog.FromContext(ctx).WithValues("controller", controllerName) + + logger.Info("Starting controller") + defer logger.Info("Shutting down controller") + + c.serviceNamespaceInformer.Informer().AddDynamicEventHandler(ctx, controllerName, cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + c.enqueueServiceNamespace(logger, obj) + }, + UpdateFunc: func(_, newObj interface{}) { + c.enqueueServiceNamespace(logger, newObj) + }, + DeleteFunc: func(obj interface{}) { + c.enqueueServiceNamespace(logger, obj) + }, + }) + + for i := 0; i < numThreads; i++ { + go wait.UntilWithContext(ctx, c.startWorker, time.Second) + } + + <-ctx.Done() +} + +func (c *controller) startWorker(ctx context.Context) { + defer runtime.HandleCrash() + + for c.processNextWorkItem(ctx) { + } +} + +func (c *controller) processNextWorkItem(ctx context.Context) bool { + // Wait until there is a new item in the working queue + k, quit := c.queue.Get() + if quit { + return false + } + key := k.(string) + + logger := klog.FromContext(ctx).WithValues("key", key) + ctx = klog.NewContext(ctx, logger) + logger.V(2).Info("processing key") + + // No matter what, tell the queue we're done with this key, to unblock + // other workers. + defer c.queue.Done(key) + + if err := c.process(ctx, key); err != nil { + runtime.HandleError(fmt.Errorf("%q controller failed to sync %q, err: %w", controllerName, key, err)) + c.queue.AddRateLimited(key) + return true + } + c.queue.Forget(key) + return true +} + +func (c *controller) process(ctx context.Context, key string) error { + ns, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + runtime.HandleError(err) + return nil // we cannot do anything + } + + //logger := klog.FromContext(ctx) + + return c.reconcile(ctx, ns, name) +} diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go new file mode 100644 index 000000000..522c445fc --- /dev/null +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go @@ -0,0 +1,125 @@ +/* +Copyright 2022 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 claimedresources + +import ( + "context" + "reflect" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/klog/v2" + + kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" +) + +const annotation = "kube-bind.io/claimedresource" + +type readReconciler struct { + getServiceNamespace func(upstreamNamespace string) (*kubebindv1alpha1.APIServiceNamespace, error) + getProviderObject func(ns, name string) (*unstructured.Unstructured, error) + + getConsumerObject func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) + updateConsumerObject func(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) + createConsumerObject func(ctx context.Context, ob *unstructured.Unstructured) (*unstructured.Unstructured, error) + deleteConsumerObject func(ctx context.Context, ns, name string) error +} + +// reconcile syncs upstream claimed resources to downstream. +func (r *readReconciler) reconcile(ctx context.Context, upstreamNS, name string) error { + logger := klog.FromContext(ctx) + logger = logger.WithValues("name", name, "upstreamNamespace", upstreamNS) + + logger.Info("reconciling object") + downstreamNS := "" + if upstreamNS != "" { + sn, err := r.getServiceNamespace(upstreamNS) + if err != nil && !errors.IsNotFound(err) { + return err + } else if errors.IsNotFound(err) { + runtime.HandleError(err) + return err // hoping the APIServiceNamespace will be created soon. Otherwise, this item goes into backoff. + } + if sn.Status.Namespace == "" { + runtime.HandleError(err) + return err // hoping the status is set soon. + } + + logger = logger.WithValues("upstreamNamespace", sn.Status.Namespace) + ctx = klog.NewContext(ctx, logger) + + // continue with downstream namespace + downstreamNS = sn.Name + logger = logger.WithValues("downstreamNamespace", downstreamNS) + } + + obj, err := r.getProviderObject(upstreamNS, name) + if errors.IsNotFound(err) { + err := r.deleteConsumerObject(ctx, downstreamNS, name) + if errors.IsNotFound(err) { + return nil + } + return err + } else if err != nil { + return err + } + + if obj.GetDeletionTimestamp() != nil && !obj.GetDeletionTimestamp().IsZero() { + logger.Info("Deleting downstream object because it has been deleted upstream", "downStreamNamespace", upstreamNS, "downstreamName", obj.GetName()) + if err := r.deleteConsumerObject(ctx, upstreamNS, obj.GetName()); err != nil { + return err + } + } + // clean up object + candidate := obj.DeepCopy() + candidate.SetUID("") + candidate.SetResourceVersion("") + candidate.SetNamespace(downstreamNS) + candidate.SetManagedFields(nil) + candidate.SetDeletionTimestamp(nil) + candidate.SetDeletionGracePeriodSeconds(nil) + candidate.SetOwnerReferences(nil) + candidate.SetFinalizers(nil) + candidate.SetAnnotations(map[string]string{ + annotation: "true"}, + ) + candidate.SetNamespace(downstreamNS) + + downstream, err := r.getConsumerObject(ctx, downstreamNS, name) + + if err != nil && !errors.IsNotFound(err) { + logger.Info("failed to get downstream object", "error", err, "downstreamNamespace", upstreamNS, "downstreamName", obj.GetName()) + return err + } else if errors.IsNotFound(err) { + logger.Info("Creating missing downstream object", "downstreamNamespace", upstreamNS, "downstreamName", obj.GetName()) + if _, err := r.createConsumerObject(ctx, candidate); err != nil { + return err + } + + return nil + } + + if !reflect.DeepEqual(candidate, downstream) { + logger.Info("Updating downstream object data", "downstreamNamespace", upstreamNS, "downstreamName", downstream.GetName()) + if _, err := r.updateConsumerObject(ctx, candidate); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go new file mode 100644 index 000000000..b2937a9d4 --- /dev/null +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go @@ -0,0 +1,240 @@ +package claimedresources + +import ( + "context" + "testing" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" +) + +func TestDownstreamCreation(t *testing.T) { + t.Parallel() + + var createdObj *unstructured.Unstructured + r := readReconciler{ + getServiceNamespace: defaultNamespace, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "dummy", + "namespace": "cluster-x-default", + }, + }, + }, nil + }, + getConsumerObject: notFound, + createConsumerObject: func(ctx context.Context, ob *unstructured.Unstructured) (*unstructured.Unstructured, error) { + createdObj = ob + return ob, nil + }, + } + + err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") + if err != nil { + t.Fatal(err) + } + + if createdObj == nil { + t.Error("reconcile did not create an object", createdObj) + } + + if v, ok := createdObj.GetAnnotations()["kube-bind.io/claimedresource"]; !ok || v != "true" { + t.Error("created object did not have 'kube-bind.io/claimedresource: true' annotation") + } +} + +func defaultNamespace(upstreamNamespace string) (*v1alpha1.APIServiceNamespace, error) { + return &v1alpha1.APIServiceNamespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: "kube-bind", + }, + Spec: v1alpha1.APIServiceNamespaceSpec{}, + Status: v1alpha1.APIServiceNamespaceStatus{ + Namespace: "cluster-x-default", + }, + }, nil +} + +func notFound(_ context.Context, ns, name string) (*unstructured.Unstructured, error) { + return nil, errors.NewNotFound(v1.Resource("Secret"), name) +} + +func TestDownstreamDeletion(t *testing.T) { + t.Parallel() + + var deleteNsn struct { + ns, name string + } + r := readReconciler{ + getServiceNamespace: defaultNamespace, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + return nil, errors.NewNotFound(v1.Resource("Secret"), name) + }, + getConsumerObject: notFound, + deleteConsumerObject: func(ctx context.Context, ns, name string) error { + deleteNsn = struct { + ns string + name string + }{ns: ns, name: name} + return nil + }, + } + + err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") + if err != nil { + t.Fatal(err) + } + + if deleteNsn.name != "dummy" || deleteNsn.ns != "default" { + t.Error("reconcile deleted the wrong object", deleteNsn) + } +} + +func TestDownstreamDeletionAlreadyGone(t *testing.T) { + t.Parallel() + + var deleteNsn struct { + ns, name string + } + r := readReconciler{ + getServiceNamespace: defaultNamespace, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + return nil, errors.NewNotFound(v1.Resource("Secret"), name) + }, + getConsumerObject: notFound, + deleteConsumerObject: func(ctx context.Context, ns, name string) error { + deleteNsn = struct { + ns string + name string + }{ns: ns, name: name} + return errors.NewNotFound(v1.Resource("Secret"), name) + + }, + } + + err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") + if err != nil { + t.Fatal(err) + } + + if deleteNsn.name != "dummy" || deleteNsn.ns != "default" { + t.Error("reconcile deleted the wrong object", deleteNsn) + } +} + +func TestUpdate(t *testing.T) { + t.Parallel() + + var updateObj *unstructured.Unstructured + r := readReconciler{ + getServiceNamespace: defaultNamespace, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + obj := &unstructured.Unstructured{} + obj.SetUnstructuredContent( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "dummy", + "namespace": "cluster-x-default", + }, + "data": map[string]interface{}{ + "username": "user", + "password": "pass", + }, + }, + ) + return obj, nil + }, + getConsumerObject: func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) { + obj := &unstructured.Unstructured{} + obj.SetUnstructuredContent( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "dummy", + "namespace": "default", + "annotations": map[string]interface{}{ + "kube-bind.io/claimedresource": "true", + }, + }, + "data": map[string]interface{}{ + "username": "user", + }, + }, + ) + return obj, nil + }, + updateConsumerObject: func(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + updateObj = obj + return obj, nil + }, + } + + err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") + if err != nil { + t.Fatal(err) + } + + if updateObj == nil { + t.Fatal("update object nil") + } + if v, ok := updateObj.GetAnnotations()["kube-bind.io/claimedresource"]; !ok || v != "true" { + t.Error("updated object did not have 'kube-bind.io/claimedresource: true' annotation") + } +} +func TestUpdateNotNeeded(t *testing.T) { + t.Parallel() + + r := readReconciler{ + getServiceNamespace: defaultNamespace, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + obj := &unstructured.Unstructured{} + obj.SetUnstructuredContent( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "dummy", + "namespace": "cluster-x-default", + }, + "data": map[string]interface{}{ + "username": "user", + "password": "pass", + }, + }, + ) + return obj, nil + }, + getConsumerObject: func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) { + obj := &unstructured.Unstructured{} + obj.SetUnstructuredContent( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "dummy", + "namespace": "default", + }, + "data": map[string]interface{}{ + "username": "user", + "password": "pass", + }, + }, + ) + obj.SetAnnotations(map[string]string{ + "kube-bind.io/claimedresource": "true", + }) + return obj, nil + }, + updateConsumerObject: func(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + t.Fatal("update function called although not needed", obj) + return nil, nil + }, + } + + err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go index 00a24ce69..310172dee 100644 --- a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go +++ b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go @@ -36,6 +36,7 @@ import ( conditionsapi "github.com/kube-bind/kube-bind/pkg/apis/third_party/conditions/apis/conditions/v1alpha1" "github.com/kube-bind/kube-bind/pkg/apis/third_party/conditions/util/conditions" bindlisters "github.com/kube-bind/kube-bind/pkg/client/listers/kubebind/v1alpha1" + "github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/claimedresources" "github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/serviceexport/multinsinformer" "github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/serviceexport/spec" "github.com/kube-bind/kube-bind/pkg/konnector/controllers/cluster/serviceexport/status" @@ -209,6 +210,68 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export return nil // nothing we can do here } + var claimControllers []func(context.Context, int) + for _, claim := range binding.Spec.PermissionClaims { + claim := claim + + if claim.State != kubebindv1alpha1.ClaimAccepted { + logger.Info("skipping non accepted claim", "claim", claim) + continue + } + + claimGVR := runtimeschema.GroupVersionResource{ + Group: claim.Group, + Version: claim.Version, + Resource: claim.Resource, + } + + var providerInf multinsinformer.GetterInformer + if claim.All { + factory := dynamicinformer.NewDynamicSharedInformerFactory(dynamicProviderClient, time.Minute*30) + factory.ForResource(claimGVR).Lister() // wire the GVR up in the informer factory + providerInf = multinsinformer.GetterInformerWrapper{ + GVR: claimGVR, + Delegate: factory, + } + } else { + providerInf, err = multinsinformer.NewDynamicMultiNamespaceInformer( + claimGVR, + r.providerNamespace, + r.providerConfig, + r.serviceNamespaceInformer, + ) + if err != nil { + logger.Info("aborting", "error", err) + return err + } + } + claimedCtrl, err := claimedresources.NewController( + claimGVR, + r.providerNamespace, + r.consumerConfig, + r.providerConfig, + consumerInf.ForResource(claimGVR), + providerInf, + r.serviceNamespaceInformer, + ) + + if err != nil { + runtime.HandleError(err) + return nil //nothing we can do here + } + logger.Info("creating claim reconciler", "gvr", claimGVR) + + claimControllers = append(claimControllers, func(ctx context.Context, i int) { + providerInf.Start(ctx) + + providerSynced := providerInf.WaitForCacheSync(ctx.Done()) + logger.V(2).Info("Synced informers", "provider", providerSynced) + + claimedCtrl.Start(ctx, i) + }) + + } + ctx, cancel := context.WithCancel(ctx) consumerInf.Start(ctx.Done()) @@ -224,6 +287,10 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export go specCtrl.Start(ctx, 1) go statusCtrl.Start(ctx, 1) + + for _, f := range claimControllers { + go f(ctx, 1) + } }() r.lock.Lock() From bd5ccf11cce4bef852806eef7748c21fba43adf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Tue, 28 Feb 2023 13:16:22 +0100 Subject: [PATCH 05/26] Use tempfile instead of stdin to pass manifest to bind apiservice --- .../bind-apiservice/plugin/servicebindings.go | 11 +++++---- pkg/kubectl/bind/cmd/cmd.go | 1 + pkg/kubectl/bind/plugin/bind.go | 23 +++++++++++++++---- test/e2e/bind/happy-case_test.go | 4 ++-- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go index 673152067..45e2411f2 100644 --- a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go +++ b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go @@ -20,7 +20,6 @@ import ( "bufio" "context" "fmt" - "os" "strings" "time" @@ -74,7 +73,7 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co var permissionClaims []kubebindv1alpha1.AcceptablePermissionClaim for _, c := range resource.PermissionClaims { - accepted, err := promptYesNo(c) + accepted, err := b.promptYesNo(c) if err != nil { return nil, err } @@ -140,11 +139,13 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co return bindings, nil } -func promptYesNo(p kubebindv1alpha1.PermissionClaim) (bool, error) { - reader := bufio.NewReader(os.Stdin) +func (opt BindAPIServiceOptions) promptYesNo(p kubebindv1alpha1.PermissionClaim) (bool, error) { + + fmt.Printf("%+v", opt.Options.IOStreams) + reader := bufio.NewReader(opt.Options.IOStreams.In) for { - fmt.Printf("binding wants permission\n%+v\n[Y/N]", p) + fmt.Fprintf(opt.Options.IOStreams.Out, "binding wants permission\n%+v\n[Y/N]", p) response, err := reader.ReadString('\n') if err != nil { diff --git a/pkg/kubectl/bind/cmd/cmd.go b/pkg/kubectl/bind/cmd/cmd.go index 0a77c111b..555f9001c 100644 --- a/pkg/kubectl/bind/cmd/cmd.go +++ b/pkg/kubectl/bind/cmd/cmd.go @@ -64,6 +64,7 @@ func New(streams genericclioptions.IOStreams) (*cobra.Command, error) { return nil }, RunE: func(cmd *cobra.Command, args []string) error { + defer opts.Cleanup() if err := logsv1.ValidateAndApply(opts.Logs, nil); err != nil { return err } diff --git a/pkg/kubectl/bind/plugin/bind.go b/pkg/kubectl/bind/plugin/bind.go index d5bb90d30..84f35bc8a 100644 --- a/pkg/kubectl/bind/plugin/bind.go +++ b/pkg/kubectl/bind/plugin/bind.go @@ -17,7 +17,6 @@ limitations under the License. package plugin import ( - "bytes" "context" "crypto/sha256" "encoding/json" @@ -70,11 +69,16 @@ type BindOptions struct { // Runner is runs the command. It can be replaced in tests. Runner func(cmd *exec.Cmd) error - flags *pflag.FlagSet + flags *pflag.FlagSet + outFile *os.File } // NewBindOptions returns new BindOptions. func NewBindOptions(streams genericclioptions.IOStreams) *BindOptions { + f, err := os.CreateTemp("", "*.yaml") + if err != nil { + panic(err) + } opts := &BindOptions{ Options: base.NewOptions(streams), Logs: logs.NewOptions(), @@ -83,6 +87,7 @@ func NewBindOptions(streams genericclioptions.IOStreams) *BindOptions { Runner: func(cmd *exec.Cmd) error { return cmd.Run() }, + outFile: f, } return opts @@ -271,11 +276,17 @@ func (b *BindOptions) Run(ctx context.Context, urlCh chan<- string) error { return err } + f := b.outFile + _, err = f.Write(bs) + if err != nil { + return err + } + args := []string{ "apiservice", "--remote-kubeconfig-namespace", secret.Namespace, "--remote-kubeconfig-name", secret.Name, - "-f", "-", + "-f", f.Name(), } b.flags.VisitAll(func(flag *pflag.Flag) { if flag.Changed && PassOnFlags.Has(flag.Name) { @@ -292,7 +303,7 @@ func (b *BindOptions) Run(ctx context.Context, urlCh chan<- string) error { fmt.Fprintf(b.Options.ErrOut, "🚀 Executing: %s %s\n", "kubectl bind", strings.Join(args, " ")) // nolint: errcheck fmt.Fprintf(b.Options.ErrOut, "✨ Use \"-o yaml\" and \"--dry-run\" to get the APIServiceExportRequest.\n and pass it to \"kubectl bind apiservice\" directly. Great for automation.\n") command := exec.CommandContext(ctx, executable, append(args, "--no-banner")...) - command.Stdin = bytes.NewReader(bs) + command.Stdin = b.Options.IOStreams.In command.Stdout = b.Options.Out command.Stderr = b.Options.ErrOut if err := b.Runner(command); err != nil { @@ -303,6 +314,10 @@ func (b *BindOptions) Run(ctx context.Context, urlCh chan<- string) error { return nil } +func (opts *BindOptions) Cleanup() { + os.Remove(opts.outFile.Name()) +} + func ClusterID(ns *corev1.Namespace) string { hash := sha256.Sum224([]byte(ns.UID)) base62hash := toBase62(hash) diff --git a/test/e2e/bind/happy-case_test.go b/test/e2e/bind/happy-case_test.go index f3d67e4ac..19978ed44 100644 --- a/test/e2e/bind/happy-case_test.go +++ b/test/e2e/bind/happy-case_test.go @@ -102,7 +102,7 @@ func testHappyCase(t *testing.T, scope kubebindv1alpha1.Scope) { invocations := make(chan framework.SubCommandInvocation, 1) framework.Bind(t, iostreams, authURLCh, invocations, fmt.Sprintf("http://%s/export", addr.String()), "--kubeconfig", consumerKubeconfig, "--skip-konnector") inv := <-invocations - requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "-", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args) + requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "*", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args) framework.BindAPIService(t, inv.Stdin, "", inv.Args...) t.Logf("Waiting for MangoDB CRD to be created on consumer side") @@ -238,7 +238,7 @@ spec: invocations := make(chan framework.SubCommandInvocation, 1) framework.Bind(t, iostreams, authURLCh, invocations, fmt.Sprintf("http://%s/export", addr.String()), "--kubeconfig", consumerKubeconfig, "--skip-konnector") inv := <-invocations - requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "-", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args) + requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "*", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args) framework.BindAPIService(t, inv.Stdin, "", inv.Args...) }, }, From 4131eac687de8dd863927dd70d2dfae13627a740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 9 Mar 2023 11:24:26 +0100 Subject: [PATCH 06/26] Change API definitions to community proposal --- .../clusterbinding_reconcile.go | 8 +- .../serviceexportrequest_reconcile.go | 2 +- contrib/example-backend/http/handler.go | 2 +- .../crd/kube-bind.io_apiservicebindings.yaml | 113 +++++++++++----- ...kube-bind.io_apiserviceexportrequests.yaml | 127 ++++++++++++------ .../crd/kube-bind.io_apiserviceexports.yaml | 120 ++++++++++++----- ...ube-bind.io_apiserviceexporttemplates.yaml | 123 ++++++++++++----- .../v1alpha1/apiservicebinding_types.go | 90 ++++++++++--- .../apiserviceexporttemplate_types.go | 3 +- .../v1alpha1/zz_generated.deepcopy.go | 98 +++++++++----- .../serviceexport/serviceexport_reconcile.go | 7 +- 11 files changed, 486 insertions(+), 207 deletions(-) diff --git a/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go b/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go index 97d1d89c6..e4a4d937a 100644 --- a/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go +++ b/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go @@ -154,16 +154,12 @@ func (r *reconciler) ensureRBACClusterRole(ctx context.Context, clusterBinding * Verbs: []string{"get", "list", "watch", "update", "patch", "delete", "create"}, }) for _, e := range export.Spec.PermissionClaims { - var verbs []string - - for _, v := range e.Verbs.Provider { - verbs = append(verbs, string(v)) - } + // TODO more fine grained filtering expected.Rules = append(expected.Rules, rbacv1.PolicyRule{ APIGroups: []string{e.Group}, Resources: []string{e.Resource}, - Verbs: verbs, + Verbs: []string{"get", "list", "watch", "update", "patch", "delete", "create"}, }) } } diff --git a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go index b133f9a7e..482f38b5d 100644 --- a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go +++ b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go @@ -117,7 +117,7 @@ func (r *reconciler) ensureExports(ctx context.Context, req *kubebindv1alpha1.AP Spec: kubebindv1alpha1.APIServiceExportSpec{ APIServiceExportCRDSpec: *exportSpec, InformerScope: r.informerScope, - PermissionClaims: template.Spec.ClaimedResources, + PermissionClaims: template.Spec.PermissionClaims, }, } diff --git a/contrib/example-backend/http/handler.go b/contrib/example-backend/http/handler.go index 1897398b4..7a56dab72 100644 --- a/contrib/example-backend/http/handler.go +++ b/contrib/example-backend/http/handler.go @@ -387,7 +387,7 @@ func (h *handler) handleBind(w http.ResponseWriter, r *http.Request) { Resources: []kubebindv1alpha1.APIServiceExportRequestResource{ { GroupResource: kubebindv1alpha1.GroupResource{Group: group, Resource: resource}, - PermissionClaims: exportTemplate.Spec.ClaimedResources, + PermissionClaims: exportTemplate.Spec.PermissionClaims, }, }, }, diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index b510e0b7c..fdae182a7 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -92,8 +92,24 @@ spec: Access is granted per GroupResource, identity, and other properties. items: properties: - all: - description: all claims all resources for the given group/resource. + adopt: + description: adopt set to true means that objects created by + the consumer are adopted by the provider. i.e. the provider + will become the owner. + type: boolean + create: + description: "only for owner Provider \n create determines whether + the kube-bind konnector will sync matching objects from the + provider side down to the consumer cluster." + properties: + donate: + description: donate set to true means that a newly created + object by the provider is immediately owned by hte consumer. + If false, the object stays in ownership of the provider + type: boolean + type: object + global: + description: Global claims global resources for the given group/resource. This is mutually exclusive with resourceSelector. type: boolean group: @@ -102,49 +118,83 @@ spec: 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 + onConflict: + description: onConflict determines how the conflicts between + objects on the consumer side will be resolved. + properties: + providerOverrides: + description: providerOverrides will make the provider override + any object that might already exist in the consumer cluster + if it has the same namespaced name as a resource created + by the provider. + type: boolean + recreateWhenConsumerSideDeleted: + description: only for owner provider When recreateWhenConsumerSideDeleted + is true the provider will recreate the object in case + the object is missing on the consumer side. Even if the + consumer mistakenly or intentionally deletes the objet, + the provider will recreate it. If the field is set as + false, the provider will not recreate the object in case + the object is deleted on the RecreateWhenConsumerSideDeleted + side. + type: boolean + type: object + required: + description: required indicates whether the APIServiceBinding + will work if this claim is not accepted. + type: boolean 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 - resourceSelector: - description: resourceSelector is a list of claimed resource - selectors. - items: - properties: - name: - description: name of an object within a claimed group/resource. - It matches the metadata.name field of the underlying - object. If namespace is unset, all objects matching - that name will be claimed. - maxLength: 253 - minLength: 1 - pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ - type: string - namespace: - description: namespace containing the named object. Matches - metadata.namespace field. If "name" is unset, all objects - from the namespace are being claimed. - minLength: 1 - type: string - type: object - x-kubernetes-validations: - - message: at least one field must be set - rule: has(self.__namespace__) || has(self.name) - type: array + selector: + description: selector selects which resources are affected by + this claim. + properties: + name: + description: name of an object within a claimed group/resource. + It matches the metadata.name field of the underlying object. + If name is unset, all objects in bound namespaces will + be claimed. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ + type: string + owner: + enum: + - Provider + - Consumer + type: string + type: object state: enum: - Accepted - Rejected type: string - verbs: + update: + description: update lists a number of claimed permissions for + the provider. "field" and "preserving" are mutually exclusive. properties: - consumer: + alwaysRecreate: + description: alwaysRecreate, when true will make the konnector + delete the old object and create a new one instead of + updating. Useful for immutable objects. + type: boolean + fields: + description: fields are the fields owned by the owner of + the claim. If the owner sets values of those fields, they + will be synced to the other participant. Mutually exclusive + with preservings. items: type: string type: array - provider: + preservings: + description: Preservings are the fields that are preserved + by the konnector during synchronization. The owner is + not able to set those fields. If the owner changes the + value of these fields, their change will be overwritten. items: type: string type: array @@ -155,6 +205,9 @@ spec: - resource - state type: object + x-kubernetes-validations: + - message: donate and adopt are mutually exclusive + rule: '!(self.create.donate && self.adopt)' type: array required: - kubeconfigSecretRef diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 20e473677..a647b7891 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -65,18 +65,31 @@ spec: type: string permissionClaims: items: - description: "PermissionClaim identifies an object by GR and + description: PermissionClaim identifies an object by GR and identity hash. Its purpose is to determine the added permissions that a service provider may request and that a consumer - may accept and allow the service provider access to. \n - TODO fix validation kubebuilder:validation:XValidation:rule=\"(has(self.all) - && self.all) != (has(self.resourceSelector) && size(self.resourceSelector) - > 0)\",message=\"either \\\"all\\\" or \\\"resourceSelector\\\" - must be set\"" + may accept and allow the service provider access to. properties: - all: - description: all claims all resources for the given group/resource. - This is mutually exclusive with resourceSelector. + adopt: + description: adopt set to true means that objects created + by the consumer are adopted by the provider. i.e. the + provider will become the owner. + type: boolean + create: + description: "only for owner Provider \n create determines + whether the kube-bind konnector will sync matching objects + from the provider side down to the consumer cluster." + properties: + donate: + description: donate set to true means that a newly + created object by the provider is immediately owned + by hte consumer. If false, the object stays in ownership + of the provider + type: boolean + type: object + global: + description: Global claims global resources for the given + group/resource. This is mutually exclusive with resourceSelector. type: boolean group: default: "" @@ -84,6 +97,31 @@ spec: 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 + onConflict: + description: onConflict determines how the conflicts between + objects on the consumer side will be resolved. + properties: + providerOverrides: + description: providerOverrides will make the provider + override any object that might already exist in + the consumer cluster if it has the same namespaced + name as a resource created by the provider. + type: boolean + recreateWhenConsumerSideDeleted: + description: only for owner provider When recreateWhenConsumerSideDeleted + is true the provider will recreate the object in + case the object is missing on the consumer side. + Even if the consumer mistakenly or intentionally + deletes the objet, the provider will recreate it. + If the field is set as false, the provider will + not recreate the object in case the object is deleted + on the RecreateWhenConsumerSideDeleted side. + type: boolean + type: object + required: + description: required indicates whether the APIServiceBinding + will work if this claim is not accepted. + type: boolean resource: description: 'resource is the name of the resource. Note: it is worth noting that you can not ask for permissions @@ -91,39 +129,49 @@ spec: binding export.' pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string - resourceSelector: - description: resourceSelector is a list of claimed resource - selectors. - items: - properties: - name: - description: name of an object within a claimed - group/resource. It matches the metadata.name field - of the underlying object. If namespace is unset, - all objects matching that name will be claimed. - maxLength: 253 - minLength: 1 - pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ - type: string - namespace: - description: namespace containing the named object. - Matches metadata.namespace field. If "name" is - unset, all objects from the namespace are being - claimed. - minLength: 1 - type: string - type: object - x-kubernetes-validations: - - message: at least one field must be set - rule: has(self.__namespace__) || has(self.name) - type: array - verbs: + selector: + description: selector selects which resources are affected + by this claim. + properties: + name: + description: name of an object within a claimed group/resource. + It matches the metadata.name field of the underlying + object. If name is unset, all objects in bound namespaces + will be claimed. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ + type: string + owner: + enum: + - Provider + - Consumer + type: string + type: object + update: + description: update lists a number of claimed permissions + for the provider. "field" and "preserving" are mutually + exclusive. properties: - consumer: + alwaysRecreate: + description: alwaysRecreate, when true will make the + konnector delete the old object and create a new + one instead of updating. Useful for immutable objects. + type: boolean + fields: + description: fields are the fields owned by the owner + of the claim. If the owner sets values of those + fields, they will be synced to the other participant. + Mutually exclusive with preservings. items: type: string type: array - provider: + preservings: + description: Preservings are the fields that are preserved + by the konnector during synchronization. The owner + is not able to set those fields. If the owner changes + the value of these fields, their change will be + overwritten. items: type: string type: array @@ -133,6 +181,9 @@ spec: required: - resource type: object + x-kubernetes-validations: + - message: donate and adopt are mutually exclusive + rule: '!(self.create.donate && self.adopt)' type: array resource: description: 'resource is the name of the resource. Note: it diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index 90020b6b9..e8c72ab85 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -110,16 +110,29 @@ spec: type: object permissionClaims: items: - description: "PermissionClaim identifies an object by GR and identity + description: PermissionClaim identifies an object by GR and identity hash. Its purpose is to determine the added permissions that a service provider may request and that a consumer may accept and - allow the service provider access to. \n TODO fix validation kubebuilder:validation:XValidation:rule=\"(has(self.all) - && self.all) != (has(self.resourceSelector) && size(self.resourceSelector) - > 0)\",message=\"either \\\"all\\\" or \\\"resourceSelector\\\" - must be set\"" + allow the service provider access to. properties: - all: - description: all claims all resources for the given group/resource. + adopt: + description: adopt set to true means that objects created by + the consumer are adopted by the provider. i.e. the provider + will become the owner. + type: boolean + create: + description: "only for owner Provider \n create determines whether + the kube-bind konnector will sync matching objects from the + provider side down to the consumer cluster." + properties: + donate: + description: donate set to true means that a newly created + object by the provider is immediately owned by hte consumer. + If false, the object stays in ownership of the provider + type: boolean + type: object + global: + description: Global claims global resources for the given group/resource. This is mutually exclusive with resourceSelector. type: boolean group: @@ -128,44 +141,78 @@ spec: 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 + onConflict: + description: onConflict determines how the conflicts between + objects on the consumer side will be resolved. + properties: + providerOverrides: + description: providerOverrides will make the provider override + any object that might already exist in the consumer cluster + if it has the same namespaced name as a resource created + by the provider. + type: boolean + recreateWhenConsumerSideDeleted: + description: only for owner provider When recreateWhenConsumerSideDeleted + is true the provider will recreate the object in case + the object is missing on the consumer side. Even if the + consumer mistakenly or intentionally deletes the objet, + the provider will recreate it. If the field is set as + false, the provider will not recreate the object in case + the object is deleted on the RecreateWhenConsumerSideDeleted + side. + type: boolean + type: object + required: + description: required indicates whether the APIServiceBinding + will work if this claim is not accepted. + type: boolean 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 - resourceSelector: - description: resourceSelector is a list of claimed resource - selectors. - items: - properties: - name: - description: name of an object within a claimed group/resource. - It matches the metadata.name field of the underlying - object. If namespace is unset, all objects matching - that name will be claimed. - maxLength: 253 - minLength: 1 - pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ - type: string - namespace: - description: namespace containing the named object. Matches - metadata.namespace field. If "name" is unset, all objects - from the namespace are being claimed. - minLength: 1 - type: string - type: object - x-kubernetes-validations: - - message: at least one field must be set - rule: has(self.__namespace__) || has(self.name) - type: array - verbs: + selector: + description: selector selects which resources are affected by + this claim. + properties: + name: + description: name of an object within a claimed group/resource. + It matches the metadata.name field of the underlying object. + If name is unset, all objects in bound namespaces will + be claimed. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ + type: string + owner: + enum: + - Provider + - Consumer + type: string + type: object + update: + description: update lists a number of claimed permissions for + the provider. "field" and "preserving" are mutually exclusive. properties: - consumer: + alwaysRecreate: + description: alwaysRecreate, when true will make the konnector + delete the old object and create a new one instead of + updating. Useful for immutable objects. + type: boolean + fields: + description: fields are the fields owned by the owner of + the claim. If the owner sets values of those fields, they + will be synced to the other participant. Mutually exclusive + with preservings. items: type: string type: array - provider: + preservings: + description: Preservings are the fields that are preserved + by the konnector during synchronization. The owner is + not able to set those fields. If the owner changes the + value of these fields, their change will be overwritten. items: type: string type: array @@ -175,6 +222,9 @@ spec: required: - resource type: object + x-kubernetes-validations: + - message: donate and adopt are mutually exclusive + rule: '!(self.create.donate && self.adopt)' maxItems: 2 type: array x-kubernetes-list-map-keys: diff --git a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml index 73c159490..d5a440210 100644 --- a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml @@ -58,18 +58,31 @@ spec: type: string type: array type: object - claimedResources: + permissionClaims: items: - description: "PermissionClaim identifies an object by GR and identity + description: PermissionClaim identifies an object by GR and identity hash. Its purpose is to determine the added permissions that a service provider may request and that a consumer may accept and - allow the service provider access to. \n TODO fix validation kubebuilder:validation:XValidation:rule=\"(has(self.all) - && self.all) != (has(self.resourceSelector) && size(self.resourceSelector) - > 0)\",message=\"either \\\"all\\\" or \\\"resourceSelector\\\" - must be set\"" + allow the service provider access to. properties: - all: - description: all claims all resources for the given group/resource. + adopt: + description: adopt set to true means that objects created by + the consumer are adopted by the provider. i.e. the provider + will become the owner. + type: boolean + create: + description: "only for owner Provider \n create determines whether + the kube-bind konnector will sync matching objects from the + provider side down to the consumer cluster." + properties: + donate: + description: donate set to true means that a newly created + object by the provider is immediately owned by hte consumer. + If false, the object stays in ownership of the provider + type: boolean + type: object + global: + description: Global claims global resources for the given group/resource. This is mutually exclusive with resourceSelector. type: boolean group: @@ -78,44 +91,78 @@ spec: 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 + onConflict: + description: onConflict determines how the conflicts between + objects on the consumer side will be resolved. + properties: + providerOverrides: + description: providerOverrides will make the provider override + any object that might already exist in the consumer cluster + if it has the same namespaced name as a resource created + by the provider. + type: boolean + recreateWhenConsumerSideDeleted: + description: only for owner provider When recreateWhenConsumerSideDeleted + is true the provider will recreate the object in case + the object is missing on the consumer side. Even if the + consumer mistakenly or intentionally deletes the objet, + the provider will recreate it. If the field is set as + false, the provider will not recreate the object in case + the object is deleted on the RecreateWhenConsumerSideDeleted + side. + type: boolean + type: object + required: + description: required indicates whether the APIServiceBinding + will work if this claim is not accepted. + type: boolean 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 - resourceSelector: - description: resourceSelector is a list of claimed resource - selectors. - items: - properties: - name: - description: name of an object within a claimed group/resource. - It matches the metadata.name field of the underlying - object. If namespace is unset, all objects matching - that name will be claimed. - maxLength: 253 - minLength: 1 - pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ - type: string - namespace: - description: namespace containing the named object. Matches - metadata.namespace field. If "name" is unset, all objects - from the namespace are being claimed. - minLength: 1 - type: string - type: object - x-kubernetes-validations: - - message: at least one field must be set - rule: has(self.__namespace__) || has(self.name) - type: array - verbs: + selector: + description: selector selects which resources are affected by + this claim. + properties: + name: + description: name of an object within a claimed group/resource. + It matches the metadata.name field of the underlying object. + If name is unset, all objects in bound namespaces will + be claimed. + maxLength: 253 + minLength: 1 + pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ + type: string + owner: + enum: + - Provider + - Consumer + type: string + type: object + update: + description: update lists a number of claimed permissions for + the provider. "field" and "preserving" are mutually exclusive. properties: - consumer: + alwaysRecreate: + description: alwaysRecreate, when true will make the konnector + delete the old object and create a new one instead of + updating. Useful for immutable objects. + type: boolean + fields: + description: fields are the fields owned by the owner of + the claim. If the owner sets values of those fields, they + will be synced to the other participant. Mutually exclusive + with preservings. items: type: string type: array - provider: + preservings: + description: Preservings are the fields that are preserved + by the konnector during synchronization. The owner is + not able to set those fields. If the owner changes the + value of these fields, their change will be overwritten. items: type: string type: array @@ -125,7 +172,9 @@ spec: required: - resource type: object - maxItems: 2 + x-kubernetes-validations: + - message: donate and adopt are mutually exclusive + rule: '!(self.create.donate && self.adopt)' type: array type: object status: diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 1d8827558..e3950e1dd 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -119,31 +119,83 @@ const ( // Its purpose is to determine the added permissions that a service provider may // request and that a consumer may accept and allow the service provider access to. // -// TODO fix validation -// kubebuilder:validation:XValidation:rule="(has(self.all) && self.all) != (has(self.resourceSelector) && size(self.resourceSelector) > 0)",message="either \"all\" or \"resourceSelector\" must be set" +// +kubebuilder:validation:XValidation:rule="!(self.create.donate && self.adopt)",message="donate and adopt are mutually exclusive" type PermissionClaim struct { GroupResource `json:","` Version string `json:"version"` - // all claims all resources for the given group/resource. + // selector selects which resources are affected by this claim. + Selector ResourceSelector `json:"selector,omitempty"` + + // required indicates whether the APIServiceBinding will work if this claim is not accepted. + Required bool `json:"required"` + + // Global claims global resources for the given group/resource. // This is mutually exclusive with resourceSelector. // +optional - All bool `json:"all,omitempty"` - - Verbs ClaimVerbs `json:"verbs"` + Global bool `json:"global,omitempty"` - // resourceSelector is a list of claimed resource selectors. + // only for owner Provider // - // +optional - ResourceSelector []ResourceSelector `json:"resourceSelector,omitempty"` + // create determines whether the kube-bind konnector will sync matching objects from the + // provider side down to the consumer cluster. + Create CreateOptions `json:"create"` + + // adopt set to true means that objects created by the consumer are adopted by the provider. + // i.e. the provider will become the owner. + Adopt bool `json:"adopt"` + + // onConflict determines how the conflicts between objects on the consumer side + // will be resolved. + OnConflict OnConflictOptions `json:"onConflict,omitempty"` + + // update lists a number of claimed permissions for the provider. + // "field" and "preserving" are mutually exclusive. + Update UpdateOptions `json:"update"` +} + +type OnConflictOptions struct { + // providerOverrides will make the provider override any object that might already exist + // in the consumer cluster if it has the same namespaced name as a resource created by the + // provider. + ProviderOverwrites bool `json:"providerOverrides"` + + // only for owner provider + // When recreateWhenConsumerSideDeleted is true the provider will recreate the object + // in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally + // deletes the objet, the provider will recreate it. If the field is set as false, + // the provider will not recreate the object in case the object is deleted on the RecreateWhenConsumerSideDeleted + // side. + RecreateWhenConsumerSideDeleted bool `json:"recreateWhenConsumerSideDeleted"` +} + +type CreateOptions struct { + // donate set to true means that a newly created object by the provider is immediately owned by hte consumer. + // If false, the object stays in ownership of the provider + Donate bool `json:"donate"` +} + +type UpdateOptions struct { + // fields are the fields owned by the owner of the claim. If the owner sets values of those + // fields, they will be synced to the other participant. + // Mutually exclusive with preservings. + Fields []string `json:"fields,omitempty"` + + // Preservings are the fields that are preserved by the konnector during synchronization. + // The owner is not able to set those fields. If the owner changes the value of these fields, + // their change will be overwritten. + Preservings []string `json:"preservings,omitempty"` + + // alwaysRecreate, when true will make the konnector delete the old object and create a new one + // instead of updating. Useful for immutable objects. + AlwaysRecreate bool `json:"alwaysRecreate,omitempty"` } -// +kubebuilder:validation:XValidation:rule="has(self.__namespace__) || has(self.name)",message="at least one field must be set" type ResourceSelector struct { // name of an object within a claimed group/resource. // It matches the metadata.name field of the underlying object. - // If namespace is unset, all objects matching that name will be claimed. + // If name is unset, all objects in bound namespaces will be claimed. // // +optional // +kubebuilder:validation:Pattern="^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$" @@ -151,24 +203,18 @@ type ResourceSelector struct { // +kubebuilder:validation:MinLength=1 Name string `json:"name,omitempty"` - // namespace containing the named object. Matches metadata.namespace field. - // If "name" is unset, all objects from the namespace are being claimed. - // - // +optional - // +kubebuilder:validation:MinLength=1 - Namespace string `json:"namespace,omitempty"` + // +kubebuilder:validation:Enum=Provider;Consumer + Owner Owner `json:"owner"` // // WARNING: If adding new fields, add them to the XValidation check! // } -type ClaimVerbs struct { - Provider []Verb `json:"provider"` - Consumer []Verb `json:"consumer"` -} +type Owner string -type Verb string +const Provider Owner = "Provider" +const Consumer Owner = "Consumer" type APIServiceBindingStatus struct { // providerPrettyName is the pretty name of the service provider cluster. This diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go b/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go index 1070cbef3..836d4b1d7 100644 --- a/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go @@ -47,8 +47,7 @@ type APIServiceExportTemplate struct { type APIServiceExportTemplateSpec struct { APIServiceSelector APIServiceSelector `json:"APIServiceSelector"` // +optional - // +kubebuilder:validation:MaxItems=2 - ClaimedResources []PermissionClaim `json:"claimedResources,omitempty"` + PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` } type APIServiceExportTemplateStatus struct{} diff --git a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go index c77bc27c6..4cada002d 100644 --- a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go @@ -525,8 +525,8 @@ func (in *APIServiceExportTemplateList) DeepCopyObject() runtime.Object { func (in *APIServiceExportTemplateSpec) DeepCopyInto(out *APIServiceExportTemplateSpec) { *out = *in in.APIServiceSelector.DeepCopyInto(&out.APIServiceSelector) - if in.ClaimedResources != nil { - in, out := &in.ClaimedResources, &out.ClaimedResources + if in.PermissionClaims != nil { + in, out := &in.PermissionClaims, &out.PermissionClaims *out = make([]PermissionClaim, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) @@ -848,32 +848,6 @@ func (in *BindingResponseAuthenticationOAuth2CodeGrant) DeepCopy() *BindingRespo return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClaimVerbs) DeepCopyInto(out *ClaimVerbs) { - *out = *in - if in.Provider != nil { - in, out := &in.Provider, &out.Provider - *out = make([]Verb, len(*in)) - copy(*out, *in) - } - if in.Consumer != nil { - in, out := &in.Consumer, &out.Consumer - *out = make([]Verb, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClaimVerbs. -func (in *ClaimVerbs) DeepCopy() *ClaimVerbs { - if in == nil { - return nil - } - out := new(ClaimVerbs) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterBinding) DeepCopyInto(out *ClusterBinding) { *out = *in @@ -995,6 +969,22 @@ 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 *CreateOptions) DeepCopyInto(out *CreateOptions) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CreateOptions. +func (in *CreateOptions) DeepCopy() *CreateOptions { + if in == nil { + return nil + } + out := new(CreateOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupResource) DeepCopyInto(out *GroupResource) { *out = *in @@ -1059,16 +1049,30 @@ func (in *OAuth2CodeGrant) DeepCopy() *OAuth2CodeGrant { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OnConflictOptions) DeepCopyInto(out *OnConflictOptions) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OnConflictOptions. +func (in *OnConflictOptions) DeepCopy() *OnConflictOptions { + if in == nil { + return nil + } + out := new(OnConflictOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PermissionClaim) DeepCopyInto(out *PermissionClaim) { *out = *in out.GroupResource = in.GroupResource - in.Verbs.DeepCopyInto(&out.Verbs) - if in.ResourceSelector != nil { - in, out := &in.ResourceSelector, &out.ResourceSelector - *out = make([]ResourceSelector, len(*in)) - copy(*out, *in) - } + out.Selector = in.Selector + out.Create = in.Create + out.OnConflict = in.OnConflict + in.Update.DeepCopyInto(&out.Update) return } @@ -1097,3 +1101,29 @@ func (in *ResourceSelector) DeepCopy() *ResourceSelector { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpdateOptions) DeepCopyInto(out *UpdateOptions) { + *out = *in + if in.Fields != nil { + in, out := &in.Fields, &out.Fields + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Preservings != nil { + in, out := &in.Preservings, &out.Preservings + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpdateOptions. +func (in *UpdateOptions) DeepCopy() *UpdateOptions { + if in == nil { + return nil + } + out := new(UpdateOptions) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go index 310172dee..c0aa6b79e 100644 --- a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go +++ b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go @@ -219,6 +219,11 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export continue } + if claim.Selector.Owner == kubebindv1alpha1.Consumer { + // TODO implement upsync + continue + } + claimGVR := runtimeschema.GroupVersionResource{ Group: claim.Group, Version: claim.Version, @@ -226,7 +231,7 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export } var providerInf multinsinformer.GetterInformer - if claim.All { + if claim.Global { factory := dynamicinformer.NewDynamicSharedInformerFactory(dynamicProviderClient, time.Minute*30) factory.ForResource(claimGVR).Lister() // wire the GVR up in the informer factory providerInf = multinsinformer.GetterInformerWrapper{ From fbbe7b96940bcc164116d8f39645adb4bcee401b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 9 Mar 2023 12:59:02 +0100 Subject: [PATCH 07/26] Add copyright header --- contrib/example-backend/exporttemplate/index.go | 16 ++++++++++++++++ .../example-backend/exporttemplate/index_test.go | 16 ++++++++++++++++ .../claimedresources_reconciler_test.go | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/contrib/example-backend/exporttemplate/index.go b/contrib/example-backend/exporttemplate/index.go index ef8f8ad87..f996a9cd2 100644 --- a/contrib/example-backend/exporttemplate/index.go +++ b/contrib/example-backend/exporttemplate/index.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 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 exporttemplate // TODO by namespace diff --git a/contrib/example-backend/exporttemplate/index_test.go b/contrib/example-backend/exporttemplate/index_test.go index b2679a9e3..6b9b29431 100644 --- a/contrib/example-backend/exporttemplate/index_test.go +++ b/contrib/example-backend/exporttemplate/index_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 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 exporttemplate import ( diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go index b2937a9d4..be42a6c01 100644 --- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 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 claimedresources import ( From 7634fb855fae15da16c7a150bcffb6c35d859b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Fri, 10 Mar 2023 13:20:43 +0100 Subject: [PATCH 08/26] Add e2e test cases for claimed resource sync --- .../crd/kube-bind.io_apiservicebindings.yaml | 3 - ...kube-bind.io_apiserviceexportrequests.yaml | 10 +- .../crd/kube-bind.io_apiserviceexports.yaml | 8 +- ...ube-bind.io_apiserviceexporttemplates.yaml | 8 +- .../v1alpha1/apiservicebinding_types.go | 2 +- .../provider/exporttemplate-mangodb.yaml | 6 ++ test/e2e/bind/happy-case_test.go | 97 ++++++++++++++++++- test/e2e/framework/bind.go | 5 +- 8 files changed, 114 insertions(+), 25 deletions(-) diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index fdae182a7..a4c1b25fc 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -205,9 +205,6 @@ spec: - resource - state type: object - x-kubernetes-validations: - - message: donate and adopt are mutually exclusive - rule: '!(self.create.donate && self.adopt)' type: array required: - kubeconfigSecretRef diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index a647b7891..954b22909 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -65,10 +65,13 @@ spec: type: string permissionClaims: items: - description: PermissionClaim identifies an object by GR and + description: "PermissionClaim identifies an object by GR and identity hash. Its purpose is to determine the added permissions that a service provider may request and that a consumer - may accept and allow the service provider access to. + may accept and allow the service provider access to. \n + TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate + && self.adopt)\",message=\"donate and adopt are mutually + exclusive\"" properties: adopt: description: adopt set to true means that objects created @@ -181,9 +184,6 @@ spec: required: - resource type: object - x-kubernetes-validations: - - message: donate and adopt are mutually exclusive - rule: '!(self.create.donate && self.adopt)' type: array resource: description: 'resource is the name of the resource. Note: it diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index e8c72ab85..8d1f7a714 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -110,10 +110,11 @@ spec: type: object permissionClaims: items: - description: PermissionClaim identifies an object by GR and identity + description: "PermissionClaim identifies an object by GR and identity hash. Its purpose is to determine the added permissions that a service provider may request and that a consumer may accept and - allow the service provider access to. + allow the service provider access to. \n TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate + && self.adopt)\",message=\"donate and adopt are mutually exclusive\"" properties: adopt: description: adopt set to true means that objects created by @@ -222,9 +223,6 @@ spec: required: - resource type: object - x-kubernetes-validations: - - message: donate and adopt are mutually exclusive - rule: '!(self.create.donate && self.adopt)' maxItems: 2 type: array x-kubernetes-list-map-keys: diff --git a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml index d5a440210..3ff10f7c3 100644 --- a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml @@ -60,10 +60,11 @@ spec: type: object permissionClaims: items: - description: PermissionClaim identifies an object by GR and identity + description: "PermissionClaim identifies an object by GR and identity hash. Its purpose is to determine the added permissions that a service provider may request and that a consumer may accept and - allow the service provider access to. + allow the service provider access to. \n TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate + && self.adopt)\",message=\"donate and adopt are mutually exclusive\"" properties: adopt: description: adopt set to true means that objects created by @@ -172,9 +173,6 @@ spec: required: - resource type: object - x-kubernetes-validations: - - message: donate and adopt are mutually exclusive - rule: '!(self.create.donate && self.adopt)' type: array type: object status: diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index e3950e1dd..f7836c9c2 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -119,7 +119,7 @@ const ( // Its purpose is to determine the added permissions that a service provider may // request and that a consumer may accept and allow the service provider access to. // -// +kubebuilder:validation:XValidation:rule="!(self.create.donate && self.adopt)",message="donate and adopt are mutually exclusive" +// TODO fix validation +kubebuilder:validation:XValidation:rule="!(self.create.donate && self.adopt)",message="donate and adopt are mutually exclusive" type PermissionClaim struct { GroupResource `json:","` diff --git a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml index 97b20db77..4eddc74ce 100644 --- a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml +++ b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml @@ -7,3 +7,9 @@ spec: APIServiceSelector: resource: mangodbs group: mangodb.com + permissionClaims: + - group: "" + resource: secrets + version: v1 + selector: + owner: Provider diff --git a/test/e2e/bind/happy-case_test.go b/test/e2e/bind/happy-case_test.go index 19978ed44..acccc83a4 100644 --- a/test/e2e/bind/happy-case_test.go +++ b/test/e2e/bind/happy-case_test.go @@ -17,8 +17,10 @@ limitations under the License. package bind import ( + "bytes" "context" "fmt" + "reflect" "strings" "testing" "time" @@ -26,6 +28,7 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/headzoo/surf.v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -75,8 +78,10 @@ func testHappyCase(t *testing.T, scope kubebindv1alpha1.Scope) { providerClient := framework.DynamicClient(t, providerConfig).Resource( schema.GroupVersionResource{Group: "mangodb.com", Version: "v1alpha1", Resource: "mangodbs"}, ) - + providerKubeClient := framework.KubeClient(t, providerConfig) + consumerKubeClient := framework.KubeClient(t, consumerConfig) upstreamNS := "unknown" + downstreamNS := "unknown" for _, tc := range []struct { name string @@ -96,6 +101,7 @@ func testHappyCase(t *testing.T, scope kubebindv1alpha1.Scope) { { name: "MangoDB is bound", step: func(t *testing.T) { + in := bytes.NewBufferString("y\n") iostreams, _, _, _ := genericclioptions.NewTestIOStreams() authURLCh := make(chan string, 1) go simulateBrowser(t, authURLCh, "mangodbs") @@ -103,7 +109,7 @@ func testHappyCase(t *testing.T, scope kubebindv1alpha1.Scope) { framework.Bind(t, iostreams, authURLCh, invocations, fmt.Sprintf("http://%s/export", addr.String()), "--kubeconfig", consumerKubeconfig, "--skip-konnector") inv := <-invocations requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "*", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args) - framework.BindAPIService(t, inv.Stdin, "", inv.Args...) + framework.BindAPIService(t, in, "", inv.Args...) t.Logf("Waiting for MangoDB CRD to be created on consumer side") crdClient := framework.ApiextensionsClient(t, consumerConfig).ApiextensionsV1().CustomResourceDefinitions() @@ -130,6 +136,17 @@ spec: return err == nil }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for MangoDB CRD to be created on consumer side") + t.Logf("Waiting for the MangoDB instance to be created on consumer side") + var consumerMangos *unstructured.UnstructuredList + require.Eventually(t, func() bool { + var err error + consumerMangos, err = consumerClient.List(ctx, metav1.ListOptions{}) + return err == nil && len(consumerMangos.Items) == 1 + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for the MangoDB instance to be created on consumer side") + + // this is used everywhere further down + downstreamNS = consumerMangos.Items[0].GetNamespace() + t.Logf("Waiting for the MangoDB instance to be created on provider side") var mangos *unstructured.UnstructuredList require.Eventually(t, func() bool { @@ -154,6 +171,80 @@ spec: }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for the MangoDB instance to be recreated upstream") }, }, + { + name: "claimed resource created upstream is created downstream", + step: func(t *testing.T) { + testSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: upstreamNS, + }, + Data: map[string][]byte{ + "test": []byte("dummy"), + }, + } + + _, err := providerKubeClient.CoreV1().Secrets(upstreamNS).Create(ctx, &testSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + require.Eventually(t, func() bool { + s, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + return err == nil && reflect.DeepEqual(testSecret.Data, s.Data) + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for the claimed resource to be created downstream") + }, + }, + { + name: "claimed resource recreated downstream if created upstream", + step: func(t *testing.T) { + err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Delete(ctx, "test-secret", metav1.DeleteOptions{}) + require.NoError(t, err) + + require.Eventually(t, func() bool { + _, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for the claimed resource to be created downstream") + }, + }, + { + name: "claimed resource updated upstream is updated downstream", + step: func(t *testing.T) { + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + obj, err := providerKubeClient.CoreV1().Secrets(upstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + require.NoError(t, err) + obj.Data["test"] = []byte("updated") + _, err = providerKubeClient.CoreV1().Secrets(upstreamNS).Update(ctx, obj, metav1.UpdateOptions{}) + return err + }) + require.NoError(t, err) + + require.Eventually(t, func() bool { + obj, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + require.NoError(t, err) + updatedValue, ok := obj.Data["test"] + if !ok { + return false + } + + return string(updatedValue) == "updated" + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for claimed secret to be updated downstream") + }, + }, + { + name: "claimed resources deleted by the provider are deleted downstream", + step: func(t *testing.T) { + err := providerKubeClient.CoreV1().Secrets(upstreamNS).Delete(ctx, "test-secret", metav1.DeleteOptions{}) + require.NoError(t, err) + + require.Eventually(t, func() bool { + _, err := consumerKubeClient.CoreV1().Secrets(upstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + return errors.IsNotFound(err) + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for claimed secret to be deleted on consumer side") + }, + }, { name: "instance spec updated downstream is updated upstream", step: func(t *testing.T) { @@ -239,7 +330,7 @@ spec: framework.Bind(t, iostreams, authURLCh, invocations, fmt.Sprintf("http://%s/export", addr.String()), "--kubeconfig", consumerKubeconfig, "--skip-konnector") inv := <-invocations requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "*", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args) - framework.BindAPIService(t, inv.Stdin, "", inv.Args...) + framework.BindAPIService(t, bytes.NewBufferString("y\n"), "", inv.Args...) }, }, } { diff --git a/test/e2e/framework/bind.go b/test/e2e/framework/bind.go index 647908fae..7aebfa55d 100644 --- a/test/e2e/framework/bind.go +++ b/test/e2e/framework/bind.go @@ -17,7 +17,6 @@ limitations under the License. package framework import ( - "bytes" "context" "io" "os" @@ -82,7 +81,7 @@ type SubCommandInvocation struct { Stdin []byte } -func BindAPIService(t *testing.T, Stdin []byte, positionalArg string, flags ...string) { +func BindAPIService(t *testing.T, Stdin io.Reader, positionalArg string, flags ...string) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -92,7 +91,7 @@ func BindAPIService(t *testing.T, Stdin []byte, positionalArg string, flags ...s } t.Logf("kubectl bind apiservice %s", strings.Join(args, " ")) - opts := bindapiserviceplugin.NewBindAPIServiceOptions(genericclioptions.IOStreams{In: bytes.NewReader(Stdin), Out: os.Stdout, ErrOut: os.Stderr}) + opts := bindapiserviceplugin.NewBindAPIServiceOptions(genericclioptions.IOStreams{In: Stdin, Out: os.Stdout, ErrOut: os.Stderr}) cmd := &cobra.Command{} opts.AddCmdFlags(cmd) err := cmd.Flags().Parse(flags) From 30ba22e3795e76b93bc885fd1428837682ea3235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Mon, 13 Mar 2023 20:12:56 +0100 Subject: [PATCH 09/26] fixup! Change API definitions to community proposal --- .../crd/kube-bind.io_apiservicebindings.yaml | 18 +++++------ ...kube-bind.io_apiserviceexportrequests.yaml | 25 ++++++++++------ .../crd/kube-bind.io_apiserviceexports.yaml | 30 ++++++++----------- ...ube-bind.io_apiserviceexporttemplates.yaml | 25 ++++++++-------- .../v1alpha1/apiservicebinding_types.go | 30 +++++++++---------- .../v1alpha1/apiserviceexport_types.go | 4 --- .../v1alpha1/apiserviceexportrequest_types.go | 7 ++++- .../apiserviceexporttemplate_types.go | 2 +- .../v1alpha1/zz_generated.deepcopy.go | 22 ++++++++++---- 9 files changed, 87 insertions(+), 76 deletions(-) diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index a4c1b25fc..404c86890 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -126,17 +126,16 @@ spec: description: providerOverrides will make the provider override any object that might already exist in the consumer cluster if it has the same namespaced name as a resource created - by the provider. + by the provider, but is not the result of syncing. type: boolean recreateWhenConsumerSideDeleted: - description: only for owner provider When recreateWhenConsumerSideDeleted - is true the provider will recreate the object in case - the object is missing on the consumer side. Even if the - consumer mistakenly or intentionally deletes the objet, - the provider will recreate it. If the field is set as - false, the provider will not recreate the object in case - the object is deleted on the RecreateWhenConsumerSideDeleted - side. + description: recreateWhenConsumerSideDeleted set to true + means the provider will recreate the object in case the + object is missing on the consumer side. Even if the consumer + mistakenly or intentionally deletes the objet, the provider + will recreate it. If the field is set as false, the provider + will not recreate the object in case the object is deleted + on the RecreateWhenConsumerSideDeleted side. type: boolean type: object required: @@ -204,6 +203,7 @@ spec: required: - resource - state + - version type: object type: array required: diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 954b22909..5b295ef77 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -64,12 +64,17 @@ spec: pattern: ^(|[a-z0-9]([-a-z0-9]*[a-z0-9](\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)?)$ type: string permissionClaims: + description: permissionClaims records decisions about permission + claims requested by the API service provider. Individual claims + can be accepted or rejected. If accepted, the API service + provider gets the requested access to the specified resources + in this workspace. Access is granted per GroupResource, identity, + and other properties. items: - description: "PermissionClaim identifies an object by GR and - identity hash. Its purpose is to determine the added permissions - that a service provider may request and that a consumer - may accept and allow the service provider access to. \n - TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate + 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. \n TODO fix validation + +kubebuilder:validation:XValidation:rule=\"!(self.create.donate && self.adopt)\",message=\"donate and adopt are mutually exclusive\"" properties: @@ -108,12 +113,13 @@ spec: description: providerOverrides will make the provider override any object that might already exist in the consumer cluster if it has the same namespaced - name as a resource created by the provider. + name as a resource created by the provider, but + is not the result of syncing. type: boolean recreateWhenConsumerSideDeleted: - description: only for owner provider When recreateWhenConsumerSideDeleted - is true the provider will recreate the object in - case the object is missing on the consumer side. + description: recreateWhenConsumerSideDeleted set to + true means the provider will recreate the object + in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally deletes the objet, the provider will recreate it. If the field is set as false, the provider will @@ -183,6 +189,7 @@ spec: type: string required: - resource + - version type: object type: array resource: diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index 8d1f7a714..c17b58ee6 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -110,10 +110,9 @@ spec: type: object permissionClaims: items: - description: "PermissionClaim identifies an object by GR and identity - hash. Its purpose is to determine the added permissions that a - service provider may request and that a consumer may accept and - allow the service provider access to. \n TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate + 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. \n TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate && self.adopt)\",message=\"donate and adopt are mutually exclusive\"" properties: adopt: @@ -150,17 +149,16 @@ spec: description: providerOverrides will make the provider override any object that might already exist in the consumer cluster if it has the same namespaced name as a resource created - by the provider. + by the provider, but is not the result of syncing. type: boolean recreateWhenConsumerSideDeleted: - description: only for owner provider When recreateWhenConsumerSideDeleted - is true the provider will recreate the object in case - the object is missing on the consumer side. Even if the - consumer mistakenly or intentionally deletes the objet, - the provider will recreate it. If the field is set as - false, the provider will not recreate the object in case - the object is deleted on the RecreateWhenConsumerSideDeleted - side. + description: recreateWhenConsumerSideDeleted set to true + means the provider will recreate the object in case the + object is missing on the consumer side. Even if the consumer + mistakenly or intentionally deletes the objet, the provider + will recreate it. If the field is set as false, the provider + will not recreate the object in case the object is deleted + on the RecreateWhenConsumerSideDeleted side. type: boolean type: object required: @@ -222,13 +220,9 @@ spec: type: string required: - resource + - version type: object - maxItems: 2 type: array - x-kubernetes-list-map-keys: - - group - - resource - x-kubernetes-list-type: map scope: description: scope indicates whether the defined custom resource is cluster- or namespace-scoped. Allowed values are `Cluster` and `Namespaced`. diff --git a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml index 3ff10f7c3..c11a2da9d 100644 --- a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml @@ -60,10 +60,9 @@ spec: type: object permissionClaims: items: - description: "PermissionClaim identifies an object by GR and identity - hash. Its purpose is to determine the added permissions that a - service provider may request and that a consumer may accept and - allow the service provider access to. \n TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate + 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. \n TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate && self.adopt)\",message=\"donate and adopt are mutually exclusive\"" properties: adopt: @@ -100,17 +99,16 @@ spec: description: providerOverrides will make the provider override any object that might already exist in the consumer cluster if it has the same namespaced name as a resource created - by the provider. + by the provider, but is not the result of syncing. type: boolean recreateWhenConsumerSideDeleted: - description: only for owner provider When recreateWhenConsumerSideDeleted - is true the provider will recreate the object in case - the object is missing on the consumer side. Even if the - consumer mistakenly or intentionally deletes the objet, - the provider will recreate it. If the field is set as - false, the provider will not recreate the object in case - the object is deleted on the RecreateWhenConsumerSideDeleted - side. + description: recreateWhenConsumerSideDeleted set to true + means the provider will recreate the object in case the + object is missing on the consumer side. Even if the consumer + mistakenly or intentionally deletes the objet, the provider + will recreate it. If the field is set as false, the provider + will not recreate the object in case the object is deleted + on the RecreateWhenConsumerSideDeleted side. type: boolean type: object required: @@ -172,6 +170,7 @@ spec: type: string required: - resource + - version type: object type: array type: object diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index f7836c9c2..9384d2481 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -115,18 +115,18 @@ const ( ClaimRejected AcceptablePermissionClaimState = "Rejected" ) -// PermissionClaim identifies an object by GR and identity hash. -// Its purpose is to determine the added permissions that a service provider may +// 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. // // TODO fix validation +kubebuilder:validation:XValidation:rule="!(self.create.donate && self.adopt)",message="donate and adopt are mutually exclusive" type PermissionClaim struct { GroupResource `json:","` + // +kubebuilder:validation:Required Version string `json:"version"` // selector selects which resources are affected by this claim. - Selector ResourceSelector `json:"selector,omitempty"` + Selector ResourceSelector `json:"selector"` // required indicates whether the APIServiceBinding will work if this claim is not accepted. Required bool `json:"required"` @@ -140,29 +140,28 @@ type PermissionClaim struct { // // create determines whether the kube-bind konnector will sync matching objects from the // provider side down to the consumer cluster. - Create CreateOptions `json:"create"` + Create *CreateOptions `json:"create"` // adopt set to true means that objects created by the consumer are adopted by the provider. // i.e. the provider will become the owner. - Adopt bool `json:"adopt"` + Adopt bool `json:"adopt,omitempty"` // onConflict determines how the conflicts between objects on the consumer side // will be resolved. - OnConflict OnConflictOptions `json:"onConflict,omitempty"` + OnConflict *OnConflictOptions `json:"onConflict,omitempty"` // update lists a number of claimed permissions for the provider. // "field" and "preserving" are mutually exclusive. - Update UpdateOptions `json:"update"` + Update *UpdateOptions `json:"update,omitempty"` } type OnConflictOptions struct { // providerOverrides will make the provider override any object that might already exist // in the consumer cluster if it has the same namespaced name as a resource created by the - // provider. + // provider, but is not the result of syncing. ProviderOverwrites bool `json:"providerOverrides"` - // only for owner provider - // When recreateWhenConsumerSideDeleted is true the provider will recreate the object + // recreateWhenConsumerSideDeleted set to true means the provider will recreate the object // in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally // deletes the objet, the provider will recreate it. If the field is set as false, // the provider will not recreate the object in case the object is deleted on the RecreateWhenConsumerSideDeleted @@ -173,7 +172,7 @@ type OnConflictOptions struct { type CreateOptions struct { // donate set to true means that a newly created object by the provider is immediately owned by hte consumer. // If false, the object stays in ownership of the provider - Donate bool `json:"donate"` + Donate bool `json:"donate,omitempty"` } type UpdateOptions struct { @@ -185,7 +184,7 @@ type UpdateOptions struct { // Preservings are the fields that are preserved by the konnector during synchronization. // The owner is not able to set those fields. If the owner changes the value of these fields, // their change will be overwritten. - Preservings []string `json:"preservings,omitempty"` + Preserving []string `json:"preservings,omitempty"` // alwaysRecreate, when true will make the konnector delete the old object and create a new one // instead of updating. Useful for immutable objects. @@ -205,15 +204,14 @@ type ResourceSelector struct { // +kubebuilder:validation:Enum=Provider;Consumer Owner Owner `json:"owner"` - - // - // WARNING: If adding new fields, add them to the XValidation check! - // } type Owner string +// Provider means that the owner of the resource is the Provider. const Provider Owner = "Provider" + +// Consumer means that the owner of the resource is the Consumer. const Consumer Owner = "Consumer" type APIServiceBindingStatus struct { diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexport_types.go b/pkg/apis/kubebind/v1alpha1/apiserviceexport_types.go index ffa0a1e7b..74d226191 100644 --- a/pkg/apis/kubebind/v1alpha1/apiserviceexport_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiserviceexport_types.go @@ -80,10 +80,6 @@ type APIServiceExportSpec struct { APIServiceExportCRDSpec `json:",inline"` // +optional - // +listType=map - // +listMapKey=group - // +listMapKey=resource - // +kubebuilder:validation:MaxItems=2 PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` // informerScope is the scope of the APIServiceExport. It can be either Cluster or Namespace. diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go b/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go index 5200eb8e7..3b2d9df85 100644 --- a/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go @@ -109,7 +109,12 @@ type APIServiceExportRequestResource struct { // versions is a list of versions that should be exported. If this is empty // a sensible default is chosen by the service provider. - Versions []string `json:"versions,omitempty"` + Versions []string `json:"versions,omitempty"` + + // permissionClaims records decisions about permission claims requested by the API service provider. + // Individual claims can be accepted or rejected. If accepted, the API service provider gets the + // requested access to the specified resources in this workspace. Access is granted per + // GroupResource, identity, and other properties. PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` } diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go b/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go index 836d4b1d7..db0e00c73 100644 --- a/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The Kube Bind Authors. +Copyright 2023 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. diff --git a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go index 4cada002d..ddb90ccf7 100644 --- a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go @@ -1070,9 +1070,21 @@ func (in *PermissionClaim) DeepCopyInto(out *PermissionClaim) { *out = *in out.GroupResource = in.GroupResource out.Selector = in.Selector - out.Create = in.Create - out.OnConflict = in.OnConflict - in.Update.DeepCopyInto(&out.Update) + if in.Create != nil { + in, out := &in.Create, &out.Create + *out = new(CreateOptions) + **out = **in + } + if in.OnConflict != nil { + in, out := &in.OnConflict, &out.OnConflict + *out = new(OnConflictOptions) + **out = **in + } + if in.Update != nil { + in, out := &in.Update, &out.Update + *out = new(UpdateOptions) + (*in).DeepCopyInto(*out) + } return } @@ -1110,8 +1122,8 @@ func (in *UpdateOptions) DeepCopyInto(out *UpdateOptions) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.Preservings != nil { - in, out := &in.Preservings, &out.Preservings + if in.Preserving != nil { + in, out := &in.Preserving, &out.Preserving *out = make([]string, len(*in)) copy(*out, *in) } From ebe304aabec34f0549b8c0510c02d0a359e46770 Mon Sep 17 00:00:00 2001 From: a9s-niklas-kempf <103116383+a9s-niklas-kempf@users.noreply.github.com> Date: Tue, 6 Jun 2023 11:12:13 +0200 Subject: [PATCH 10/26] Add readable prompts for permission claims (#1) Add human readable prompts for permission claims to cli plugin Add unit tests for permission claim prompts --- .../crd/kube-bind.io_apiservicebindings.yaml | 2 +- ...kube-bind.io_apiserviceexportrequests.yaml | 2 +- .../crd/kube-bind.io_apiserviceexports.yaml | 2 +- ...ube-bind.io_apiserviceexporttemplates.yaml | 2 +- .../v1alpha1/apiservicebinding_types.go | 2 +- .../bind-apiservice/plugin/servicebindings.go | 151 +++- .../plugin/servicebindings_test.go | 807 ++++++++++++++++++ 7 files changed, 960 insertions(+), 8 deletions(-) create mode 100644 pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index 404c86890..e7c859839 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -104,7 +104,7 @@ spec: properties: donate: description: donate set to true means that a newly created - object by the provider is immediately owned by hte consumer. + object by the provider is immediately owned by the consumer. If false, the object stays in ownership of the provider type: boolean type: object diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 5b295ef77..602bf8cfb 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -91,7 +91,7 @@ spec: donate: description: donate set to true means that a newly created object by the provider is immediately owned - by hte consumer. If false, the object stays in ownership + by the consumer. If false, the object stays in ownership of the provider type: boolean type: object diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index c17b58ee6..dc0680d50 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -127,7 +127,7 @@ spec: properties: donate: description: donate set to true means that a newly created - object by the provider is immediately owned by hte consumer. + object by the provider is immediately owned by the consumer. If false, the object stays in ownership of the provider type: boolean type: object diff --git a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml index c11a2da9d..2a15d271d 100644 --- a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml @@ -77,7 +77,7 @@ spec: properties: donate: description: donate set to true means that a newly created - object by the provider is immediately owned by hte consumer. + object by the provider is immediately owned by the consumer. If false, the object stays in ownership of the provider type: boolean type: object diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 9384d2481..21e048931 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -170,7 +170,7 @@ type OnConflictOptions struct { } type CreateOptions struct { - // donate set to true means that a newly created object by the provider is immediately owned by hte consumer. + // donate set to true means that a newly created object by the provider is immediately owned by the consumer. // If false, the object stays in ownership of the provider Donate bool `json:"donate,omitempty"` } diff --git a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go index 45e2411f2..6a84429a4 100644 --- a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go +++ b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The Kube Bind Authors. +Copyright 2023 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. @@ -18,8 +18,10 @@ package plugin import ( "bufio" + "bytes" "context" "fmt" + "io" "strings" "time" @@ -139,13 +141,156 @@ func (b *BindAPIServiceOptions) createAPIServiceBindings(ctx context.Context, co return bindings, nil } +func printPermissionClaim(w io.Writer, p kubebindv1alpha1.PermissionClaim) error { + var b bytes.Buffer + + var groupResource string + if p.GroupResource.Group != "" { + groupResource = fmt.Sprintf("%s objects (apiVersion: \"%s/%s\")", p.GroupResource.Resource, p.GroupResource.Group, p.Version) + } else { + groupResource = fmt.Sprintf("%s objects (apiVersion: \"%s\")", p.GroupResource.Resource, p.Version) + } + + if err := writeFirstLines(&b, groupResource, p); err != nil { + return err + } + + if err := writeOnConflict(&b, p); err != nil { + return err + } + + if err := writeUpdateClause(&b, p); err != nil { + return err + } + + if err := writeRequiredAndAcceptance(&b, p.Required); err != nil { + return err + } + + _, err := fmt.Fprint(w, b.String()) + return err +} + +func writeFirstLines(b *bytes.Buffer, groupResource string, claim kubebindv1alpha1.PermissionClaim) error { + var err error + + donate := false + if claim.Create != nil { + donate = claim.Create.Donate + } + adopt := claim.Adopt + + name := "" + var owner kubebindv1alpha1.Owner + if (claim.Selector != kubebindv1alpha1.ResourceSelector{}) { + name = claim.Selector.Name + owner = claim.Selector.Owner + } + + switch { + case !donate && !adopt: + groupResource = "read " + groupResource + case donate && !adopt: + groupResource = "create user owned " + groupResource + case !donate && adopt: + groupResource = "have ownership of " + groupResource + } + + _, err = fmt.Fprintf(b, "The provider wants to %s on your cluster.", groupResource) + + if owner == kubebindv1alpha1.Consumer { + owner = "you" + } + if owner == kubebindv1alpha1.Provider { + owner = "the provider" + } + switch { + case owner == "" && name == "": + _, err = fmt.Fprintf(b, "\n") + case owner != "" && name == "": + _, err = fmt.Fprintf(b, " This only applies to objects which are owned by %s.\n", owner) + case owner == "" && name != "": + _, err = fmt.Fprintf(b, " This only applies to objects which are referenced with:\n\tname: \"%s\"\n", name) + case owner != "" && name != "": + _, err = fmt.Fprintf(b, " This only applies to objects which are owned by %s and to objects which are referenced with:\n name: \"%s\"\n", owner, name) + } + + return err + +} + +func writeOnConflict(b *bytes.Buffer, claim kubebindv1alpha1.PermissionClaim) error { + var err error + + if claim.OnConflict != nil { + switch { + case claim.OnConflict.ProviderOverwrites && claim.OnConflict.RecreateWhenConsumerSideDeleted: + _, err = b.WriteString("Conflicting objects will be overwritten and created objects will be recreated upon deletion.\n") + case claim.OnConflict.ProviderOverwrites: + _, err = b.WriteString("Conflicting objects will be overwritten and created objects will not be recreated upon deletion.\n") + case claim.OnConflict.RecreateWhenConsumerSideDeleted: + _, err = b.WriteString("Conflicting objects will not be overwritten and created objects will be recreated upon deletion.\n") + default: //Do nothing + } + } + return err +} + +func writeUpdateClause(b *bytes.Buffer, claim kubebindv1alpha1.PermissionClaim) error { + var err error + + if claim.Update == nil { + return nil + } + + if claim.Update.Fields != nil { + owner := "the provider" + if claim.Create != nil && claim.Create.Donate { + owner = "the user" + } + _, err = fmt.Fprintf(b, "The following fields of the objects will still be able to be changed by %s:\n", owner) + } + if claim.Update.Preserving != nil { + _, err = b.WriteString("The following fields of the objects will be overwritten with their initial values, if they are modified:\n") + } + + for _, s := range append(claim.Update.Fields, claim.Update.Preserving...) { + _, err = fmt.Fprintf(b, "\t\"%s\"\n", s) + } + + if claim.Update.AlwaysRecreate { + _, err = b.WriteString("Modification of said objects will by handled by deletion and recreation of said objects.\n") + } + + return err +} + +func writeRequiredAndAcceptance(b *bytes.Buffer, required bool) error { + var err error + + if required { + _, err = fmt.Fprint(b, "Accepting this Permission is required in order to proceed.\n") + } + if !required { + _, err = fmt.Fprint(b, "Accepting this Permission is optional.\n") + } + if err != nil { + return nil + } + + _, err = fmt.Fprint(b, "Do you accept this Permission? [No,Yes]\n") + + return err +} + func (opt BindAPIServiceOptions) promptYesNo(p kubebindv1alpha1.PermissionClaim) (bool, error) { - fmt.Printf("%+v", opt.Options.IOStreams) reader := bufio.NewReader(opt.Options.IOStreams.In) for { - fmt.Fprintf(opt.Options.IOStreams.Out, "binding wants permission\n%+v\n[Y/N]", p) + if err := printPermissionClaim(opt.Options.Out, p); err != nil { + return false, err + } response, err := reader.ReadString('\n') if err != nil { diff --git a/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go b/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go new file mode 100644 index 000000000..568e875d2 --- /dev/null +++ b/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go @@ -0,0 +1,807 @@ +/* +Copyright 2023 The Kube Bind Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugin + +import ( + "bytes" + "os" + "testing" + + "k8s.io/cli-runtime/pkg/genericclioptions" + + kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" +) + +func TestHumanReadablePromt(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + testData kubebindv1alpha1.PermissionClaim + expectedOutput string + }{ + {"Owner=Provider", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,Required=false", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: false, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is optional.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,Selector.Name", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Name: "bar", + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider and to objects which are referenced with:\n" + + "\tname: \"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,GroupResource.Group", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "example.com", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,Selector.Name,GroupResource.Group", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "example.com", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Name: "bar", + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster. This only applies to objects which are owned by the provider and to objects which are referenced with:\n" + + "\tname: \"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,CreateOptions={}", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Create: &kubebindv1alpha1.CreateOptions{}, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,CreateOptions=false", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Create: &kubebindv1alpha1.CreateOptions{ + Donate: false, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,CreateOption.Donate=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Create: &kubebindv1alpha1.CreateOptions{ + Donate: true, + }, + }, + "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,OnConflict={}", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + OnConflict: &kubebindv1alpha1.OnConflictOptions{}, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,OnConflict.ProviderOverwrites=false", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + OnConflict: &kubebindv1alpha1.OnConflictOptions{ + ProviderOverwrites: false, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,OnConflict.ProviderOverwrites=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + OnConflict: &kubebindv1alpha1.OnConflictOptions{ + ProviderOverwrites: true, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Conflicting objects will be overwritten and created objects will not be recreated upon deletion.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,OnConflict.RecreateWhenConsumerSideDeleted=false", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + OnConflict: &kubebindv1alpha1.OnConflictOptions{ + RecreateWhenConsumerSideDeleted: false, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,OnConflict.RecreateWhenConsumerSideDeleted=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + OnConflict: &kubebindv1alpha1.OnConflictOptions{ + RecreateWhenConsumerSideDeleted: true, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Conflicting objects will not be overwritten and created objects will be recreated upon deletion.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,UpdateOptions={}", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Update: &kubebindv1alpha1.UpdateOptions{}, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,UpdateOptions.Fields", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Update: &kubebindv1alpha1.UpdateOptions{ + Fields: []string{"foo", "bar"}, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The following fields of the objects will still be able to be changed by the provider:\n" + + "\t\"foo\"\n" + + "\t\"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,UpdateOptions.Preserving", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Update: &kubebindv1alpha1.UpdateOptions{ + Preserving: []string{"foo", "bar"}, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The following fields of the objects will be overwritten with their initial values, if they are modified:\n" + " \"foo\"\n" + + "\t\"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,UpdateOptions.AlwaysRecreate=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Update: &kubebindv1alpha1.UpdateOptions{ + AlwaysRecreate: true, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "Modification of said objects will by handled by deletion and recreation of said objects.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,UpdateOptions.Fields,CreateOptions.Donate=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Create: &kubebindv1alpha1.CreateOptions{ + Donate: true, + }, + Update: &kubebindv1alpha1.UpdateOptions{ + Fields: []string{"foo", "bar"}, + }, + }, + "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The following fields of the objects will still be able to be changed by the user:\n" + + "\t\"foo\"\n" + + "\t\"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Provider,UpdateOptions.Preserving,CreateOptions.Donate=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + Create: &kubebindv1alpha1.CreateOptions{ + Donate: true, + }, + Update: &kubebindv1alpha1.UpdateOptions{ + Preserving: []string{"foo", "bar"}, + }, + }, + "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The following fields of the objects will be overwritten with their initial values, if they are modified:\n" + " \"foo\"\n" + + "\t\"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,Selector.Name", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Name: "bar", + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you and to objects which are referenced with:\n" + + "\tname: \"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,GroupResource.Group", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "example.com", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,Selector.Name,GroupResource.Group", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "example.com", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Name: "bar", + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster. This only applies to objects which are owned by you and to objects which are referenced with:\n" + + "\tname: \"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,Adopt=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Adopt: true, + Required: true, + }, + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,Selector.Name,Adopt=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Name: "bar", + Owner: kubebindv1alpha1.Consumer, + }, + Adopt: true, + Required: true, + }, + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you and to objects which are referenced with:\n" + + "\tname: \"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,OnConflict={}", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + OnConflict: &kubebindv1alpha1.OnConflictOptions{}, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,OnConflict.ProviderOverwrites=false", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + OnConflict: &kubebindv1alpha1.OnConflictOptions{ + ProviderOverwrites: false, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,OnConflict.ProviderOverwrites=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + OnConflict: &kubebindv1alpha1.OnConflictOptions{ + ProviderOverwrites: true, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "Conflicting objects will be overwritten and created objects will not be recreated upon deletion.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,UpdateOptions={}", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + Update: &kubebindv1alpha1.UpdateOptions{}, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,UpdateOptions.Fields", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + Update: &kubebindv1alpha1.UpdateOptions{ + Fields: []string{"foo", "bar"}, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The following fields of the objects will still be able to be changed by the provider:\n" + + "\t\"foo\"\n" + + "\t\"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,UpdateOptions.Preserving", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + Update: &kubebindv1alpha1.UpdateOptions{ + Preserving: []string{"foo", "bar"}, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The following fields of the objects will be overwritten with their initial values, if they are modified:\n" + " \"foo\"\n" + + "\t\"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,UpdateOptions.AlwaysRecreate=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + Update: &kubebindv1alpha1.UpdateOptions{ + AlwaysRecreate: true, + }, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "Modification of said objects will by handled by deletion and recreation of said objects.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,UpdateOptions.Fields,Adopt=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + Adopt: true, + Update: &kubebindv1alpha1.UpdateOptions{ + Fields: []string{"foo", "bar"}, + }, + }, + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The following fields of the objects will still be able to be changed by the provider:\n" + + "\t\"foo\"\n" + + "\t\"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Owner=Consumer,UpdateOptions.Preserving,Adopt=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Owner: kubebindv1alpha1.Consumer, + }, + Required: true, + Adopt: true, + Update: &kubebindv1alpha1.UpdateOptions{ + Preserving: []string{"foo", "bar"}, + }, + }, + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The following fields of the objects will be overwritten with their initial values, if they are modified:\n" + " \"foo\"\n" + + "\t\"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Selector={}", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{}, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Selector.Owner=\"\",Selector.Name", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{ + Name: "bar", + }, + Required: true, + }, + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are referenced with:\n" + + "\tname: \"bar\"\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Selector={},donate=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{}, + Required: true, + Create: &kubebindv1alpha1.CreateOptions{ + Donate: true, + }, + }, + "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Selector={},donate=true,update.Fields=[\"spec\"]", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{}, + Create: &kubebindv1alpha1.CreateOptions{ + Donate: true, + }, + Update: &kubebindv1alpha1.UpdateOptions{ + Fields: []string{"spec"}, + }, + }, + "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The following fields of the objects will still be able to be changed by the user:\n" + + "\t\"spec\"\n" + + "Accepting this Permission is optional.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Selector={},adopt=true", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{}, + Required: true, + Adopt: true, + }, + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + {"Selector={},adopt=true,update.Fields=[\"spec\"]", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: kubebindv1alpha1.ResourceSelector{}, + Adopt: true, + Update: &kubebindv1alpha1.UpdateOptions{ + Fields: []string{"spec"}, + }, + }, + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The following fields of the objects will still be able to be changed by the provider:\n" + + "\t\"spec\"\n" + + "Accepting this Permission is optional.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var output bytes.Buffer + var input bytes.Buffer + input.WriteString("y\n") + opts := NewBindAPIServiceOptions(genericclioptions.IOStreams{In: &input, Out: &output, ErrOut: os.Stderr}) + b, err := opts.promptYesNo(tt.testData) + if output.String() != tt.expectedOutput { + t.Errorf("Expected IO Output did not match. got: \"\n%s\"\nwanted: \"\n%s\"\n", output.String(), tt.expectedOutput) + } + if b == false || (err != nil) { + t.Errorf("Expected Return value did not match. got: \"%v\", \"%v\"", b, err) + } + }) + } +} From cf6c74cc04263da4c457cd509e9bb73f79d43126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 7 Jun 2023 14:09:13 +0200 Subject: [PATCH 11/26] Add synchronization from consumer to provider --- .../claimedresources_controller.go | 43 +- .../claimedresources_reconciler.go | 201 +++++-- .../claimedresources_reconciler_test.go | 205 ++++++- .../serviceexport/serviceexport_reconcile.go | 6 +- test/e2e/bind/fixtures/consumer/bootstrap.go | 42 ++ .../bind/fixtures/consumer/crd-mangodb.yaml | 58 ++ .../consumer/exporttemplate-mangodb.yaml | 15 + test/e2e/konnector/claimedresources_test.go | 527 ++++++++++++++++++ 8 files changed, 1022 insertions(+), 75 deletions(-) create mode 100644 test/e2e/bind/fixtures/consumer/bootstrap.go create mode 100644 test/e2e/bind/fixtures/consumer/crd-mangodb.yaml create mode 100644 test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml create mode 100644 test/e2e/konnector/claimedresources_test.go diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go index 3f35d5c6d..a10d7738a 100644 --- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go @@ -49,6 +49,7 @@ const ( // NewController returns a new controller reconciling downstream objects to upstream. func NewController( gvr schema.GroupVersionResource, + claim kubebindv1alpha1.PermissionClaim, providerNamespace string, consumerConfig, providerConfig *rest.Config, consumerDynamicInformer informers.GenericInformer, @@ -75,6 +76,8 @@ func NewController( c := &controller{ queue: queue, + claim: claim, + consumerClient: consumerClient, providerClient: providerClient, @@ -109,6 +112,17 @@ func NewController( } return obj.(*unstructured.Unstructured), nil }, + createProviderObject: func(ctx context.Context, obj *unstructured.Unstructured) error { + _, err := providerClient.Resource(gvr).Namespace(obj.GetNamespace()).Create(ctx, obj, metav1.CreateOptions{}) + return err + }, + updateProviderObject: func(ctx context.Context, obj *unstructured.Unstructured) error { + _, err := providerClient.Resource(gvr).Namespace(obj.GetNamespace()).Update(ctx, obj, metav1.UpdateOptions{}) + return err + }, + deleteProviderObject: func(ctx context.Context, ns, name string) error { + return providerClient.Resource(gvr).Namespace(ns).Delete(ctx, name, metav1.DeleteOptions{}) + }, deleteConsumerObject: func(ctx context.Context, ns, name string) error { return consumerClient.Resource(gvr).Namespace(ns).Delete(ctx, name, metav1.DeleteOptions{}) }, @@ -118,14 +132,6 @@ func NewController( createConsumerObject: func(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { return consumerClient.Resource(gvr).Namespace(obj.GetNamespace()).Create(ctx, obj, metav1.CreateOptions{}) }, - // requeue: func(obj *unstructured.Unstructured, after time.Duration) error { - // key, err := cache.MetaNamespaceKeyFunc(obj) - // if err != nil { - // return err - // } - // queue.AddAfter(key, after) - // return nil - // }, }, } @@ -160,6 +166,8 @@ func NewController( type controller struct { queue workqueue.RateLimitingInterface + claim kubebindv1alpha1.PermissionClaim + consumerClient dynamicclient.Interface providerClient dynamicclient.Interface @@ -175,18 +183,25 @@ type controller struct { readReconciler } -func isClaimed(obj *unstructured.Unstructured) bool { - for k, v := range obj.GetAnnotations() { - if k == annotation && v == "true" { - return true +func (c *controller) isClaimed(obj *unstructured.Unstructured) bool { + if c.claim.Selector.Owner != "" { + for k, v := range obj.GetAnnotations() { + if k == annotation { + return kubebindv1alpha1.Owner(v) == c.claim.Selector.Owner + } } } - return false + + if c.claim.Selector.Name != "" && obj.GetName() != c.claim.Selector.Name { + return false + } + + return true } func (c *controller) enqueueConsumer(logger klog.Logger, obj interface{}) { o := obj.(*unstructured.Unstructured) - if !isClaimed(o) { + if !c.isClaimed(o) { return } diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go index 522c445fc..3b9979b57 100644 --- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The Kube Bind Authors. +Copyright 2023 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. @@ -20,6 +20,7 @@ import ( "context" "reflect" + "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/runtime" @@ -28,11 +29,14 @@ import ( kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" ) -const annotation = "kube-bind.io/claimedresource" +const annotation = "kube-bind.io/resource-owner" type readReconciler struct { - getServiceNamespace func(upstreamNamespace string) (*kubebindv1alpha1.APIServiceNamespace, error) - getProviderObject func(ns, name string) (*unstructured.Unstructured, error) + getServiceNamespace func(upstreamNamespace string) (*kubebindv1alpha1.APIServiceNamespace, error) + getProviderObject func(ns, name string) (*unstructured.Unstructured, error) + createProviderObject func(ctx context.Context, obj *unstructured.Unstructured) error + updateProviderObject func(ctx context.Context, obj *unstructured.Unstructured) error + deleteProviderObject func(ctx context.Context, ns, name string) error getConsumerObject func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) updateConsumerObject func(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) @@ -41,14 +45,14 @@ type readReconciler struct { } // reconcile syncs upstream claimed resources to downstream. -func (r *readReconciler) reconcile(ctx context.Context, upstreamNS, name string) error { +func (r *readReconciler) reconcile(ctx context.Context, providerNS, name string) error { logger := klog.FromContext(ctx) - logger = logger.WithValues("name", name, "upstreamNamespace", upstreamNS) + logger = logger.WithValues("name", name, "providerNamespace", providerNS) logger.Info("reconciling object") - downstreamNS := "" - if upstreamNS != "" { - sn, err := r.getServiceNamespace(upstreamNS) + consumerNS := "" + if providerNS != "" { + sn, err := r.getServiceNamespace(providerNS) if err != nil && !errors.IsNotFound(err) { return err } else if errors.IsNotFound(err) { @@ -60,31 +64,135 @@ func (r *readReconciler) reconcile(ctx context.Context, upstreamNS, name string) return err // hoping the status is set soon. } - logger = logger.WithValues("upstreamNamespace", sn.Status.Namespace) + logger = logger.WithValues("providerNamespace", sn.Status.Namespace) + consumerNS = sn.Name + logger = logger.WithValues("consumerNamespace", consumerNS) + ctx = klog.NewContext(ctx, logger) + } + + providerObj, providerErr := r.getProviderObject(providerNS, name) + if providerErr != nil && !errors.IsNotFound(providerErr) { + return providerErr + } + consumerObj, consumerErr := r.getConsumerObject(ctx, consumerNS, name) + if consumerErr != nil && !errors.IsNotFound(consumerErr) { + return consumerErr + } - // continue with downstream namespace - downstreamNS = sn.Name - logger = logger.WithValues("downstreamNamespace", downstreamNS) + if errors.IsNotFound(providerErr) && errors.IsNotFound(consumerErr) { + // Nothing to do + return nil } - obj, err := r.getProviderObject(upstreamNS, name) - if errors.IsNotFound(err) { - err := r.deleteConsumerObject(ctx, downstreamNS, name) - if errors.IsNotFound(err) { + // Determine owner + owner := determineOwner(providerObj, consumerObj) + logger = logger.WithValues("owner", owner) + + switch owner { + case kubebindv1alpha1.Provider: + if errors.IsNotFound(providerErr) { + err := r.deleteConsumerObject(ctx, consumerNS, name) + if errors.IsNotFound(err) { + return nil + } + return err + } + ownerCandidate := providerObj.DeepCopy() + + // Set owner annotation if needed + r.makeProviderOwner(ctx, ownerCandidate) + if !equality.Semantic.DeepEqual(providerObj, ownerCandidate) { + if err := r.updateProviderObject(ctx, ownerCandidate); err != nil { + return err + } + } + + if errors.IsNotFound(consumerErr) { + logger.Info("Creating missing downstream object", "downstreamNamespace", providerNS, "downstreamName", providerObj.GetName()) + + candidate := candidateFromOwnerObj(consumerNS, providerObj) + r.makeProviderOwner(ctx, candidate) + + if _, err := r.createConsumerObject(ctx, candidate); err != nil { + return err + } + return nil } - return err - } else if err != nil { - return err - } - if obj.GetDeletionTimestamp() != nil && !obj.GetDeletionTimestamp().IsZero() { - logger.Info("Deleting downstream object because it has been deleted upstream", "downStreamNamespace", upstreamNS, "downstreamName", obj.GetName()) - if err := r.deleteConsumerObject(ctx, upstreamNS, obj.GetName()); err != nil { + if providerObj.GetDeletionTimestamp() != nil && !providerObj.GetDeletionTimestamp().IsZero() { + logger.Info("Deleting downstream object because it has been deleted upstream", "downStreamNamespace", providerNS, "downstreamName", providerObj.GetName()) + if err := r.deleteConsumerObject(ctx, providerNS, providerObj.GetName()); err != nil { + return err + } + } + + candidate := candidateFromOwnerObj(consumerNS, providerObj) + if !reflect.DeepEqual(candidate, consumerObj) { + logger.Info("Updating downstream object data", "downstreamNamespace", consumerNS, "downstreamName", consumerObj.GetName()) + if _, err := r.updateConsumerObject(ctx, candidate); err != nil { + logger.Error(err, "error updating consumer object") + return err + } + } + + case kubebindv1alpha1.Consumer: + if errors.IsNotFound(consumerErr) { + logger.Info("Owner copy of the object is gone, deleting downstream object", "name", name, "namespace", providerNS) + err := r.deleteProviderObject(ctx, providerNS, name) + if errors.IsNotFound(err) { + return nil + } return err } + + ownerCandidate := consumerObj.DeepCopy() + r.makeConsumerOwner(ownerCandidate) + if !equality.Semantic.DeepEqual(consumerObj, ownerCandidate) { + logger.Info("setting owner annotation for Consumer object") + if _, err := r.updateConsumerObject(ctx, ownerCandidate); err != nil { + return err + } + } + + candidate := candidateFromOwnerObj(providerNS, ownerCandidate) + r.makeConsumerOwner(candidate) + + if errors.IsNotFound(providerErr) { + logger.Info("creating consumer owned object at provider") + return r.createProviderObject(ctx, candidate) + } + + if !equality.Semantic.DeepEqual(providerObj, candidate) { + logger.Info("updating consumer owned object at provider") + return r.updateProviderObject(ctx, candidate) + } + } + + return nil +} + +func (r readReconciler) makeConsumerOwner(obj *unstructured.Unstructured) { + a := obj.GetAnnotations() + if a == nil { + a = map[string]string{} + } + a[annotation] = string(kubebindv1alpha1.Consumer) + obj.SetAnnotations(a) +} + +func (r readReconciler) makeProviderOwner(ctx context.Context, obj *unstructured.Unstructured) { + + a := obj.GetAnnotations() + if a == nil { + a = map[string]string{} } + a[annotation] = string(kubebindv1alpha1.Provider) + obj.SetAnnotations(a) +} + +func candidateFromOwnerObj(downstreamNS string, obj *unstructured.Unstructured) *unstructured.Unstructured { // clean up object candidate := obj.DeepCopy() candidate.SetUID("") @@ -95,31 +203,38 @@ func (r *readReconciler) reconcile(ctx context.Context, upstreamNS, name string) candidate.SetDeletionGracePeriodSeconds(nil) candidate.SetOwnerReferences(nil) candidate.SetFinalizers(nil) - candidate.SetAnnotations(map[string]string{ - annotation: "true"}, - ) candidate.SetNamespace(downstreamNS) - downstream, err := r.getConsumerObject(ctx, downstreamNS, name) + return candidate +} - if err != nil && !errors.IsNotFound(err) { - logger.Info("failed to get downstream object", "error", err, "downstreamNamespace", upstreamNS, "downstreamName", obj.GetName()) - return err - } else if errors.IsNotFound(err) { - logger.Info("Creating missing downstream object", "downstreamNamespace", upstreamNS, "downstreamName", obj.GetName()) - if _, err := r.createConsumerObject(ctx, candidate); err != nil { - return err +// determineOwner determines the owner of a resource given at least one object exists either on the +// consumer or provider side +func determineOwner(providerObj, consumerObj *unstructured.Unstructured) kubebindv1alpha1.Owner { + if providerObj != nil { + ownerAnn := providerObj.GetAnnotations()[annotation] + switch ownerAnn { + case "Provider": + return kubebindv1alpha1.Provider + case "Consumer": + return kubebindv1alpha1.Consumer + } + if ownerAnn == "" && consumerObj == nil { + return kubebindv1alpha1.Provider } - - return nil } - if !reflect.DeepEqual(candidate, downstream) { - logger.Info("Updating downstream object data", "downstreamNamespace", upstreamNS, "downstreamName", downstream.GetName()) - if _, err := r.updateConsumerObject(ctx, candidate); err != nil { - return err + if consumerObj != nil { + ownerAnn := consumerObj.GetAnnotations()[annotation] + switch ownerAnn { + case "Provider": + return kubebindv1alpha1.Provider + case "Consumer": + return kubebindv1alpha1.Consumer + } + if ownerAnn == "" && providerObj == nil { + return kubebindv1alpha1.Consumer } } - - return nil + panic("should not happen") } diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go index be42a6c01..6067d3792 100644 --- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler_test.go @@ -32,6 +32,8 @@ func TestDownstreamCreation(t *testing.T) { t.Parallel() var createdObj *unstructured.Unstructured + var providerObj *unstructured.Unstructured + r := readReconciler{ getServiceNamespace: defaultNamespace, getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { @@ -49,6 +51,58 @@ func TestDownstreamCreation(t *testing.T) { createdObj = ob return ob, nil }, + updateProviderObject: func(ctx context.Context, obj *unstructured.Unstructured) error { + providerObj = obj + return nil + }, + } + + err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") + if err != nil { + t.Fatal(err) + } + + if createdObj == nil { + t.Error("reconcile did not create an object", createdObj) + } + + if v, ok := createdObj.GetAnnotations()["kube-bind.io/resource-owner"]; !ok || v != "Provider" { + t.Error("created object did not have 'kube-bind.io/resource-owner: Provider' annotation", createdObj) + } + if v, ok := providerObj.GetAnnotations()["kube-bind.io/resource-owner"]; !ok || v != "Provider" { + t.Error("pre-existing object was not updated to be the owner") + } +} + +func TestUpstreamCreation(t *testing.T) { + t.Parallel() + + var createdObj *unstructured.Unstructured + var providerObj *unstructured.Unstructured + + r := readReconciler{ + getServiceNamespace: defaultNamespace, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + return notFound(context.TODO(), ns, name) + }, + getConsumerObject: func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "dummy", + "namespace": "cluster-x-default", + }, + }, + }, nil + }, + createProviderObject: func(ctx context.Context, ob *unstructured.Unstructured) error { + createdObj = ob + return nil + }, + updateConsumerObject: func(ctx context.Context, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + providerObj = obj + return obj, nil + }, } err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") @@ -60,8 +114,11 @@ func TestDownstreamCreation(t *testing.T) { t.Error("reconcile did not create an object", createdObj) } - if v, ok := createdObj.GetAnnotations()["kube-bind.io/claimedresource"]; !ok || v != "true" { - t.Error("created object did not have 'kube-bind.io/claimedresource: true' annotation") + if v, ok := createdObj.GetAnnotations()["kube-bind.io/resource-owner"]; !ok || v != "Consumer" { + t.Error("created object did not have 'kube-bind.io/resource-owner: Consumer' annotation", createdObj) + } + if v, ok := providerObj.GetAnnotations()["kube-bind.io/resource-owner"]; !ok || v != "Consumer" { + t.Error("pre-existing object was not updated to be the owner") } } @@ -81,7 +138,46 @@ func defaultNamespace(upstreamNamespace string) (*v1alpha1.APIServiceNamespace, func notFound(_ context.Context, ns, name string) (*unstructured.Unstructured, error) { return nil, errors.NewNotFound(v1.Resource("Secret"), name) } +func TestUpstreamDeletion(t *testing.T) { + t.Parallel() + var deleteNsn struct { + ns, name string + } + r := readReconciler{ + getServiceNamespace: defaultNamespace, + getConsumerObject: notFound, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "kube-bind.io/resource-owner": "Consumer", + }, + "name": "dummy", + "namespace": "default", + }, + }, + }, nil + }, + deleteProviderObject: func(ctx context.Context, ns, name string) error { + deleteNsn = struct { + ns string + name string + }{ns: ns, name: name} + return nil + }, + } + + err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") + if err != nil { + t.Fatal(err) + } + + if deleteNsn.name != "dummy" || deleteNsn.ns != "cluster-x-default" { + t.Error("reconcile deleted the wrong object", deleteNsn) + } +} func TestDownstreamDeletion(t *testing.T) { t.Parallel() @@ -93,7 +189,19 @@ func TestDownstreamDeletion(t *testing.T) { getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { return nil, errors.NewNotFound(v1.Resource("Secret"), name) }, - getConsumerObject: notFound, + getConsumerObject: func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "kube-bind.io/resource-owner": "Provider", + }, + "name": "dummy", + "namespace": "cluster-x-default", + }, + }, + }, nil + }, deleteConsumerObject: func(ctx context.Context, ns, name string) error { deleteNsn = struct { ns string @@ -124,14 +232,25 @@ func TestDownstreamDeletionAlreadyGone(t *testing.T) { getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { return nil, errors.NewNotFound(v1.Resource("Secret"), name) }, - getConsumerObject: notFound, + getConsumerObject: func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "kube-bind.io/resource-owner": "Provider", + }, + "name": "dummy", + "namespace": "cluster-x-default", + }, + }, + }, nil + }, deleteConsumerObject: func(ctx context.Context, ns, name string) error { deleteNsn = struct { ns string name string }{ns: ns, name: name} return errors.NewNotFound(v1.Resource("Secret"), name) - }, } @@ -145,6 +264,65 @@ func TestDownstreamDeletionAlreadyGone(t *testing.T) { } } +func TestDownstreamUpdate(t *testing.T) { + t.Parallel() + + var updateObj *unstructured.Unstructured + r := readReconciler{ + getServiceNamespace: defaultNamespace, + getConsumerObject: func(ctx context.Context, ns, name string) (*unstructured.Unstructured, error) { + obj := &unstructured.Unstructured{} + obj.SetUnstructuredContent( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "dummy", + "namespace": "cluster-x-default", + "annotations": map[string]interface{}{ + "kube-bind.io/resource-owner": "Consumer", + }, + }, + "data": map[string]interface{}{ + "username": "user", + "password": "pass", + }, + }, + ) + return obj, nil + }, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + obj := &unstructured.Unstructured{} + obj.SetUnstructuredContent( + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "dummy", + "namespace": "default", + }, + "data": map[string]interface{}{ + "username": "user", + }, + }, + ) + return obj, nil + }, + updateProviderObject: func(ctx context.Context, obj *unstructured.Unstructured) error { + updateObj = obj + return nil + }, + } + + err := r.reconcile(context.TODO(), "cluster-x-default", "dummy") + if err != nil { + t.Fatal(err) + } + + if updateObj == nil { + t.Fatal("update object nil") + } + if v, ok := updateObj.GetAnnotations()["kube-bind.io/resource-owner"]; !ok || v != "Consumer" { + t.Error("updated object did not have 'kube-bind.io/resource-owner: Consumer' annotation") + } +} + func TestUpdate(t *testing.T) { t.Parallel() @@ -158,6 +336,9 @@ func TestUpdate(t *testing.T) { "metadata": map[string]interface{}{ "name": "dummy", "namespace": "cluster-x-default", + "annotations": map[string]interface{}{ + "kube-bind.io/resource-owner": "Provider", + }, }, "data": map[string]interface{}{ "username": "user", @@ -174,9 +355,6 @@ func TestUpdate(t *testing.T) { "metadata": map[string]interface{}{ "name": "dummy", "namespace": "default", - "annotations": map[string]interface{}{ - "kube-bind.io/claimedresource": "true", - }, }, "data": map[string]interface{}{ "username": "user", @@ -199,8 +377,8 @@ func TestUpdate(t *testing.T) { if updateObj == nil { t.Fatal("update object nil") } - if v, ok := updateObj.GetAnnotations()["kube-bind.io/claimedresource"]; !ok || v != "true" { - t.Error("updated object did not have 'kube-bind.io/claimedresource: true' annotation") + if v, ok := updateObj.GetAnnotations()["kube-bind.io/resource-owner"]; !ok || v != "Provider" { + t.Error("updated object did not have 'kube-bind.io/resource-owner: Provider' annotation") } } func TestUpdateNotNeeded(t *testing.T) { @@ -213,8 +391,9 @@ func TestUpdateNotNeeded(t *testing.T) { obj.SetUnstructuredContent( map[string]interface{}{ "metadata": map[string]interface{}{ - "name": "dummy", - "namespace": "cluster-x-default", + "name": "dummy", + "namespace": "cluster-x-default", + "annotations": map[string]interface{}{"kube-bind.io/resource-owner": "Provider"}, }, "data": map[string]interface{}{ "username": "user", @@ -239,7 +418,7 @@ func TestUpdateNotNeeded(t *testing.T) { }, ) obj.SetAnnotations(map[string]string{ - "kube-bind.io/claimedresource": "true", + "kube-bind.io/resource-owner": "Provider", }) return obj, nil }, diff --git a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go index c0aa6b79e..9222dbdba 100644 --- a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go +++ b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go @@ -219,11 +219,6 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export continue } - if claim.Selector.Owner == kubebindv1alpha1.Consumer { - // TODO implement upsync - continue - } - claimGVR := runtimeschema.GroupVersionResource{ Group: claim.Group, Version: claim.Version, @@ -252,6 +247,7 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export } claimedCtrl, err := claimedresources.NewController( claimGVR, + claim.PermissionClaim, r.providerNamespace, r.consumerConfig, r.providerConfig, diff --git a/test/e2e/bind/fixtures/consumer/bootstrap.go b/test/e2e/bind/fixtures/consumer/bootstrap.go new file mode 100644 index 000000000..a263d0caf --- /dev/null +++ b/test/e2e/bind/fixtures/consumer/bootstrap.go @@ -0,0 +1,42 @@ +/* +Copyright 2023 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 consumer + +import ( + "context" + "embed" + "testing" + + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + + "github.com/kube-bind/kube-bind/pkg/bootstrap" +) + +//go:embed *.yaml +var raw embed.FS + +func Bootstrap(t *testing.T, discoveryClient discovery.DiscoveryInterface, dynamicClient dynamic.Interface, batteriesIncluded sets.String) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + err := bootstrap.Bootstrap(ctx, discoveryClient, dynamicClient, batteriesIncluded, raw) + require.NoError(t, err) +} diff --git a/test/e2e/bind/fixtures/consumer/crd-mangodb.yaml b/test/e2e/bind/fixtures/consumer/crd-mangodb.yaml new file mode 100644 index 000000000..0ec795060 --- /dev/null +++ b/test/e2e/bind/fixtures/consumer/crd-mangodb.yaml @@ -0,0 +1,58 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: mangodbs.mangodb.com + labels: + kube-bind.io/exported: "true" +spec: + group: mangodb.com + names: + kind: MangoDB + listKind: MangoDBList + plural: mangodbs + singular: mangodb + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + tier: + type: string + enum: + - Dedicated + - Shared + default: Shared + region: + type: string + default: us-east-1 + minLength: 1 + backup: + type: boolean + default: false + tokenSecret: + type: string + minLength: 1 + required: + - tokenSecret + status: + type: object + properties: + phase: + type: string + enum: + - Pending + - Running + - Succeeded + - Failed + - Unknown + required: + - spec diff --git a/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml new file mode 100644 index 000000000..b6445686d --- /dev/null +++ b/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml @@ -0,0 +1,15 @@ +kind: APIServiceExportTemplate +apiVersion: kube-bind.io/v1alpha1 +metadata: + name: "mangodbs" + namespace: default +spec: + APIServiceSelector: + resource: mangodbs + group: mangodb.com + permissionClaims: + - group: "" + resource: secrets + version: v1 + selector: + owner: Consumer diff --git a/test/e2e/konnector/claimedresources_test.go b/test/e2e/konnector/claimedresources_test.go new file mode 100644 index 000000000..2c971d87c --- /dev/null +++ b/test/e2e/konnector/claimedresources_test.go @@ -0,0 +1,527 @@ +package konnector + +import ( + "bytes" + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "gopkg.in/headzoo/surf.v1" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/cli-runtime/pkg/genericclioptions" + "sigs.k8s.io/yaml" + + kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" + consumerfixtures "github.com/kube-bind/kube-bind/test/e2e/bind/fixtures/consumer" + providerfixtures "github.com/kube-bind/kube-bind/test/e2e/bind/fixtures/provider" + "github.com/kube-bind/kube-bind/test/e2e/framework" +) + +func TestProviderOwned(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + t.Logf("Creating provider workspace") + providerConfig, providerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-claimed-resources-provider")) + + t.Logf("Starting backend with random port") + addr, _ := framework.StartBackend(t, providerConfig, "--kubeconfig="+providerKubeconfig, "--listen-port=0", "--consumer-scope="+string(kubebindv1alpha1.NamespacedScope)) + + t.Logf("Creating MangoDB CRD on provider side") + providerfixtures.Bootstrap(t, framework.DiscoveryClient(t, providerConfig), framework.DynamicClient(t, providerConfig), nil) + + t.Logf("Creating consumer workspace and starting konnector") + consumerConfig, consumerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-claimed-resources-provider")) + framework.StartKonnector(t, consumerConfig, "--kubeconfig="+consumerKubeconfig) + + providerKubeClient := framework.KubeClient(t, providerConfig) + consumerKubeClient := framework.KubeClient(t, consumerConfig) + + consumerClient := framework.DynamicClient(t, consumerConfig).Resource( + schema.GroupVersionResource{Group: "mangodb.com", Version: "v1alpha1", Resource: "mangodbs"}, + ).Namespace("default") + providerClient := framework.DynamicClient(t, providerConfig).Resource( + schema.GroupVersionResource{Group: "mangodb.com", Version: "v1alpha1", Resource: "mangodbs"}, + ) + + upstreamNS := "unknown" + downstreamNS := "unknown" + + for _, tc := range []struct { + name string + step func(t *testing.T) + }{ + { + name: "MangoDB is bound dry run", + step: func(t *testing.T) { + iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams() + authURLDryRunCh := make(chan string, 1) + go simulateBrowser(t, authURLDryRunCh, "mangodbs") + framework.Bind(t, iostreams, authURLDryRunCh, nil, fmt.Sprintf("http://%s/export", addr.String()), "--kubeconfig", consumerKubeconfig, "--skip-konnector", "--dry-run") + _, err := yaml.YAMLToJSON(bufOut.Bytes()) + require.NoError(t, err) + }, + }, + { + name: "MangoDB is bound", + step: func(t *testing.T) { + in := bytes.NewBufferString("y\n") + iostreams, _, _, _ := genericclioptions.NewTestIOStreams() + authURLCh := make(chan string, 1) + go simulateBrowser(t, authURLCh, "mangodbs") + invocations := make(chan framework.SubCommandInvocation, 1) + framework.Bind(t, iostreams, authURLCh, invocations, fmt.Sprintf("http://%s/export", addr.String()), "--kubeconfig", consumerKubeconfig, "--skip-konnector") + inv := <-invocations + requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "*", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args) + framework.BindAPIService(t, in, "", inv.Args...) + + t.Logf("Waiting for MangoDB CRD to be created on consumer side") + crdClient := framework.ApiextensionsClient(t, consumerConfig).ApiextensionsV1().CustomResourceDefinitions() + require.Eventually(t, func() bool { + _, err := crdClient.Get(ctx, "mangodbs.mangodb.com", metav1.GetOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for MangoDB CRD to be created on consumer side") + }, + }, + { + name: "instances are synced", + step: func(t *testing.T) { + t.Logf("Trying to create MangoDB on consumer side") + + require.Eventually(t, func() bool { + _, err := consumerClient.Create(ctx, toUnstructured(t, ` +apiVersion: mangodb.com/v1alpha1 +kind: MangoDB +metadata: + name: test +spec: + tokenSecret: credentials +`), metav1.CreateOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for MangoDB CRD to be created on consumer side") + + t.Logf("Waiting for the MangoDB instance to be created on consumer side") + var consumerMangos *unstructured.UnstructuredList + require.Eventually(t, func() bool { + var err error + consumerMangos, err = consumerClient.List(ctx, metav1.ListOptions{}) + return err == nil && len(consumerMangos.Items) == 1 + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for the MangoDB instance to be created on consumer side") + + // this is used everywhere further down + downstreamNS = consumerMangos.Items[0].GetNamespace() + + t.Logf("Waiting for the MangoDB instance to be created on provider side") + var mangos *unstructured.UnstructuredList + require.Eventually(t, func() bool { + var err error + mangos, err = providerClient.List(ctx, metav1.ListOptions{}) + return err == nil && len(mangos.Items) == 1 + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for the MangoDB instance to be created on provider side") + + // this is used everywhere further down + upstreamNS = mangos.Items[0].GetNamespace() + }, + }, + { + name: "secret creation at provider", + step: func(t *testing.T) { + testSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: upstreamNS, + }, + Data: map[string][]byte{ + "test": []byte("dummy"), + }, + } + + _, err := providerKubeClient.CoreV1().Secrets(upstreamNS).Create(ctx, &testSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + }, + }, + { + name: "secret is automatically created at consumer", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + _, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be created at consumer") + }, + }, + { + name: "secret updated at consumer is overwritten", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + s, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if err != nil { + return false + } + + s.Data["test"] = []byte("updated") + + _, err = consumerKubeClient.CoreV1().Secrets(downstreamNS).Update(ctx, s, metav1.UpdateOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be updated at consumer") + + require.Eventually(t, func() bool { + s, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if err != nil { + return false + } + + if v, ok := s.Data["test"]; ok && equality.Semantic.DeepEqual(v, []byte("dummy")) { + return true + } + return false + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be overwritten at consumer") + }, + }, + { + name: "secret is automatically updated at consumer", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + s, err := providerKubeClient.CoreV1().Secrets(upstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if err != nil { + return false + } + + s.Data["test"] = []byte("updated") + + _, err = providerKubeClient.CoreV1().Secrets(upstreamNS).Update(ctx, s, metav1.UpdateOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be updated at provider") + + require.Eventually(t, func() bool { + s, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if err != nil { + return false + } + + if v, ok := s.Data["test"]; ok && equality.Semantic.DeepEqual(v, []byte("updated")) { + return true + } + return false + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be updated at consumer") + }, + }, + { + name: "secret deleted at consumer is automatically recreated", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Delete(ctx, "test-secret", metav1.DeleteOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be deleted at consumer") + + require.Eventually(t, func() bool { + _, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + return !errors.IsNotFound(err) + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be recreated at consumer") + }, + }, + { + name: "secret is automatically deleted at consumer", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + err := providerKubeClient.CoreV1().Secrets(upstreamNS).Delete(ctx, "test-secret", metav1.DeleteOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be deleted at provider") + + require.Eventually(t, func() bool { + _, err := consumerKubeClient.CoreV1().Secrets(downstreamNS).Get(ctx, "test-secret", metav1.GetOptions{}) + return errors.IsNotFound(err) + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be deleted at consumer") + }, + }, + } { + tc.step(t) + } +} + +func TestConsumerOwned(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + t.Logf("Creating provider workspace") + providerConfig, providerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-claimed-resources-consumer")) + + t.Logf("Starting backend with random port") + addr, _ := framework.StartBackend(t, providerConfig, "--kubeconfig="+providerKubeconfig, "--listen-port=0", "--consumer-scope="+string(kubebindv1alpha1.NamespacedScope)) + + t.Logf("Creating MangoDB CRD on provider side") + consumerfixtures.Bootstrap(t, framework.DiscoveryClient(t, providerConfig), framework.DynamicClient(t, providerConfig), nil) + + t.Logf("Creating consumer workspace and starting konnector") + consumerConfig, consumerKubeconfig := framework.NewWorkspace(t, framework.ClientConfig(t), framework.WithGenerateName("test-claimed-resources-consumer")) + framework.StartKonnector(t, consumerConfig, "--kubeconfig="+consumerKubeconfig) + + providerKubeClient := framework.KubeClient(t, providerConfig) + consumerKubeClient := framework.KubeClient(t, consumerConfig) + + consumerClient := framework.DynamicClient(t, consumerConfig).Resource( + schema.GroupVersionResource{Group: "mangodb.com", Version: "v1alpha1", Resource: "mangodbs"}, + ).Namespace("default") + providerClient := framework.DynamicClient(t, providerConfig).Resource( + schema.GroupVersionResource{Group: "mangodb.com", Version: "v1alpha1", Resource: "mangodbs"}, + ) + + providerNS := "unknown" + consumerNS := "unknown" + + for _, tc := range []struct { + name string + step func(t *testing.T) + }{ + { + name: "MangoDB is bound dry run", + step: func(t *testing.T) { + iostreams, _, bufOut, _ := genericclioptions.NewTestIOStreams() + authURLDryRunCh := make(chan string, 1) + go simulateBrowser(t, authURLDryRunCh, "mangodbs") + framework.Bind(t, iostreams, authURLDryRunCh, nil, fmt.Sprintf("http://%s/export", addr.String()), "--kubeconfig", consumerKubeconfig, "--skip-konnector", "--dry-run") + _, err := yaml.YAMLToJSON(bufOut.Bytes()) + require.NoError(t, err) + }, + }, + { + name: "MangoDB is bound", + step: func(t *testing.T) { + in := bytes.NewBufferString("y\n") + iostreams, _, _, _ := genericclioptions.NewTestIOStreams() + authURLCh := make(chan string, 1) + go simulateBrowser(t, authURLCh, "mangodbs") + invocations := make(chan framework.SubCommandInvocation, 1) + framework.Bind(t, iostreams, authURLCh, invocations, fmt.Sprintf("http://%s/export", addr.String()), "--kubeconfig", consumerKubeconfig, "--skip-konnector") + inv := <-invocations + requireEqualSlicePattern(t, []string{"apiservice", "--remote-kubeconfig-namespace", "*", "--remote-kubeconfig-name", "*", "-f", "*", "--kubeconfig=" + consumerKubeconfig, "--skip-konnector=true", "--no-banner"}, inv.Args) + framework.BindAPIService(t, in, "", inv.Args...) + + t.Logf("Waiting for MangoDB CRD to be created on consumer side") + crdClient := framework.ApiextensionsClient(t, consumerConfig).ApiextensionsV1().CustomResourceDefinitions() + require.Eventually(t, func() bool { + _, err := crdClient.Get(ctx, "mangodbs.mangodb.com", metav1.GetOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for MangoDB CRD to be created on consumer side") + }, + }, + { + name: "instances are synced", + step: func(t *testing.T) { + t.Logf("Trying to create MangoDB on consumer side") + + require.Eventually(t, func() bool { + _, err := consumerClient.Create(ctx, toUnstructured(t, ` +apiVersion: mangodb.com/v1alpha1 +kind: MangoDB +metadata: + name: test +spec: + tokenSecret: credentials +`), metav1.CreateOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for MangoDB CRD to be created on consumer side") + + t.Logf("Waiting for the MangoDB instance to be created on consumer side") + var consumerMangos *unstructured.UnstructuredList + require.Eventually(t, func() bool { + var err error + consumerMangos, err = consumerClient.List(ctx, metav1.ListOptions{}) + return err == nil && len(consumerMangos.Items) == 1 + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for the MangoDB instance to be created on consumer side") + + // this is used everywhere further down + consumerNS = consumerMangos.Items[0].GetNamespace() + + t.Logf("Waiting for the MangoDB instance to be created on provider side") + var mangos *unstructured.UnstructuredList + require.Eventually(t, func() bool { + var err error + mangos, err = providerClient.List(ctx, metav1.ListOptions{}) + return err == nil && len(mangos.Items) == 1 + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for the MangoDB instance to be created on provider side") + + // this is used everywhere further down + providerNS = mangos.Items[0].GetNamespace() + }, + }, + { + name: "secret creation at consumer", + step: func(t *testing.T) { + testSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: consumerNS, + }, + Data: map[string][]byte{ + "test": []byte("dummy"), + }, + } + + _, err := consumerKubeClient.CoreV1().Secrets(consumerNS).Create(ctx, &testSecret, metav1.CreateOptions{}) + require.NoError(t, err) + }, + }, + { + name: "secret is automatically created at provider", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + _, err := providerKubeClient.CoreV1().Secrets(providerNS).Get(ctx, "test-secret", metav1.GetOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be created on provider side") + }, + }, + { + name: "secret updated at provider is overwritten", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + s, err := providerKubeClient.CoreV1().Secrets(providerNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if err != nil { + return false + } + + s.Data["test"] = []byte("updated") + + _, err = providerKubeClient.CoreV1().Secrets(providerNS).Update(ctx, s, metav1.UpdateOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be updated at provider") + + require.Eventually(t, func() bool { + s, err := providerKubeClient.CoreV1().Secrets(providerNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if err != nil { + return false + } + + if v, ok := s.Data["test"]; ok && equality.Semantic.DeepEqual(v, []byte("dummy")) { + return true + } + return false + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be overwritten at consumer") + }, + }, + { + name: "secret is automatically updated at provider", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + s, err := consumerKubeClient.CoreV1().Secrets(consumerNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if err != nil { + return false + } + + s.Data["test"] = []byte("updated") + + _, err = consumerKubeClient.CoreV1().Secrets(consumerNS).Update(ctx, s, metav1.UpdateOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be updated at consumer") + + require.Eventually(t, func() bool { + s, err := providerKubeClient.CoreV1().Secrets(providerNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if err != nil { + return false + } + + if v, ok := s.Data["test"]; ok && equality.Semantic.DeepEqual(v, []byte("updated")) { + return true + } + return false + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be updated at provider") + + }, + }, + { + name: "secret deleted at provider is automatically recreated", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + err := providerKubeClient.CoreV1().Secrets(providerNS).Delete(ctx, "test-secret", metav1.DeleteOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be deleted at provider") + + require.Eventually(t, func() bool { + _, err := providerKubeClient.CoreV1().Secrets(providerNS).Get(ctx, "test-secret", metav1.GetOptions{}) + return !errors.IsNotFound(err) + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be recreated at provider") + }, + }, + { + name: "secret is automatically deleted at provider", + step: func(t *testing.T) { + require.Eventually(t, func() bool { + err := consumerKubeClient.CoreV1().Secrets(consumerNS).Delete(ctx, "test-secret", metav1.DeleteOptions{}) + return err == nil + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be deleted at consumer") + + require.Eventually(t, func() bool { + s, err := providerKubeClient.CoreV1().Secrets(providerNS).Get(ctx, "test-secret", metav1.GetOptions{}) + if errors.IsNotFound(err) { + return true + } else { + t.Logf("secret still exists: %+v", s) + return false + } + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for secret to be deleted at provider") + }, + }, + } { + tc.step(t) + } +} + +func simulateBrowser(t *testing.T, authURLCh chan string, resource string) { + browser := surf.NewBrowser() + authURL := <-authURLCh + + t.Logf("Browsing to auth URL: %s", authURL) + err := browser.Open(authURL) + require.NoError(t, err) + + t.Logf("Waiting for browser to be at /resources") + framework.BrowerEventuallyAtPath(t, browser, "/resources") + + t.Logf("Clicking %s", resource) + err = browser.Click("a." + resource) + require.NoError(t, err) + + t.Logf("Waiting for browser to be forwarded to client") + framework.BrowerEventuallyAtPath(t, browser, "/callback") +} + +func requireEqualSlicePattern(t *testing.T, pattern []string, slice []string) { + t.Helper() + + require.Equal(t, len(pattern), len(slice), "slice length doesn't match pattern length\n got: %s\nexpected: %s", strings.Join(slice, " "), strings.Join(pattern, " ")) + + for i, s := range slice { + if pattern[i] == "*" { + continue + } + require.Equal(t, pattern[i], s, "slice doesn't match pattern at index %d\n got: %s\nexpected: %s", i, strings.Join(slice, " "), strings.Join(pattern, " ")) + } +} + +func toUnstructured(t *testing.T, manifest string) *unstructured.Unstructured { + t.Helper() + + obj := map[string]interface{}{} + err := yaml.Unmarshal([]byte(manifest), &obj) + require.NoError(t, err) + + return &unstructured.Unstructured{Object: obj} +} From d1e29fb65e681fc6304f6b21bfa498d2715d1d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 7 Jun 2023 16:45:55 +0200 Subject: [PATCH 12/26] Change CRD according to discussion --- .../crd/kube-bind.io_apiservicebindings.yaml | 20 +++++++------- ...kube-bind.io_apiserviceexportrequests.yaml | 26 ++++++++++--------- .../crd/kube-bind.io_apiserviceexports.yaml | 20 +++++++------- ...ube-bind.io_apiserviceexporttemplates.yaml | 20 +++++++------- .../v1alpha1/apiservicebinding_types.go | 13 +++++----- 5 files changed, 54 insertions(+), 45 deletions(-) diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index e7c859839..1a342cfb7 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -125,7 +125,7 @@ spec: providerOverrides: description: providerOverrides will make the provider override any object that might already exist in the consumer cluster - if it has the same namespaced name as a resource created + if it has the same resource identifier as a resource created by the provider, but is not the result of syncing. type: boolean recreateWhenConsumerSideDeleted: @@ -182,18 +182,20 @@ spec: updating. Useful for immutable objects. type: boolean fields: - description: fields are the fields owned by the owner of - the claim. If the owner sets values of those fields, they - will be synced to the other participant. Mutually exclusive - with preservings. + description: fields are a list of JSON Paths describing + which parts of an object the provider wants to control + in case the object is owned by the consumer as a whole. + This field cannot be set if provider owned objects are + selected in this claim, and must be non-empty otherwise. + Mutually exclusive with preservings. items: type: string type: array preservings: - description: Preservings are the fields that are preserved - by the konnector during synchronization. The owner is - not able to set those fields. If the owner changes the - value of these fields, their change will be overwritten. + description: preserving is a list of JSON Paths describing + fields that should be preserved while updating with the + object state on the provider side. This field cannot be + set if consumer owned objects are selected in this claim. items: type: string type: array diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 602bf8cfb..6a4fe96dd 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -112,9 +112,9 @@ spec: providerOverrides: description: providerOverrides will make the provider override any object that might already exist in - the consumer cluster if it has the same namespaced - name as a resource created by the provider, but - is not the result of syncing. + the consumer cluster if it has the same resource + identifier as a resource created by the provider, + but is not the result of syncing. type: boolean recreateWhenConsumerSideDeleted: description: recreateWhenConsumerSideDeleted set to @@ -168,19 +168,21 @@ spec: one instead of updating. Useful for immutable objects. type: boolean fields: - description: fields are the fields owned by the owner - of the claim. If the owner sets values of those - fields, they will be synced to the other participant. - Mutually exclusive with preservings. + description: fields are a list of JSON Paths describing + which parts of an object the provider wants to control + in case the object is owned by the consumer as a + whole. This field cannot be set if provider owned + objects are selected in this claim, and must be + non-empty otherwise. Mutually exclusive with preservings. items: type: string type: array preservings: - description: Preservings are the fields that are preserved - by the konnector during synchronization. The owner - is not able to set those fields. If the owner changes - the value of these fields, their change will be - overwritten. + description: preserving is a list of JSON Paths describing + fields that should be preserved while updating with + the object state on the provider side. This field + cannot be set if consumer owned objects are selected + in this claim. items: type: string type: array diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index dc0680d50..7b05a7900 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -148,7 +148,7 @@ spec: providerOverrides: description: providerOverrides will make the provider override any object that might already exist in the consumer cluster - if it has the same namespaced name as a resource created + if it has the same resource identifier as a resource created by the provider, but is not the result of syncing. type: boolean recreateWhenConsumerSideDeleted: @@ -200,18 +200,20 @@ spec: updating. Useful for immutable objects. type: boolean fields: - description: fields are the fields owned by the owner of - the claim. If the owner sets values of those fields, they - will be synced to the other participant. Mutually exclusive - with preservings. + description: fields are a list of JSON Paths describing + which parts of an object the provider wants to control + in case the object is owned by the consumer as a whole. + This field cannot be set if provider owned objects are + selected in this claim, and must be non-empty otherwise. + Mutually exclusive with preservings. items: type: string type: array preservings: - description: Preservings are the fields that are preserved - by the konnector during synchronization. The owner is - not able to set those fields. If the owner changes the - value of these fields, their change will be overwritten. + description: preserving is a list of JSON Paths describing + fields that should be preserved while updating with the + object state on the provider side. This field cannot be + set if consumer owned objects are selected in this claim. items: type: string type: array diff --git a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml index 2a15d271d..b828768e5 100644 --- a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml @@ -98,7 +98,7 @@ spec: providerOverrides: description: providerOverrides will make the provider override any object that might already exist in the consumer cluster - if it has the same namespaced name as a resource created + if it has the same resource identifier as a resource created by the provider, but is not the result of syncing. type: boolean recreateWhenConsumerSideDeleted: @@ -150,18 +150,20 @@ spec: updating. Useful for immutable objects. type: boolean fields: - description: fields are the fields owned by the owner of - the claim. If the owner sets values of those fields, they - will be synced to the other participant. Mutually exclusive - with preservings. + description: fields are a list of JSON Paths describing + which parts of an object the provider wants to control + in case the object is owned by the consumer as a whole. + This field cannot be set if provider owned objects are + selected in this claim, and must be non-empty otherwise. + Mutually exclusive with preservings. items: type: string type: array preservings: - description: Preservings are the fields that are preserved - by the konnector during synchronization. The owner is - not able to set those fields. If the owner changes the - value of these fields, their change will be overwritten. + description: preserving is a list of JSON Paths describing + fields that should be preserved while updating with the + object state on the provider side. This field cannot be + set if consumer owned objects are selected in this claim. items: type: string type: array diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 21e048931..a28c03f6a 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -157,7 +157,7 @@ type PermissionClaim struct { type OnConflictOptions struct { // providerOverrides will make the provider override any object that might already exist - // in the consumer cluster if it has the same namespaced name as a resource created by the + // in the consumer cluster if it has the same resource identifier as a resource created by the // provider, but is not the result of syncing. ProviderOverwrites bool `json:"providerOverrides"` @@ -176,14 +176,15 @@ type CreateOptions struct { } type UpdateOptions struct { - // fields are the fields owned by the owner of the claim. If the owner sets values of those - // fields, they will be synced to the other participant. + // fields are a list of JSON Paths describing which parts of an object the provider wants to control in case + // the object is owned by the consumer as a whole. This field cannot be set if provider owned objects + // are selected in this claim, and must be non-empty otherwise. // Mutually exclusive with preservings. Fields []string `json:"fields,omitempty"` - // Preservings are the fields that are preserved by the konnector during synchronization. - // The owner is not able to set those fields. If the owner changes the value of these fields, - // their change will be overwritten. + // preserving is a list of JSON Paths describing fields that should be preserved while updating with the + // object state on the provider side. This field cannot be set if consumer owned objects are selected in + // this claim. Preserving []string `json:"preservings,omitempty"` // alwaysRecreate, when true will make the konnector delete the old object and create a new one From a8b97e08601c94818bc087c58863b7a1befbb8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 7 Jun 2023 17:12:51 +0200 Subject: [PATCH 13/26] fixup! Add readable prompts for permission claims (#1) --- .../bind-apiservice/plugin/servicebindings.go | 44 ++++----- .../plugin/servicebindings_test.go | 97 ++++++++++--------- 2 files changed, 73 insertions(+), 68 deletions(-) diff --git a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go index 6a84429a4..7ffd78b52 100644 --- a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go +++ b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go @@ -187,34 +187,34 @@ func writeFirstLines(b *bytes.Buffer, groupResource string, claim kubebindv1alph owner = claim.Selector.Owner } + var verb string + switch owner { + case kubebindv1alpha1.Provider: + verb = "write" + case kubebindv1alpha1.Consumer: + verb = "read" + default: + verb = "read and write" + } + switch { case !donate && !adopt: - groupResource = "read " + groupResource + groupResource = verb + " " + groupResource case donate && !adopt: groupResource = "create user owned " + groupResource case !donate && adopt: groupResource = "have ownership of " + groupResource } - _, err = fmt.Fprintf(b, "The provider wants to %s on your cluster.", groupResource) - - if owner == kubebindv1alpha1.Consumer { - owner = "you" - } - if owner == kubebindv1alpha1.Provider { - owner = "the provider" - } - switch { - case owner == "" && name == "": - _, err = fmt.Fprintf(b, "\n") - case owner != "" && name == "": - _, err = fmt.Fprintf(b, " This only applies to objects which are owned by %s.\n", owner) - case owner == "" && name != "": - _, err = fmt.Fprintf(b, " This only applies to objects which are referenced with:\n\tname: \"%s\"\n", name) - case owner != "" && name != "": - _, err = fmt.Fprintf(b, " This only applies to objects which are owned by %s and to objects which are referenced with:\n name: \"%s\"\n", owner, name) + var ref string + if name != "" { + ref = fmt.Sprintf(" which are referenced with:\n\tname: \"%s\"\n", name) + } else { + ref = " " } + _, err = fmt.Fprintf(b, "The provider wants to %s%son your cluster.\n", groupResource, ref) + return err } @@ -244,14 +244,10 @@ func writeUpdateClause(b *bytes.Buffer, claim kubebindv1alpha1.PermissionClaim) } if claim.Update.Fields != nil { - owner := "the provider" - if claim.Create != nil && claim.Create.Donate { - owner = "the user" - } - _, err = fmt.Fprintf(b, "The following fields of the objects will still be able to be changed by %s:\n", owner) + _, err = fmt.Fprintf(b, "The following fields of the objects will still be able to be changed by the provider:\n") } if claim.Update.Preserving != nil { - _, err = b.WriteString("The following fields of the objects will be overwritten with their initial values, if they are modified:\n") + _, err = b.WriteString("The following fields of the objects will be preserved by the provider:\n") } for _, s := range append(claim.Update.Fields, claim.Update.Preserving...) { diff --git a/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go b/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go index 568e875d2..8b683a69a 100644 --- a/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go +++ b/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go @@ -46,7 +46,7 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -62,7 +62,7 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: false, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is optional.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -79,8 +79,9 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider and to objects which are referenced with:\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") which are referenced with:\n" + "\tname: \"bar\"\n" + + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -96,7 +97,7 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"example.com/v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -113,8 +114,9 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster. This only applies to objects which are owned by the provider and to objects which are referenced with:\n" + + "The provider wants to write foo objects (apiVersion: \"example.com/v1\") which are referenced with:\n" + "\tname: \"bar\"\n" + + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -131,11 +133,11 @@ func TestHumanReadablePromt(t *testing.T) { Required: true, Create: &kubebindv1alpha1.CreateOptions{}, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,CreateOptions=false", + {"Owner=Provider,CreateOptions.Donate=false", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", @@ -150,7 +152,7 @@ func TestHumanReadablePromt(t *testing.T) { Donate: false, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -169,7 +171,7 @@ func TestHumanReadablePromt(t *testing.T) { Donate: true, }, }, - "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -186,7 +188,7 @@ func TestHumanReadablePromt(t *testing.T) { Required: true, OnConflict: &kubebindv1alpha1.OnConflictOptions{}, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -205,7 +207,7 @@ func TestHumanReadablePromt(t *testing.T) { ProviderOverwrites: false, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -224,7 +226,7 @@ func TestHumanReadablePromt(t *testing.T) { ProviderOverwrites: true, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Conflicting objects will be overwritten and created objects will not be recreated upon deletion.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -244,7 +246,7 @@ func TestHumanReadablePromt(t *testing.T) { RecreateWhenConsumerSideDeleted: false, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -263,7 +265,7 @@ func TestHumanReadablePromt(t *testing.T) { RecreateWhenConsumerSideDeleted: true, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Conflicting objects will not be overwritten and created objects will be recreated upon deletion.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -281,7 +283,7 @@ func TestHumanReadablePromt(t *testing.T) { Required: true, Update: &kubebindv1alpha1.UpdateOptions{}, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -300,8 +302,8 @@ func TestHumanReadablePromt(t *testing.T) { Fields: []string{"foo", "bar"}, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + - "The following fields of the objects will still be able to be changed by the provider:\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The following fields of the objects will still be able to be changed by the provider:\n" + // TODO "\t\"foo\"\n" + "\t\"bar\"\n" + "Accepting this Permission is required in order to proceed.\n" + @@ -322,8 +324,9 @@ func TestHumanReadablePromt(t *testing.T) { Preserving: []string{"foo", "bar"}, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + - "The following fields of the objects will be overwritten with their initial values, if they are modified:\n" + " \"foo\"\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The following fields of the objects will be preserved by the provider:\n" + + "\t\"foo\"\n" + "\t\"bar\"\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -343,7 +346,7 @@ func TestHumanReadablePromt(t *testing.T) { AlwaysRecreate: true, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + + "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Modification of said objects will by handled by deletion and recreation of said objects.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -366,8 +369,8 @@ func TestHumanReadablePromt(t *testing.T) { Fields: []string{"foo", "bar"}, }, }, - "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + - "The following fields of the objects will still be able to be changed by the user:\n" + + "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The following fields of the objects will still be able to be changed by the provider:\n" + "\t\"foo\"\n" + "\t\"bar\"\n" + "Accepting this Permission is required in order to proceed.\n" + @@ -391,8 +394,9 @@ func TestHumanReadablePromt(t *testing.T) { Preserving: []string{"foo", "bar"}, }, }, - "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by the provider.\n" + - "The following fields of the objects will be overwritten with their initial values, if they are modified:\n" + " \"foo\"\n" + + "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The following fields of the objects will be preserved by the provider:\n" + + "\t\"foo\"\n" + "\t\"bar\"\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -409,7 +413,7 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -426,8 +430,9 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you and to objects which are referenced with:\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") which are referenced with:\n" + "\tname: \"bar\"\n" + + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -443,7 +448,7 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -460,8 +465,9 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"example.com/v1\") on your cluster. This only applies to objects which are owned by you and to objects which are referenced with:\n" + + "The provider wants to read foo objects (apiVersion: \"example.com/v1\") which are referenced with:\n" + "\tname: \"bar\"\n" + + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -478,7 +484,7 @@ func TestHumanReadablePromt(t *testing.T) { Adopt: true, Required: true, }, - "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -496,8 +502,9 @@ func TestHumanReadablePromt(t *testing.T) { Adopt: true, Required: true, }, - "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you and to objects which are referenced with:\n" + + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") which are referenced with:\n" + "\tname: \"bar\"\n" + + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -514,7 +521,7 @@ func TestHumanReadablePromt(t *testing.T) { Required: true, OnConflict: &kubebindv1alpha1.OnConflictOptions{}, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -533,7 +540,7 @@ func TestHumanReadablePromt(t *testing.T) { ProviderOverwrites: false, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -552,7 +559,7 @@ func TestHumanReadablePromt(t *testing.T) { ProviderOverwrites: true, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + "Conflicting objects will be overwritten and created objects will not be recreated upon deletion.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -570,7 +577,7 @@ func TestHumanReadablePromt(t *testing.T) { Required: true, Update: &kubebindv1alpha1.UpdateOptions{}, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -589,7 +596,7 @@ func TestHumanReadablePromt(t *testing.T) { Fields: []string{"foo", "bar"}, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + "The following fields of the objects will still be able to be changed by the provider:\n" + "\t\"foo\"\n" + "\t\"bar\"\n" + @@ -611,8 +618,9 @@ func TestHumanReadablePromt(t *testing.T) { Preserving: []string{"foo", "bar"}, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + - "The following fields of the objects will be overwritten with their initial values, if they are modified:\n" + " \"foo\"\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The following fields of the objects will be preserved by the provider:\n" + + "\t\"foo\"\n" + "\t\"bar\"\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -632,7 +640,7 @@ func TestHumanReadablePromt(t *testing.T) { AlwaysRecreate: true, }, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + "Modification of said objects will by handled by deletion and recreation of said objects.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -653,7 +661,7 @@ func TestHumanReadablePromt(t *testing.T) { Fields: []string{"foo", "bar"}, }, }, - "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster.\n" + "The following fields of the objects will still be able to be changed by the provider:\n" + "\t\"foo\"\n" + "\t\"bar\"\n" + @@ -676,8 +684,8 @@ func TestHumanReadablePromt(t *testing.T) { Preserving: []string{"foo", "bar"}, }, }, - "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are owned by you.\n" + - "The following fields of the objects will be overwritten with their initial values, if they are modified:\n" + " \"foo\"\n" + + "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The following fields of the objects will be preserved by the provider:\n" + " \"foo\"\n" + "\t\"bar\"\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -692,7 +700,7 @@ func TestHumanReadablePromt(t *testing.T) { Selector: kubebindv1alpha1.ResourceSelector{}, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + + "The provider wants to read and write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -708,8 +716,9 @@ func TestHumanReadablePromt(t *testing.T) { }, Required: true, }, - "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster. This only applies to objects which are referenced with:\n" + + "The provider wants to read and write foo objects (apiVersion: \"v1\") which are referenced with:\n" + "\tname: \"bar\"\n" + + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -746,7 +755,7 @@ func TestHumanReadablePromt(t *testing.T) { }, }, "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster.\n" + - "The following fields of the objects will still be able to be changed by the user:\n" + + "The following fields of the objects will still be able to be changed by the provider:\n" + "\t\"spec\"\n" + "Accepting this Permission is optional.\n" + "Do you accept this Permission? [No,Yes]\n", From ba371f975db361edb4f97f58ddb7338e6639867c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 14 Jun 2023 09:08:30 +0200 Subject: [PATCH 14/26] fixup! Add synchronization from consumer to provider --- test/e2e/konnector/claimedresources_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/e2e/konnector/claimedresources_test.go b/test/e2e/konnector/claimedresources_test.go index 2c971d87c..09c21aab8 100644 --- a/test/e2e/konnector/claimedresources_test.go +++ b/test/e2e/konnector/claimedresources_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 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 konnector import ( From 51294bbd7b3e63484c684ee89faf722bab47e050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 22 Jun 2023 08:40:16 +0200 Subject: [PATCH 15/26] Move implmentation specific backend CRDs * Update e2e tests * update codegen scripts to include new crd --- contrib/deploy/crd/bootstrap.go | 181 ++++++++++++++++++ ...example.com_apiserviceexporttemplates.yaml | 4 +- .../apiserviceexporttemplate_types.go | 4 +- .../apis/examplebackend/v1alpha1/doc.go | 23 +++ .../apis/examplebackend/v1alpha1/register.go | 55 ++++++ .../v1alpha1/zz_generated.deepcopy.go | 150 +++++++++++++++ .../client/clientset/versioned/clientset.go | 122 ++++++++++++ .../client/clientset/versioned/doc.go | 20 ++ .../versioned/fake/clientset_generated.go | 86 +++++++++ .../client/clientset/versioned/fake/doc.go | 20 ++ .../clientset/versioned/fake/register.go | 57 ++++++ .../client/clientset/versioned/scheme/doc.go | 20 ++ .../clientset/versioned/scheme/register.go | 57 ++++++ .../v1alpha1/apiserviceexporttemplate.go | 6 +- .../typed/examplebackend/v1alpha1/doc.go | 20 ++ .../v1alpha1/examplebackend_client.go | 108 +++++++++++ .../typed/examplebackend/v1alpha1/fake/doc.go | 20 ++ .../fake/fake_apiserviceexporttemplate.go | 8 +- .../fake/fake_examplebackend_client.go | 41 ++++ .../v1alpha1/generated_expansion.go | 21 ++ .../examplebackend/interface.go | 46 +++++ .../v1alpha1/apiserviceexporttemplate.go | 16 +- .../examplebackend/v1alpha1/interface.go | 45 +++++ .../informers/externalversions/factory.go | 181 ++++++++++++++++++ .../informers/externalversions/generic.go | 63 ++++++ .../internalinterfaces/factory_interfaces.go | 41 ++++ .../v1alpha1/apiserviceexporttemplate.go | 2 +- .../v1alpha1/expansion_generated.go | 27 +++ .../example-backend/exporttemplate/index.go | 14 +- hack/update-codegen-clients.sh | 7 + hack/update-codegen.sh | 11 ++ pkg/apis/kubebind/v1alpha1/register.go | 2 - .../v1alpha1/zz_generated.deepcopy.go | 122 ------------ .../v1alpha1/fake/fake_kubebind_client.go | 4 - .../kubebind/v1alpha1/generated_expansion.go | 2 - .../kubebind/v1alpha1/kubebind_client.go | 5 - .../informers/externalversions/generic.go | 2 - .../kubebind/v1alpha1/interface.go | 7 - .../kubebind/v1alpha1/expansion_generated.go | 8 - .../consumer/exporttemplate-mangodb.yaml | 2 +- .../provider/exporttemplate-mangodb.yaml | 2 +- test/e2e/framework/backend.go | 9 +- 42 files changed, 1460 insertions(+), 181 deletions(-) create mode 100644 contrib/deploy/crd/bootstrap.go rename deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml => contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml (99%) rename {pkg/apis/kubebind => contrib/example-backend/apis/examplebackend}/v1alpha1/apiserviceexporttemplate_types.go (94%) create mode 100644 contrib/example-backend/apis/examplebackend/v1alpha1/doc.go create mode 100644 contrib/example-backend/apis/examplebackend/v1alpha1/register.go create mode 100644 contrib/example-backend/apis/examplebackend/v1alpha1/zz_generated.deepcopy.go create mode 100644 contrib/example-backend/client/clientset/versioned/clientset.go create mode 100644 contrib/example-backend/client/clientset/versioned/doc.go create mode 100644 contrib/example-backend/client/clientset/versioned/fake/clientset_generated.go create mode 100644 contrib/example-backend/client/clientset/versioned/fake/doc.go create mode 100644 contrib/example-backend/client/clientset/versioned/fake/register.go create mode 100644 contrib/example-backend/client/clientset/versioned/scheme/doc.go create mode 100644 contrib/example-backend/client/clientset/versioned/scheme/register.go rename {pkg/client/clientset/versioned/typed/kubebind => contrib/example-backend/client/clientset/versioned/typed/examplebackend}/v1alpha1/apiserviceexporttemplate.go (96%) create mode 100644 contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/doc.go create mode 100644 contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/examplebackend_client.go create mode 100644 contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/doc.go rename {pkg/client/clientset/versioned/typed/kubebind => contrib/example-backend/client/clientset/versioned/typed/examplebackend}/v1alpha1/fake/fake_apiserviceexporttemplate.go (95%) create mode 100644 contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_examplebackend_client.go create mode 100644 contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/generated_expansion.go create mode 100644 contrib/example-backend/client/informers/externalversions/examplebackend/interface.go rename {pkg/client/informers/externalversions/kubebind => contrib/example-backend/client/informers/externalversions/examplebackend}/v1alpha1/apiserviceexporttemplate.go (80%) create mode 100644 contrib/example-backend/client/informers/externalversions/examplebackend/v1alpha1/interface.go create mode 100644 contrib/example-backend/client/informers/externalversions/factory.go create mode 100644 contrib/example-backend/client/informers/externalversions/generic.go create mode 100644 contrib/example-backend/client/informers/externalversions/internalinterfaces/factory_interfaces.go rename {pkg/client/listers/kubebind => contrib/example-backend/client/listers/examplebackend}/v1alpha1/apiserviceexporttemplate.go (97%) create mode 100644 contrib/example-backend/client/listers/examplebackend/v1alpha1/expansion_generated.go diff --git a/contrib/deploy/crd/bootstrap.go b/contrib/deploy/crd/bootstrap.go new file mode 100644 index 000000000..8751ed705 --- /dev/null +++ b/contrib/deploy/crd/bootstrap.go @@ -0,0 +1,181 @@ +/* +Copyright 2023 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 crd + +import ( + "context" + "embed" + "fmt" + "sync" + "time" + + crdhelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + extensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver" + apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + kerrors "k8s.io/apimachinery/pkg/util/errors" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/util/retry" + "k8s.io/klog/v2" +) + +//go:embed *.yaml +var raw embed.FS + +// CreateFromFS creates the given CRDs using the target client from the +// provided filesystem and waits for it to become established. This call is blocking. +func CreateFromFS(ctx context.Context, client apiextensionsv1client.CustomResourceDefinitionInterface, fs embed.FS, grs ...metav1.GroupResource) error { + wg := sync.WaitGroup{} + bootstrapErrChan := make(chan error, len(grs)) + for _, gk := range grs { + wg.Add(1) + go func(gr metav1.GroupResource) { + defer wg.Done() + err := retryRetryableErrors(func() error { + return createSingleFromFS(ctx, client, gr, fs) + }) + // wait.Poll functions return ErrWaitTimeout instead the context cancellation error, for backward compatibility reasons, see: + // https://github.com/kubernetes/kubernetes/blob/b5f8cca701575678819b5e9e6372df989ab6799f/staging/src/k8s.io/apimachinery/pkg/util/wait/wait.go + // however, retryOnError swallows that error and replaces it for the last one, that is nil if it is still retrying, see: + // https://github.com/kubernetes/kubernetes/blob/ee81e5ebfad1b3f3c1112e7b83b0a5113286a3d3/pkg/client/unversioned/util.go + // if the context is cancelled, we have to inform the upper layers about that, so context error takes precedence. + if ctx.Err() != nil { + err = ctx.Err() + } + bootstrapErrChan <- err + }(gk) + } + wg.Wait() + close(bootstrapErrChan) + var bootstrapErrors []error + for err := range bootstrapErrChan { + bootstrapErrors = append(bootstrapErrors, err) + } + if err := kerrors.NewAggregate(bootstrapErrors); err != nil { + return fmt.Errorf("could not bootstrap CRDs: %w", err) + } + return nil +} + +// Create creates the given CRDs using the target client and waits +// for all of them to become established in parallel. This call is blocking. +func Create(ctx context.Context, client apiextensionsv1client.CustomResourceDefinitionInterface, grs ...metav1.GroupResource) error { + return CreateFromFS(ctx, client, raw, grs...) +} + +// CreateFromFS creates the given CRD using the target client from the +// provided filesystem and waits for it to become established. This call is blocking. +func createSingleFromFS(ctx context.Context, client apiextensionsv1client.CustomResourceDefinitionInterface, gr metav1.GroupResource, fs embed.FS) error { + crd, err := CRD(fs, gr) + if err != nil { + return err + } + + return CreateSingle(ctx, client, crd) +} + +// CRD returns an *apiextensionsv1.CustomResourceDefinition for the GroupResource specified by gr from fs. The embedded +// file's name must have the format _.yaml. +func CRD(fs embed.FS, gr metav1.GroupResource) (*apiextensionsv1.CustomResourceDefinition, error) { + raw, err := fs.ReadFile(fmt.Sprintf("%s_%s.yaml", gr.Group, gr.Resource)) + if err != nil { + return nil, fmt.Errorf("could not read CRD %s: %w", gr.String(), err) + } + + expectedGvk := &schema.GroupVersionKind{Group: apiextensionsv1.GroupName, Version: "v1", Kind: "CustomResourceDefinition"} + + obj, gvk, err := extensionsapiserver.Codecs.UniversalDeserializer().Decode(raw, expectedGvk, &apiextensionsv1.CustomResourceDefinition{}) + if err != nil { + return nil, fmt.Errorf("could not decode raw CRD %s: %w", gr.String(), err) + } + + if !equality.Semantic.DeepEqual(gvk, expectedGvk) { + return nil, fmt.Errorf("decoded CRD %s into incorrect GroupVersionKind, got %#v, wanted %#v", gr.String(), gvk, expectedGvk) + } + + crd, ok := obj.(*apiextensionsv1.CustomResourceDefinition) + if !ok { + return nil, fmt.Errorf("decoded CRD %s into incorrect type, got %T, wanted %T", gr.String(), obj, &apiextensionsv1.CustomResourceDefinition{}) + } + + return crd, nil +} + +func CreateSingle(ctx context.Context, client apiextensionsv1client.CustomResourceDefinitionInterface, rawCRD *apiextensionsv1.CustomResourceDefinition) error { + start := time.Now() + klog.V(4).Infof("Bootstrapping %v", rawCRD.Name) + + updateNeeded := false + crd, err := client.Get(ctx, rawCRD.Name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + crd, err = client.Create(ctx, rawCRD, metav1.CreateOptions{}) + if err != nil { + // If multiple post-start hooks specify the same CRD, they could race with each other, so we need to + // handle the scenario where another hook created this CRD after our Get() call returned not found. + if apierrors.IsAlreadyExists(err) { + // Re-get so we have the correct resourceVersion + crd, err = client.Get(ctx, rawCRD.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("error getting CRD %s: %w", rawCRD.Name, err) + } + updateNeeded = true + } else { + return fmt.Errorf("error creating CRD %s: %w", rawCRD.Name, err) + } + } else { + klog.Infof("Bootstrapped CRD %v after %s", crd.Name, time.Since(start).String()) + } + } else { + return fmt.Errorf("error fetching CRD %s: %w", rawCRD.Name, err) + } + } else { + updateNeeded = true + } + + if updateNeeded { + rawCRD.ResourceVersion = crd.ResourceVersion + _, err := client.Update(ctx, rawCRD, metav1.UpdateOptions{}) + if err != nil { + return err + } + klog.Infof("Updated CRD %v after %s", rawCRD.Name, time.Since(start).String()) + } + + return wait.PollImmediateInfiniteWithContext(ctx, 100*time.Millisecond, func(ctx context.Context) (bool, error) { + crd, err := client.Get(ctx, rawCRD.Name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return false, fmt.Errorf("CRD %s was deleted before being established", rawCRD.Name) + } + return false, fmt.Errorf("error fetching CRD %s: %w", rawCRD.Name, err) + } + + return crdhelpers.IsCRDConditionTrue(crd, apiextensionsv1.Established), nil + }) +} + +func retryRetryableErrors(f func() error) error { + return retry.OnError(retry.DefaultBackoff, func(err error) bool { + return utilnet.IsConnectionRefused(err) || apierrors.IsTooManyRequests(err) || apierrors.IsConflict(err) + }, f) +} diff --git a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml b/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml similarity index 99% rename from deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml rename to contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml index b828768e5..dca2f41cd 100644 --- a/deploy/crd/kube-bind.io_apiserviceexporttemplates.yaml +++ b/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml @@ -5,9 +5,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null - name: apiserviceexporttemplates.kube-bind.io + name: apiserviceexporttemplates.example.com spec: - group: kube-bind.io + group: example.com names: categories: - kube-bindings diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go b/contrib/example-backend/apis/examplebackend/v1alpha1/apiserviceexporttemplate_types.go similarity index 94% rename from pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go rename to contrib/example-backend/apis/examplebackend/v1alpha1/apiserviceexporttemplate_types.go index db0e00c73..60e05bb2d 100644 --- a/pkg/apis/kubebind/v1alpha1/apiserviceexporttemplate_types.go +++ b/contrib/example-backend/apis/examplebackend/v1alpha1/apiserviceexporttemplate_types.go @@ -18,6 +18,8 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" ) // APIServiceExportTemplate specifies the resource to be exported. @@ -47,7 +49,7 @@ type APIServiceExportTemplate struct { type APIServiceExportTemplateSpec struct { APIServiceSelector APIServiceSelector `json:"APIServiceSelector"` // +optional - PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` + PermissionClaims []v1alpha1.PermissionClaim `json:"permissionClaims,omitempty"` } type APIServiceExportTemplateStatus struct{} diff --git a/contrib/example-backend/apis/examplebackend/v1alpha1/doc.go b/contrib/example-backend/apis/examplebackend/v1alpha1/doc.go new file mode 100644 index 000000000..7dea12136 --- /dev/null +++ b/contrib/example-backend/apis/examplebackend/v1alpha1/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2023 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 v1alpha1 defines the v1alpha1 version of the Example Backend API +// +// +groupName=example.com +// +groupGoName=ExampleBackend +// +k8s:deepcopy-gen=package,register +// +kubebuilder:validation:Optional +package v1alpha1 diff --git a/contrib/example-backend/apis/examplebackend/v1alpha1/register.go b/contrib/example-backend/apis/examplebackend/v1alpha1/register.go new file mode 100644 index 000000000..302da5d0a --- /dev/null +++ b/contrib/example-backend/apis/examplebackend/v1alpha1/register.go @@ -0,0 +1,55 @@ +/* +Copyright 2023 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +const ( + // GroupName is the group name used in this package + GroupName = "example.com" + + // GroupVersion is the group version used in this package + GroupVersion = "v1alpha1" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &APIServiceExportTemplate{}, + &APIServiceExportTemplateList{}, + ) + + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/contrib/example-backend/apis/examplebackend/v1alpha1/zz_generated.deepcopy.go b/contrib/example-backend/apis/examplebackend/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..5b23c5f21 --- /dev/null +++ b/contrib/example-backend/apis/examplebackend/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,150 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +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 deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" + + kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" +) + +// 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) + out.Status = in.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 *APIServiceExportTemplateSpec) DeepCopyInto(out *APIServiceExportTemplateSpec) { + *out = *in + in.APIServiceSelector.DeepCopyInto(&out.APIServiceSelector) + if in.PermissionClaims != nil { + in, out := &in.PermissionClaims, &out.PermissionClaims + *out = make([]kubebindv1alpha1.PermissionClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + 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 + 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 *APIServiceSelector) DeepCopyInto(out *APIServiceSelector) { + *out = *in + if in.Versions != nil { + in, out := &in.Versions, &out.Versions + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceSelector. +func (in *APIServiceSelector) DeepCopy() *APIServiceSelector { + if in == nil { + return nil + } + out := new(APIServiceSelector) + in.DeepCopyInto(out) + return out +} diff --git a/contrib/example-backend/client/clientset/versioned/clientset.go b/contrib/example-backend/client/clientset/versioned/clientset.go new file mode 100644 index 000000000..85fdb4669 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/clientset.go @@ -0,0 +1,122 @@ +/* +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 versioned + +import ( + "fmt" + "net/http" + + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" + + examplebackendv1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + ExampleBackendV1alpha1() examplebackendv1alpha1.ExampleBackendV1alpha1Interface +} + +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. +type Clientset struct { + *discovery.DiscoveryClient + exampleBackendV1alpha1 *examplebackendv1alpha1.ExampleBackendV1alpha1Client +} + +// ExampleBackendV1alpha1 retrieves the ExampleBackendV1alpha1Client +func (c *Clientset) ExampleBackendV1alpha1() examplebackendv1alpha1.ExampleBackendV1alpha1Interface { + return c.exampleBackendV1alpha1 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + // share the transport between all clients + httpClient, err := rest.HTTPClientFor(&configShallowCopy) + if err != nil { + return nil, err + } + + return NewForConfigAndClient(&configShallowCopy, httpClient) +} + +// NewForConfigAndClient creates a new Clientset for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. +func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + + var cs Clientset + var err error + cs.exampleBackendV1alpha1, err = examplebackendv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *Clientset { + cs, err := NewForConfig(c) + if err != nil { + panic(err) + } + return cs +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.exampleBackendV1alpha1 = examplebackendv1alpha1.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/contrib/example-backend/client/clientset/versioned/doc.go b/contrib/example-backend/client/clientset/versioned/doc.go new file mode 100644 index 000000000..10f8b80ef --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/doc.go @@ -0,0 +1,20 @@ +/* +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. + +// This package has the automatically generated clientset. +package versioned diff --git a/contrib/example-backend/client/clientset/versioned/fake/clientset_generated.go b/contrib/example-backend/client/clientset/versioned/fake/clientset_generated.go new file mode 100644 index 000000000..e7e88c819 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,86 @@ +/* +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 ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" + + clientset "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned" + examplebackendv1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1" + fakeexamplebackendv1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) + +// ExampleBackendV1alpha1 retrieves the ExampleBackendV1alpha1Client +func (c *Clientset) ExampleBackendV1alpha1() examplebackendv1alpha1.ExampleBackendV1alpha1Interface { + return &fakeexamplebackendv1alpha1.FakeExampleBackendV1alpha1{Fake: &c.Fake} +} diff --git a/contrib/example-backend/client/clientset/versioned/fake/doc.go b/contrib/example-backend/client/clientset/versioned/fake/doc.go new file mode 100644 index 000000000..ca4191ad7 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/fake/doc.go @@ -0,0 +1,20 @@ +/* +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. + +// This package has the automatically generated fake clientset. +package fake diff --git a/contrib/example-backend/client/clientset/versioned/fake/register.go b/contrib/example-backend/client/clientset/versioned/fake/register.go new file mode 100644 index 000000000..67292a373 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/fake/register.go @@ -0,0 +1,57 @@ +/* +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 ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + examplebackendv1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +var localSchemeBuilder = runtime.SchemeBuilder{ + examplebackendv1alpha1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/contrib/example-backend/client/clientset/versioned/scheme/doc.go b/contrib/example-backend/client/clientset/versioned/scheme/doc.go new file mode 100644 index 000000000..b5ec927c6 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/scheme/doc.go @@ -0,0 +1,20 @@ +/* +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. + +// This package contains the scheme of the automatically generated clientset. +package scheme diff --git a/contrib/example-backend/client/clientset/versioned/scheme/register.go b/contrib/example-backend/client/clientset/versioned/scheme/register.go new file mode 100644 index 000000000..5629c7885 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/scheme/register.go @@ -0,0 +1,57 @@ +/* +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 scheme + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + examplebackendv1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" +) + +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + examplebackendv1alpha1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(Scheme)) +} diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/apiserviceexporttemplate.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/apiserviceexporttemplate.go similarity index 96% rename from pkg/client/clientset/versioned/typed/kubebind/v1alpha1/apiserviceexporttemplate.go rename to contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/apiserviceexporttemplate.go index 62a0172a4..9465b30b8 100644 --- a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/apiserviceexporttemplate.go +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/apiserviceexporttemplate.go @@ -27,8 +27,8 @@ import ( watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" - v1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" - scheme "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned/scheme" + v1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" + scheme "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned/scheme" ) // APIServiceExportTemplatesGetter has a method to return a APIServiceExportTemplateInterface. @@ -58,7 +58,7 @@ type aPIServiceExportTemplates struct { } // newAPIServiceExportTemplates returns a APIServiceExportTemplates -func newAPIServiceExportTemplates(c *KubeBindV1alpha1Client, namespace string) *aPIServiceExportTemplates { +func newAPIServiceExportTemplates(c *ExampleBackendV1alpha1Client, namespace string) *aPIServiceExportTemplates { return &aPIServiceExportTemplates{ client: c.RESTClient(), ns: namespace, diff --git a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/doc.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/doc.go new file mode 100644 index 000000000..05ebc8af6 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +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. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/examplebackend_client.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/examplebackend_client.go new file mode 100644 index 000000000..49ff92a75 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/examplebackend_client.go @@ -0,0 +1,108 @@ +/* +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 v1alpha1 + +import ( + "net/http" + + rest "k8s.io/client-go/rest" + + v1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" + "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned/scheme" +) + +type ExampleBackendV1alpha1Interface interface { + RESTClient() rest.Interface + APIServiceExportTemplatesGetter +} + +// ExampleBackendV1alpha1Client is used to interact with features provided by the example.com group. +type ExampleBackendV1alpha1Client struct { + restClient rest.Interface +} + +func (c *ExampleBackendV1alpha1Client) APIServiceExportTemplates(namespace string) APIServiceExportTemplateInterface { + return newAPIServiceExportTemplates(c, namespace) +} + +// NewForConfig creates a new ExampleBackendV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*ExampleBackendV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new ExampleBackendV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*ExampleBackendV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &ExampleBackendV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new ExampleBackendV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *ExampleBackendV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new ExampleBackendV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *ExampleBackendV1alpha1Client { + return &ExampleBackendV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *ExampleBackendV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/doc.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/doc.go new file mode 100644 index 000000000..a0ee34800 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +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 has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_apiserviceexporttemplate.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_apiserviceexporttemplate.go similarity index 95% rename from pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_apiserviceexporttemplate.go rename to contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_apiserviceexporttemplate.go index 3e8daa7ec..6c6755ddf 100644 --- a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_apiserviceexporttemplate.go +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_apiserviceexporttemplate.go @@ -28,18 +28,18 @@ import ( watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" - v1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" + v1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" ) // FakeAPIServiceExportTemplates implements APIServiceExportTemplateInterface type FakeAPIServiceExportTemplates struct { - Fake *FakeKubeBindV1alpha1 + Fake *FakeExampleBackendV1alpha1 ns string } -var apiserviceexporttemplatesResource = schema.GroupVersionResource{Group: "kube-bind.io", Version: "v1alpha1", Resource: "apiserviceexporttemplates"} +var apiserviceexporttemplatesResource = schema.GroupVersionResource{Group: "example.com", Version: "v1alpha1", Resource: "apiserviceexporttemplates"} -var apiserviceexporttemplatesKind = schema.GroupVersionKind{Group: "kube-bind.io", Version: "v1alpha1", Kind: "APIServiceExportTemplate"} +var apiserviceexporttemplatesKind = schema.GroupVersionKind{Group: "example.com", Version: "v1alpha1", Kind: "APIServiceExportTemplate"} // Get takes name of the aPIServiceExportTemplate, and returns the corresponding aPIServiceExportTemplate object, and an error if there is any. func (c *FakeAPIServiceExportTemplates) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { diff --git a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_examplebackend_client.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_examplebackend_client.go new file mode 100644 index 000000000..4b1d95374 --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_examplebackend_client.go @@ -0,0 +1,41 @@ +/* +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 ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + + v1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1" +) + +type FakeExampleBackendV1alpha1 struct { + *testing.Fake +} + +func (c *FakeExampleBackendV1alpha1) APIServiceExportTemplates(namespace string) v1alpha1.APIServiceExportTemplateInterface { + return &FakeAPIServiceExportTemplates{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeExampleBackendV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/generated_expansion.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/generated_expansion.go new file mode 100644 index 000000000..e9426f91c --- /dev/null +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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 v1alpha1 + +type APIServiceExportTemplateExpansion interface{} diff --git a/contrib/example-backend/client/informers/externalversions/examplebackend/interface.go b/contrib/example-backend/client/informers/externalversions/examplebackend/interface.go new file mode 100644 index 000000000..5c6fb8ecb --- /dev/null +++ b/contrib/example-backend/client/informers/externalversions/examplebackend/interface.go @@ -0,0 +1,46 @@ +/* +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 examplebackend + +import ( + v1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/client/informers/externalversions/examplebackend/v1alpha1" + internalinterfaces "github.com/kube-bind/kube-bind/contrib/example-backend/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/kubebind/v1alpha1/apiserviceexporttemplate.go b/contrib/example-backend/client/informers/externalversions/examplebackend/v1alpha1/apiserviceexporttemplate.go similarity index 80% rename from pkg/client/informers/externalversions/kubebind/v1alpha1/apiserviceexporttemplate.go rename to contrib/example-backend/client/informers/externalversions/examplebackend/v1alpha1/apiserviceexporttemplate.go index 83c7b410b..929074642 100644 --- a/pkg/client/informers/externalversions/kubebind/v1alpha1/apiserviceexporttemplate.go +++ b/contrib/example-backend/client/informers/externalversions/examplebackend/v1alpha1/apiserviceexporttemplate.go @@ -27,10 +27,10 @@ import ( watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" - kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" - versioned "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned" - internalinterfaces "github.com/kube-bind/kube-bind/pkg/client/informers/externalversions/internalinterfaces" - v1alpha1 "github.com/kube-bind/kube-bind/pkg/client/listers/kubebind/v1alpha1" + examplebackendv1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" + versioned "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned" + internalinterfaces "github.com/kube-bind/kube-bind/contrib/example-backend/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/client/listers/examplebackend/v1alpha1" ) // APIServiceExportTemplateInformer provides access to a shared informer and lister for @@ -63,16 +63,16 @@ func NewFilteredAPIServiceExportTemplateInformer(client versioned.Interface, nam if tweakListOptions != nil { tweakListOptions(&options) } - return client.KubeBindV1alpha1().APIServiceExportTemplates(namespace).List(context.TODO(), options) + return client.ExampleBackendV1alpha1().APIServiceExportTemplates(namespace).List(context.TODO(), options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.KubeBindV1alpha1().APIServiceExportTemplates(namespace).Watch(context.TODO(), options) + return client.ExampleBackendV1alpha1().APIServiceExportTemplates(namespace).Watch(context.TODO(), options) }, }, - &kubebindv1alpha1.APIServiceExportTemplate{}, + &examplebackendv1alpha1.APIServiceExportTemplate{}, resyncPeriod, indexers, ) @@ -83,7 +83,7 @@ func (f *aPIServiceExportTemplateInformer) defaultInformer(client versioned.Inte } func (f *aPIServiceExportTemplateInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&kubebindv1alpha1.APIServiceExportTemplate{}, f.defaultInformer) + return f.factory.InformerFor(&examplebackendv1alpha1.APIServiceExportTemplate{}, f.defaultInformer) } func (f *aPIServiceExportTemplateInformer) Lister() v1alpha1.APIServiceExportTemplateLister { diff --git a/contrib/example-backend/client/informers/externalversions/examplebackend/v1alpha1/interface.go b/contrib/example-backend/client/informers/externalversions/examplebackend/v1alpha1/interface.go new file mode 100644 index 000000000..90b89d67a --- /dev/null +++ b/contrib/example-backend/client/informers/externalversions/examplebackend/v1alpha1/interface.go @@ -0,0 +1,45 @@ +/* +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 v1alpha1 + +import ( + internalinterfaces "github.com/kube-bind/kube-bind/contrib/example-backend/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // APIServiceExportTemplates returns a APIServiceExportTemplateInformer. + APIServiceExportTemplates() APIServiceExportTemplateInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// APIServiceExportTemplates returns a APIServiceExportTemplateInformer. +func (v *version) APIServiceExportTemplates() APIServiceExportTemplateInformer { + return &aPIServiceExportTemplateInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/contrib/example-backend/client/informers/externalversions/factory.go b/contrib/example-backend/client/informers/externalversions/factory.go new file mode 100644 index 000000000..6f8288421 --- /dev/null +++ b/contrib/example-backend/client/informers/externalversions/factory.go @@ -0,0 +1,181 @@ +/* +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 externalversions + +import ( + reflect "reflect" + sync "sync" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + + versioned "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned" + examplebackend "github.com/kube-bind/kube-bind/contrib/example-backend/client/informers/externalversions/examplebackend" + internalinterfaces "github.com/kube-bind/kube-bind/contrib/example-backend/client/informers/externalversions/internalinterfaces" +) + +// SharedInformerOption defines the functional option type for SharedInformerFactory. +type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory + +type sharedInformerFactory struct { + client versioned.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration + customResync map[reflect.Type]time.Duration + + informers map[reflect.Type]cache.SharedIndexInformer + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[reflect.Type]bool +} + +// WithCustomResyncConfig sets a custom resync period for the specified informer types. +func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + for k, v := range resyncConfig { + factory.customResync[reflect.TypeOf(k)] = v + } + return factory + } +} + +// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. +func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.tweakListOptions = tweakListOptions + return factory + } +} + +// WithNamespace limits the SharedInformerFactory to the specified namespace. +func WithNamespace(namespace string) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.namespace = namespace + return factory + } +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. +func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +// Deprecated: Please use NewSharedInformerFactoryWithOptions instead +func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) +} + +// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. +func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { + factory := &sharedInformerFactory{ + client: client, + namespace: v1.NamespaceAll, + defaultResync: defaultResync, + informers: make(map[reflect.Type]cache.SharedIndexInformer), + startedInformers: make(map[reflect.Type]bool), + customResync: make(map[reflect.Type]time.Duration), + } + + // Apply all options + for _, opt := range options { + factory = opt(factory) + } + + return factory +} + +// Start initializes all requested informers. +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.lock.Lock() + defer f.lock.Unlock() + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + go informer.Run(stopCh) + f.startedInformers[informerType] = true + } + } +} + +// WaitForCacheSync waits for all started informers' cache were synced. +func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + informers := func() map[reflect.Type]cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[reflect.Type]cache.SharedIndexInformer{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + res := map[reflect.Type]bool{} + for informType, informer := range informers { + res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + } + return res +} + +// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + + resyncPeriod, exists := f.customResync[informerType] + if !exists { + resyncPeriod = f.defaultResync + } + + informer = newFunc(f.client, resyncPeriod) + f.informers[informerType] = informer + + return informer +} + +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +type SharedInformerFactory interface { + internalinterfaces.SharedInformerFactory + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + + ExampleBackend() examplebackend.Interface +} + +func (f *sharedInformerFactory) ExampleBackend() examplebackend.Interface { + return examplebackend.New(f, f.namespace, f.tweakListOptions) +} diff --git a/contrib/example-backend/client/informers/externalversions/generic.go b/contrib/example-backend/client/informers/externalversions/generic.go new file mode 100644 index 000000000..6fa70cab5 --- /dev/null +++ b/contrib/example-backend/client/informers/externalversions/generic.go @@ -0,0 +1,63 @@ +/* +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 externalversions + +import ( + "fmt" + + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + + v1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" +) + +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() cache.SharedIndexInformer + Lister() cache.GenericLister +} + +type genericInformer struct { + informer cache.SharedIndexInformer + resource schema.GroupResource +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() cache.GenericLister { + return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) +} + +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { + switch resource { + // Group=example.com, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("apiserviceexporttemplates"): + return &genericInformer{resource: resource.GroupResource(), informer: f.ExampleBackend().V1alpha1().APIServiceExportTemplates().Informer()}, nil + + } + + return nil, fmt.Errorf("no informer found for %v", resource) +} diff --git a/contrib/example-backend/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/contrib/example-backend/client/informers/externalversions/internalinterfaces/factory_interfaces.go new file mode 100644 index 000000000..7d649b9ec --- /dev/null +++ b/contrib/example-backend/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -0,0 +1,41 @@ +/* +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 internalinterfaces + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" + + versioned "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned" +) + +// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. +type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer +} + +// TweakListOptionsFunc is a function that transforms a v1.ListOptions. +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/pkg/client/listers/kubebind/v1alpha1/apiserviceexporttemplate.go b/contrib/example-backend/client/listers/examplebackend/v1alpha1/apiserviceexporttemplate.go similarity index 97% rename from pkg/client/listers/kubebind/v1alpha1/apiserviceexporttemplate.go rename to contrib/example-backend/client/listers/examplebackend/v1alpha1/apiserviceexporttemplate.go index 0abe5c922..52c9351df 100644 --- a/pkg/client/listers/kubebind/v1alpha1/apiserviceexporttemplate.go +++ b/contrib/example-backend/client/listers/examplebackend/v1alpha1/apiserviceexporttemplate.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" - v1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" + v1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" ) // APIServiceExportTemplateLister helps list APIServiceExportTemplates. diff --git a/contrib/example-backend/client/listers/examplebackend/v1alpha1/expansion_generated.go b/contrib/example-backend/client/listers/examplebackend/v1alpha1/expansion_generated.go new file mode 100644 index 000000000..0fbccdc2a --- /dev/null +++ b/contrib/example-backend/client/listers/examplebackend/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +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 v1alpha1 + +// APIServiceExportTemplateListerExpansion allows custom methods to be added to +// APIServiceExportTemplateLister. +type APIServiceExportTemplateListerExpansion interface{} + +// APIServiceExportTemplateNamespaceListerExpansion allows custom methods to be added to +// APIServiceExportTemplateNamespaceLister. +type APIServiceExportTemplateNamespaceListerExpansion interface{} diff --git a/contrib/example-backend/exporttemplate/index.go b/contrib/example-backend/exporttemplate/index.go index f996a9cd2..74c1a68ad 100644 --- a/contrib/example-backend/exporttemplate/index.go +++ b/contrib/example-backend/exporttemplate/index.go @@ -28,8 +28,8 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" - kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" - templates "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned" + "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" + templates "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned" ) type Index struct { @@ -54,7 +54,7 @@ func (i Index) GetExported(ctx context.Context) ([]apiextensionsv1.CustomResourc if err != nil { return nil, err } - exports, err := i.templates.KubeBindV1alpha1().APIServiceExportTemplates(i.clusterNs).List(ctx, v1.ListOptions{}) + exports, err := i.templates.ExampleBackendV1alpha1().APIServiceExportTemplates(i.clusterNs).List(ctx, v1.ListOptions{}) if err != nil { return nil, err } @@ -77,10 +77,10 @@ func (i Index) GetExported(ctx context.Context) ([]apiextensionsv1.CustomResourc return exported, nil } -func (i Index) TemplateFor(ctx context.Context, group, resource string) (kubebindv1alpha1.APIServiceExportTemplate, error) { - exports, err := i.templates.KubeBindV1alpha1().APIServiceExportTemplates(i.clusterNs).List(ctx, v1.ListOptions{}) +func (i Index) TemplateFor(ctx context.Context, group, resource string) (v1alpha1.APIServiceExportTemplate, error) { + exports, err := i.templates.ExampleBackendV1alpha1().APIServiceExportTemplates(i.clusterNs).List(ctx, v1.ListOptions{}) if err != nil { - return kubebindv1alpha1.APIServiceExportTemplate{}, nil + return v1alpha1.APIServiceExportTemplate{}, nil } for _, e := range exports.Items { @@ -89,5 +89,5 @@ func (i Index) TemplateFor(ctx context.Context, group, resource string) (kubebin } } - return kubebindv1alpha1.APIServiceExportTemplate{}, fmt.Errorf("not found: %s/%s", group, resource) + return v1alpha1.APIServiceExportTemplate{}, fmt.Errorf("not found: %s/%s", group, resource) } diff --git a/hack/update-codegen-clients.sh b/hack/update-codegen-clients.sh index ae33d3520..50f01a258 100755 --- a/hack/update-codegen-clients.sh +++ b/hack/update-codegen-clients.sh @@ -30,3 +30,10 @@ bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \ --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate/boilerplate.generatego.txt \ --output-base "${SCRIPT_ROOT}" \ --trim-path-prefix github.com/kube-bind/kube-bind + +bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \ + github.com/kube-bind/kube-bind/contrib/example-backend/client github.com/kube-bind/kube-bind/contrib/example-backend/apis \ + "examplebackend:v1alpha1" \ + --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate/boilerplate.generatego.txt \ + --output-base "${SCRIPT_ROOT}" \ + --trim-path-prefix github.com/kube-bind/kube-bind diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 783472d67..0a852adc3 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -36,6 +36,17 @@ cd pkg/apis output:crd:artifacts:config=../../deploy/crd cd - +# Update generated CRD YAML +cd contrib/example-backend/apis +../../../${CONTROLLER_GEN} \ + crd \ + rbac:roleName=manager-role \ + webhook \ + paths="./..." \ + output:crd:artifacts:config=../../../contrib/deploy/crd +cd - + + cd deploy/crd for CRD in *.yaml; do if [ -f "../patches/${CRD}-patch" ]; then diff --git a/pkg/apis/kubebind/v1alpha1/register.go b/pkg/apis/kubebind/v1alpha1/register.go index 1db69012d..40cc76723 100644 --- a/pkg/apis/kubebind/v1alpha1/register.go +++ b/pkg/apis/kubebind/v1alpha1/register.go @@ -48,8 +48,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &APIServiceExport{}, &APIServiceExportList{}, - &APIServiceExportTemplate{}, - &APIServiceExportTemplateList{}, &APIServiceBinding{}, &APIServiceBindingList{}, &APIServiceExportRequest{}, diff --git a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go index ddb90ccf7..8132985fb 100644 --- a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go @@ -460,107 +460,6 @@ 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) - out.Status = in.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 *APIServiceExportTemplateSpec) DeepCopyInto(out *APIServiceExportTemplateSpec) { - *out = *in - in.APIServiceSelector.DeepCopyInto(&out.APIServiceSelector) - if in.PermissionClaims != nil { - in, out := &in.PermissionClaims, &out.PermissionClaims - *out = make([]PermissionClaim, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - 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 - 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 *APIServiceExportVersion) DeepCopyInto(out *APIServiceExportVersion) { *out = *in @@ -682,27 +581,6 @@ func (in *APIServiceNamespaceStatus) DeepCopy() *APIServiceNamespaceStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIServiceSelector) DeepCopyInto(out *APIServiceSelector) { - *out = *in - if in.Versions != nil { - in, out := &in.Versions, &out.Versions - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIServiceSelector. -func (in *APIServiceSelector) DeepCopy() *APIServiceSelector { - if in == nil { - return nil - } - out := new(APIServiceSelector) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AcceptablePermissionClaim) DeepCopyInto(out *AcceptablePermissionClaim) { *out = *in diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_kubebind_client.go b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_kubebind_client.go index 80d3a4713..c90a8d66f 100644 --- a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_kubebind_client.go +++ b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/fake/fake_kubebind_client.go @@ -41,10 +41,6 @@ func (c *FakeKubeBindV1alpha1) APIServiceExportRequests(namespace string) v1alph return &FakeAPIServiceExportRequests{c, namespace} } -func (c *FakeKubeBindV1alpha1) APIServiceExportTemplates(namespace string) v1alpha1.APIServiceExportTemplateInterface { - return &FakeAPIServiceExportTemplates{c, namespace} -} - func (c *FakeKubeBindV1alpha1) APIServiceNamespaces(namespace string) v1alpha1.APIServiceNamespaceInterface { return &FakeAPIServiceNamespaces{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/generated_expansion.go index cc6d3f06d..3b60d85b6 100644 --- a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/generated_expansion.go @@ -24,8 +24,6 @@ type APIServiceExportExpansion interface{} type APIServiceExportRequestExpansion interface{} -type APIServiceExportTemplateExpansion interface{} - type APIServiceNamespaceExpansion interface{} type ClusterBindingExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/kubebind_client.go b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/kubebind_client.go index 8bf1c6a14..93bdcc882 100644 --- a/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/kubebind_client.go +++ b/pkg/client/clientset/versioned/typed/kubebind/v1alpha1/kubebind_client.go @@ -32,7 +32,6 @@ type KubeBindV1alpha1Interface interface { APIServiceBindingsGetter APIServiceExportsGetter APIServiceExportRequestsGetter - APIServiceExportTemplatesGetter APIServiceNamespacesGetter ClusterBindingsGetter } @@ -54,10 +53,6 @@ func (c *KubeBindV1alpha1Client) APIServiceExportRequests(namespace string) APIS return newAPIServiceExportRequests(c, namespace) } -func (c *KubeBindV1alpha1Client) APIServiceExportTemplates(namespace string) APIServiceExportTemplateInterface { - return newAPIServiceExportTemplates(c, namespace) -} - func (c *KubeBindV1alpha1Client) APIServiceNamespaces(namespace string) APIServiceNamespaceInterface { return newAPIServiceNamespaces(c, namespace) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 2e7f684e0..0a4c24562 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -60,8 +60,6 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().APIServiceExports().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("apiserviceexportrequests"): return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().APIServiceExportRequests().Informer()}, nil - case v1alpha1.SchemeGroupVersion.WithResource("apiserviceexporttemplates"): - return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().APIServiceExportTemplates().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("apiservicenamespaces"): return &genericInformer{resource: resource.GroupResource(), informer: f.KubeBind().V1alpha1().APIServiceNamespaces().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("clusterbindings"): diff --git a/pkg/client/informers/externalversions/kubebind/v1alpha1/interface.go b/pkg/client/informers/externalversions/kubebind/v1alpha1/interface.go index b0ef4dc95..22ed7eeda 100644 --- a/pkg/client/informers/externalversions/kubebind/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/kubebind/v1alpha1/interface.go @@ -30,8 +30,6 @@ type Interface interface { APIServiceExports() APIServiceExportInformer // APIServiceExportRequests returns a APIServiceExportRequestInformer. APIServiceExportRequests() APIServiceExportRequestInformer - // APIServiceExportTemplates returns a APIServiceExportTemplateInformer. - APIServiceExportTemplates() APIServiceExportTemplateInformer // APIServiceNamespaces returns a APIServiceNamespaceInformer. APIServiceNamespaces() APIServiceNamespaceInformer // ClusterBindings returns a ClusterBindingInformer. @@ -64,11 +62,6 @@ func (v *version) APIServiceExportRequests() APIServiceExportRequestInformer { return &aPIServiceExportRequestInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } -// APIServiceExportTemplates returns a APIServiceExportTemplateInformer. -func (v *version) APIServiceExportTemplates() APIServiceExportTemplateInformer { - return &aPIServiceExportTemplateInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} - // APIServiceNamespaces returns a APIServiceNamespaceInformer. func (v *version) APIServiceNamespaces() APIServiceNamespaceInformer { return &aPIServiceNamespaceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/listers/kubebind/v1alpha1/expansion_generated.go b/pkg/client/listers/kubebind/v1alpha1/expansion_generated.go index d17725677..3cdf7aed8 100644 --- a/pkg/client/listers/kubebind/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/kubebind/v1alpha1/expansion_generated.go @@ -38,14 +38,6 @@ type APIServiceExportRequestListerExpansion interface{} // APIServiceExportRequestNamespaceLister. type APIServiceExportRequestNamespaceListerExpansion interface{} -// APIServiceExportTemplateListerExpansion allows custom methods to be added to -// APIServiceExportTemplateLister. -type APIServiceExportTemplateListerExpansion interface{} - -// APIServiceExportTemplateNamespaceListerExpansion allows custom methods to be added to -// APIServiceExportTemplateNamespaceLister. -type APIServiceExportTemplateNamespaceListerExpansion interface{} - // APIServiceNamespaceListerExpansion allows custom methods to be added to // APIServiceNamespaceLister. type APIServiceNamespaceListerExpansion interface{} diff --git a/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml index b6445686d..4a38b9dda 100644 --- a/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml +++ b/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml @@ -1,5 +1,5 @@ kind: APIServiceExportTemplate -apiVersion: kube-bind.io/v1alpha1 +apiVersion: example.com/v1alpha1 metadata: name: "mangodbs" namespace: default diff --git a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml index 4eddc74ce..bfbccd344 100644 --- a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml +++ b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml @@ -1,5 +1,5 @@ kind: APIServiceExportTemplate -apiVersion: kube-bind.io/v1alpha1 +apiVersion: example.com/v1alpha1 metadata: name: "mangodbs" namespace: default diff --git a/test/e2e/framework/backend.go b/test/e2e/framework/backend.go index f7b932aff..a85e29474 100644 --- a/test/e2e/framework/backend.go +++ b/test/e2e/framework/backend.go @@ -35,7 +35,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" + backendcrd "github.com/kube-bind/kube-bind/contrib/deploy/crd" backend "github.com/kube-bind/kube-bind/contrib/example-backend" + backendv1alpha1 "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" "github.com/kube-bind/kube-bind/contrib/example-backend/options" "github.com/kube-bind/kube-bind/deploy/crd" kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" @@ -67,7 +69,12 @@ func StartBackendWithoutDefaultArgs(t *testing.T, clientConfig *rest.Config, arg metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiserviceexports"}, metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiservicenamespaces"}, metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiserviceexportrequests"}, - metav1.GroupResource{Group: kubebindv1alpha1.GroupName, Resource: "apiserviceexporttemplates"}, + ) + require.NoError(t, err) + + err = backendcrd.Create(ctx, + crdClient.ApiextensionsV1().CustomResourceDefinitions(), + metav1.GroupResource{Group: backendv1alpha1.GroupName, Resource: "apiserviceexporttemplates"}, ) require.NoError(t, err) From 9413089329ed0f98c8114f0b1be9c36d64d64ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 22 Jun 2023 10:43:51 +0200 Subject: [PATCH 16/26] Fix bug when reconciling claimed resources Name conflicts caused a panic, this has been converted to an error and handled Additional protections agains memory aliasing when crating RBAC for claimed resources --- .../clusterbinding_reconcile.go | 2 ++ .../claimedresources_controller.go | 2 +- .../claimedresources_reconciler.go | 23 +++++++++++-------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go b/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go index e4a4d937a..6fe7cd8af 100644 --- a/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go +++ b/contrib/example-backend/controllers/clusterbinding/clusterbinding_reconcile.go @@ -148,12 +148,14 @@ func (r *reconciler) ensureRBACClusterRole(ctx context.Context, clusterBinding * }, } for _, export := range exports { + export := export expected.Rules = append(expected.Rules, rbacv1.PolicyRule{ APIGroups: []string{export.Spec.Group}, Resources: []string{export.Spec.Names.Plural}, Verbs: []string{"get", "list", "watch", "update", "patch", "delete", "create"}, }) for _, e := range export.Spec.PermissionClaims { + e := e // TODO more fine grained filtering expected.Rules = append(expected.Rules, rbacv1.PolicyRule{ diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go index a10d7738a..5c7665392 100644 --- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go @@ -58,7 +58,7 @@ func NewController( ) (*controller, error) { queue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerName) - logger := klog.Background().WithValues("controller", controllerName) + logger := klog.Background().WithValues("controller", controllerName, "gvr", gvr) providerConfig = rest.CopyConfig(providerConfig) providerConfig = rest.AddUserAgent(providerConfig, controllerName) diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go index 3b9979b57..642c791af 100644 --- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go @@ -18,6 +18,7 @@ package claimedresources import ( "context" + "fmt" "reflect" "k8s.io/apimachinery/pkg/api/equality" @@ -86,7 +87,11 @@ func (r *readReconciler) reconcile(ctx context.Context, providerNS, name string) } // Determine owner - owner := determineOwner(providerObj, consumerObj) + owner, err := determineOwner(providerObj, consumerObj) + if err != nil { // nothing we can do + logger.Error(err, "could not determine owner") + return nil + } logger = logger.WithValues("owner", owner) switch owner { @@ -210,17 +215,17 @@ func candidateFromOwnerObj(downstreamNS string, obj *unstructured.Unstructured) // determineOwner determines the owner of a resource given at least one object exists either on the // consumer or provider side -func determineOwner(providerObj, consumerObj *unstructured.Unstructured) kubebindv1alpha1.Owner { +func determineOwner(providerObj, consumerObj *unstructured.Unstructured) (kubebindv1alpha1.Owner, error) { if providerObj != nil { ownerAnn := providerObj.GetAnnotations()[annotation] switch ownerAnn { case "Provider": - return kubebindv1alpha1.Provider + return kubebindv1alpha1.Provider, nil case "Consumer": - return kubebindv1alpha1.Consumer + return kubebindv1alpha1.Consumer, nil } if ownerAnn == "" && consumerObj == nil { - return kubebindv1alpha1.Provider + return kubebindv1alpha1.Provider, nil } } @@ -228,13 +233,13 @@ func determineOwner(providerObj, consumerObj *unstructured.Unstructured) kubebin ownerAnn := consumerObj.GetAnnotations()[annotation] switch ownerAnn { case "Provider": - return kubebindv1alpha1.Provider + return kubebindv1alpha1.Provider, nil case "Consumer": - return kubebindv1alpha1.Consumer + return kubebindv1alpha1.Consumer, nil } if ownerAnn == "" && providerObj == nil { - return kubebindv1alpha1.Consumer + return kubebindv1alpha1.Consumer, nil } } - panic("should not happen") + return "", fmt.Errorf("unable to determine owner") } From a0e3133509627b5898f45f87b1f56521f38414ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Mon, 26 Jun 2023 10:43:27 +0200 Subject: [PATCH 17/26] Adjust api types to design doc --- ...example.com_apiserviceexporttemplates.yaml | 235 ++++++++++++---- .../exporttemplate/index_test.go | 4 +- .../crd/kube-bind.io_apiservicebindings.yaml | 230 ++++++++++++---- ...kube-bind.io_apiserviceexportrequests.yaml | 252 +++++++++++++----- .../crd/kube-bind.io_apiserviceexports.yaml | 235 ++++++++++++---- .../v1alpha1/apiservicebinding_types.go | 178 ++++++++++--- .../v1alpha1/zz_generated.deepcopy.go | 111 +++++++- .../claimedresources_controller.go | 34 ++- .../serviceexport/serviceexport_reconcile.go | 5 +- .../bind-apiservice/plugin/servicebindings.go | 60 +++-- .../plugin/servicebindings_test.go | 242 +++++++++-------- .../consumer/exporttemplate-mangodb.yaml | 2 + .../provider/exporttemplate-mangodb.yaml | 2 + 13 files changed, 1184 insertions(+), 406 deletions(-) diff --git a/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml b/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml index dca2f41cd..ca2ba28bb 100644 --- a/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml +++ b/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml @@ -60,31 +60,32 @@ spec: type: object permissionClaims: items: - description: "PermissionClaim selects objects of a GVR that a service + 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. \n TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate - && self.adopt)\",message=\"donate and adopt are mutually exclusive\"" + the service provider access to. properties: - adopt: - description: adopt set to true means that objects created by - the consumer are adopted by the provider. i.e. the provider + autoAdopt: + description: AutoAdopt set to true means that objects created + by the consumer are adopted by the provider. i.e. the provider will become the owner. type: boolean + autoDonate: + description: AutoDonate set to true means that a newly created + object by the provider is immediately owned by the consumer. + If false, the object stays in ownership of the provider. + type: boolean create: - description: "only for owner Provider \n create determines whether + description: "only for owner Provider \n Create determines whether the kube-bind konnector will sync matching objects from the provider side down to the consumer cluster." properties: - donate: - description: donate set to true means that a newly created - object by the provider is immediately owned by the consumer. - If false, the object stays in ownership of the provider + replaceExisting: + description: "ReplaceExisting means that an existing object + owned by the consumer will be replaced by the provider + object. \n If set to false, and a conflicting consumer + object exists, it is not touched." type: boolean type: object - global: - description: Global claims global resources for the given group/resource. - This is mutually exclusive with resourceSelector. - type: boolean group: default: "" description: group is the name of an API group. For core groups @@ -95,24 +96,67 @@ spec: description: onConflict determines how the conflicts between objects on the consumer side will be resolved. properties: - providerOverrides: - description: providerOverrides will make the provider override - any object that might already exist in the consumer cluster - if it has the same resource identifier as a resource created - by the provider, but is not the result of syncing. - type: boolean recreateWhenConsumerSideDeleted: - description: recreateWhenConsumerSideDeleted set to true - means the provider will recreate the object in case the - object is missing on the consumer side. Even if the consumer - mistakenly or intentionally deletes the objet, the provider - will recreate it. If the field is set as false, the provider - will not recreate the object in case the object is deleted - on the RecreateWhenConsumerSideDeleted side. + default: true + description: "RecreateWhenConsumerSideDeleted set to true + (the default) means the provider will recreate the object + in case the object is missing on the consumer side, but + has been synchronized before. \n If set to false, deleted + provider-owned objects get deleted on the provider side + as well. \n Even if the consumer mistakenly or intentionally + deletes the object, the provider will recreate it. If + the field is set as false, the provider will not recreate + the object in case the object is deleted on the RecreateWhenConsumerSideDeleted + side." type: boolean type: object + read: + properties: + annotations: + description: Annotations is a list of claimed annotation + key wildcard patterns that are synchronized from the consumer + side to the provider on objects that are owned by the + consumer + items: + properties: + pattern: + type: string + type: object + type: array + labels: + description: Labels is a list of claimed label key wildcard + patterns that are synchronized from the consumer side + to the provider on objects that are owned by the consumer + items: + properties: + pattern: + type: string + type: object + type: array + labelsOnProviderOwnedObjects: + description: labelsOnProviderOwnedObjects is a list of claimed + label key wildcard patterns that are synchronized from + the consumer side to the provider on objects owned by + the provider. + items: + properties: + pattern: + type: string + type: object + type: array + overrideAnnotations: + description: OverrideAnnotations is a list of claimed annotation + key wildcard patterns that are synchronized from the consumer + side to the provider on objects owned by the provider. + items: + properties: + pattern: + type: string + type: object + type: array + type: object required: - description: required indicates whether the APIServiceBinding + description: Required indicates whether the APIServiceBinding will work if this claim is not accepted. type: boolean resource: @@ -122,18 +166,56 @@ spec: pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string selector: - description: selector selects which resources are affected by + description: Selector selects which resources are affected by this claim. properties: - name: - description: name of an object within a claimed group/resource. - It matches the metadata.name field of the underlying object. - If name is unset, all objects in bound namespaces will - be claimed. - maxLength: 253 - minLength: 1 - pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ - type: string + fieldSelectors: + description: FieldSelectors is a list of field selectors + matching selected resources, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. + items: + type: string + type: array + labelSelectors: + description: LabelSelectors is a list of label selectors + matching selected resources. label selectors follow the + same rules as kubernetes label selectors, see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. + items: + additionalProperties: + type: string + type: object + type: array + names: + default: + - '*' + description: Names is a list of specific resource names + to select. Names matches the metadata.name field of the + underlying object. An entry of "*" anywhere in the list + means all object names of the group/resource within the + "namespaces" field are claimed. Wildcard entries other + than "*" and regular expressions are currently unsupported. + items: + type: string + type: array + x-kubernetes-validations: + - message: only names or * are allowed + rule: self.all(n, n.matches('^[A-z]*|[*]$')) + namespaces: + default: + - '*' + description: Namespaces represents namespaces where an object + of the given group/resoruce may be managed. Namespaces + matches against the metadata.namespace field. A value + of "*" matches namespaced objects across all namespaces. + If namespaces is not set (an empty list), matches cluster-scoped + resources. If the "names" field is unset, all objects + of the group/resource within the listed namespaces (or + cluster) will be claimed. + items: + type: string + type: array + x-kubernetes-validations: + - message: only names or * are allowed + rule: self.all(n, n.matches('^[A-z]*|[*]$')) owner: enum: - Provider @@ -141,29 +223,72 @@ spec: type: string type: object update: - description: update lists a number of claimed permissions for + description: Update lists a number of claimed permissions for the provider. "field" and "preserving" are mutually exclusive. properties: alwaysRecreate: - description: alwaysRecreate, when true will make the konnector - delete the old object and create a new one instead of - updating. Useful for immutable objects. + description: "AlwaysRecreate, when true will delete the + old object and create new ones instead of updating. Useful + for immutable objects. \n This does not apply to metadata + field updates." type: boolean + annotations: + description: "Annotations is a list of claimed annotation + keys or annotation wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the provider. \n By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array fields: - description: fields are a list of JSON Paths describing - which parts of an object the provider wants to control - in case the object is owned by the consumer as a whole. - This field cannot be set if provider owned objects are - selected in this claim, and must be non-empty otherwise. - Mutually exclusive with preservings. + description: "Fields are a list of JSON Paths describing + which parts of an object the provider wants to control. + \n This field is ignored if the owner in the claim selector + is set to \"Provider\"." items: type: string type: array + labels: + description: "Labels is a list of claimed label keys or + label wildcard patterns that are synchronized from the + provider to the consumer for objects owned by the provider. + \n By default, no labels are synced." + items: + properties: + pattern: + type: string + type: object + type: array + overrideAnnotations: + description: "OverrideAnnotations is a list of claiemd annotation + key wildcard patterns that are synchronized from the provider + to the consumer for objects owned by the consumer. \n + By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array + overrideLabels: + description: "OverrideLabels is a list of claiemd label + key wildcard patterns that are synchronized from the provider + to the consumer for objects owned by the consumer. \n + By default, no labels are synced." + items: + properties: + pattern: + type: string + type: object + type: array preservings: - description: preserving is a list of JSON Paths describing - fields that should be preserved while updating with the - object state on the provider side. This field cannot be - set if consumer owned objects are selected in this claim. + description: "Preserving is a list of JSON Paths describing + which parts of an object owned by the provider the consumer + keeps controlling. \n This field is ignored if the owner + in the claim selector is set to \"Consumer\"." items: type: string type: array @@ -174,6 +299,10 @@ spec: - resource - version type: object + x-kubernetes-validations: + - message: donate and adopt are mutually exclusive + rule: '!(has(self.autoDonate) && self.autoDonate && has(self.autoAdopt) + && self.autoAdopt)' type: array type: object status: diff --git a/contrib/example-backend/exporttemplate/index_test.go b/contrib/example-backend/exporttemplate/index_test.go index 6b9b29431..680967ee8 100644 --- a/contrib/example-backend/exporttemplate/index_test.go +++ b/contrib/example-backend/exporttemplate/index_test.go @@ -26,8 +26,8 @@ import ( crd "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" - templates "github.com/kube-bind/kube-bind/pkg/client/clientset/versioned/fake" + "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" + templates "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned/fake" ) var mangodb = apiextensions.CustomResourceDefinition{ diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index 1a342cfb7..a57e3c7c7 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -92,26 +92,28 @@ spec: Access is granted per GroupResource, identity, and other properties. items: properties: - adopt: - description: adopt set to true means that objects created by - the consumer are adopted by the provider. i.e. the provider + autoAdopt: + description: AutoAdopt set to true means that objects created + by the consumer are adopted by the provider. i.e. the provider will become the owner. type: boolean + autoDonate: + description: AutoDonate set to true means that a newly created + object by the provider is immediately owned by the consumer. + If false, the object stays in ownership of the provider. + type: boolean create: - description: "only for owner Provider \n create determines whether + description: "only for owner Provider \n Create determines whether the kube-bind konnector will sync matching objects from the provider side down to the consumer cluster." properties: - donate: - description: donate set to true means that a newly created - object by the provider is immediately owned by the consumer. - If false, the object stays in ownership of the provider + replaceExisting: + description: "ReplaceExisting means that an existing object + owned by the consumer will be replaced by the provider + object. \n If set to false, and a conflicting consumer + object exists, it is not touched." type: boolean type: object - global: - description: Global claims global resources for the given group/resource. - This is mutually exclusive with resourceSelector. - type: boolean group: default: "" description: group is the name of an API group. For core groups @@ -122,24 +124,67 @@ spec: description: onConflict determines how the conflicts between objects on the consumer side will be resolved. properties: - providerOverrides: - description: providerOverrides will make the provider override - any object that might already exist in the consumer cluster - if it has the same resource identifier as a resource created - by the provider, but is not the result of syncing. - type: boolean recreateWhenConsumerSideDeleted: - description: recreateWhenConsumerSideDeleted set to true - means the provider will recreate the object in case the - object is missing on the consumer side. Even if the consumer - mistakenly or intentionally deletes the objet, the provider - will recreate it. If the field is set as false, the provider - will not recreate the object in case the object is deleted - on the RecreateWhenConsumerSideDeleted side. + default: true + description: "RecreateWhenConsumerSideDeleted set to true + (the default) means the provider will recreate the object + in case the object is missing on the consumer side, but + has been synchronized before. \n If set to false, deleted + provider-owned objects get deleted on the provider side + as well. \n Even if the consumer mistakenly or intentionally + deletes the object, the provider will recreate it. If + the field is set as false, the provider will not recreate + the object in case the object is deleted on the RecreateWhenConsumerSideDeleted + side." type: boolean type: object + read: + properties: + annotations: + description: Annotations is a list of claimed annotation + key wildcard patterns that are synchronized from the consumer + side to the provider on objects that are owned by the + consumer + items: + properties: + pattern: + type: string + type: object + type: array + labels: + description: Labels is a list of claimed label key wildcard + patterns that are synchronized from the consumer side + to the provider on objects that are owned by the consumer + items: + properties: + pattern: + type: string + type: object + type: array + labelsOnProviderOwnedObjects: + description: labelsOnProviderOwnedObjects is a list of claimed + label key wildcard patterns that are synchronized from + the consumer side to the provider on objects owned by + the provider. + items: + properties: + pattern: + type: string + type: object + type: array + overrideAnnotations: + description: OverrideAnnotations is a list of claimed annotation + key wildcard patterns that are synchronized from the consumer + side to the provider on objects owned by the provider. + items: + properties: + pattern: + type: string + type: object + type: array + type: object required: - description: required indicates whether the APIServiceBinding + description: Required indicates whether the APIServiceBinding will work if this claim is not accepted. type: boolean resource: @@ -149,18 +194,56 @@ spec: pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string selector: - description: selector selects which resources are affected by + description: Selector selects which resources are affected by this claim. properties: - name: - description: name of an object within a claimed group/resource. - It matches the metadata.name field of the underlying object. - If name is unset, all objects in bound namespaces will - be claimed. - maxLength: 253 - minLength: 1 - pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ - type: string + fieldSelectors: + description: FieldSelectors is a list of field selectors + matching selected resources, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. + items: + type: string + type: array + labelSelectors: + description: LabelSelectors is a list of label selectors + matching selected resources. label selectors follow the + same rules as kubernetes label selectors, see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. + items: + additionalProperties: + type: string + type: object + type: array + names: + default: + - '*' + description: Names is a list of specific resource names + to select. Names matches the metadata.name field of the + underlying object. An entry of "*" anywhere in the list + means all object names of the group/resource within the + "namespaces" field are claimed. Wildcard entries other + than "*" and regular expressions are currently unsupported. + items: + type: string + type: array + x-kubernetes-validations: + - message: only names or * are allowed + rule: self.all(n, n.matches('^[A-z]*|[*]$')) + namespaces: + default: + - '*' + description: Namespaces represents namespaces where an object + of the given group/resoruce may be managed. Namespaces + matches against the metadata.namespace field. A value + of "*" matches namespaced objects across all namespaces. + If namespaces is not set (an empty list), matches cluster-scoped + resources. If the "names" field is unset, all objects + of the group/resource within the listed namespaces (or + cluster) will be claimed. + items: + type: string + type: array + x-kubernetes-validations: + - message: only names or * are allowed + rule: self.all(n, n.matches('^[A-z]*|[*]$')) owner: enum: - Provider @@ -173,29 +256,72 @@ spec: - Rejected type: string update: - description: update lists a number of claimed permissions for + description: Update lists a number of claimed permissions for the provider. "field" and "preserving" are mutually exclusive. properties: alwaysRecreate: - description: alwaysRecreate, when true will make the konnector - delete the old object and create a new one instead of - updating. Useful for immutable objects. + description: "AlwaysRecreate, when true will delete the + old object and create new ones instead of updating. Useful + for immutable objects. \n This does not apply to metadata + field updates." type: boolean + annotations: + description: "Annotations is a list of claimed annotation + keys or annotation wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the provider. \n By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array fields: - description: fields are a list of JSON Paths describing - which parts of an object the provider wants to control - in case the object is owned by the consumer as a whole. - This field cannot be set if provider owned objects are - selected in this claim, and must be non-empty otherwise. - Mutually exclusive with preservings. + description: "Fields are a list of JSON Paths describing + which parts of an object the provider wants to control. + \n This field is ignored if the owner in the claim selector + is set to \"Provider\"." items: type: string type: array + labels: + description: "Labels is a list of claimed label keys or + label wildcard patterns that are synchronized from the + provider to the consumer for objects owned by the provider. + \n By default, no labels are synced." + items: + properties: + pattern: + type: string + type: object + type: array + overrideAnnotations: + description: "OverrideAnnotations is a list of claiemd annotation + key wildcard patterns that are synchronized from the provider + to the consumer for objects owned by the consumer. \n + By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array + overrideLabels: + description: "OverrideLabels is a list of claiemd label + key wildcard patterns that are synchronized from the provider + to the consumer for objects owned by the consumer. \n + By default, no labels are synced." + items: + properties: + pattern: + type: string + type: object + type: array preservings: - description: preserving is a list of JSON Paths describing - fields that should be preserved while updating with the - object state on the provider side. This field cannot be - set if consumer owned objects are selected in this claim. + description: "Preserving is a list of JSON Paths describing + which parts of an object owned by the provider the consumer + keeps controlling. \n This field is ignored if the owner + in the claim selector is set to \"Consumer\"." items: type: string type: array @@ -207,6 +333,10 @@ spec: - state - version type: object + x-kubernetes-validations: + - message: donate and adopt are mutually exclusive + rule: '!(has(self.autoDonate) && self.autoDonate && has(self.autoAdopt) + && self.autoAdopt)' type: array required: - kubeconfigSecretRef diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 6a4fe96dd..33dd63a15 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -71,34 +71,33 @@ spec: in this workspace. Access is granted per GroupResource, identity, and other properties. items: - description: "PermissionClaim selects objects of a GVR that + 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. \n TODO fix validation - +kubebuilder:validation:XValidation:rule=\"!(self.create.donate - && self.adopt)\",message=\"donate and adopt are mutually - exclusive\"" + and allow the service provider access to. properties: - adopt: - description: adopt set to true means that objects created - by the consumer are adopted by the provider. i.e. the - provider will become the owner. + autoAdopt: + description: AutoAdopt set to true means that objects + created by the consumer are adopted by the provider. + i.e. the provider will become the owner. + type: boolean + autoDonate: + description: AutoDonate set to true means that a newly + created object by the provider is immediately owned + by the consumer. If false, the object stays in ownership + of the provider. type: boolean create: - description: "only for owner Provider \n create determines + description: "only for owner Provider \n Create determines whether the kube-bind konnector will sync matching objects from the provider side down to the consumer cluster." properties: - donate: - description: donate set to true means that a newly - created object by the provider is immediately owned - by the consumer. If false, the object stays in ownership - of the provider + replaceExisting: + description: "ReplaceExisting means that an existing + object owned by the consumer will be replaced by + the provider object. \n If set to false, and a conflicting + consumer object exists, it is not touched." type: boolean type: object - global: - description: Global claims global resources for the given - group/resource. This is mutually exclusive with resourceSelector. - type: boolean group: default: "" description: group is the name of an API group. For core @@ -109,26 +108,70 @@ spec: description: onConflict determines how the conflicts between objects on the consumer side will be resolved. properties: - providerOverrides: - description: providerOverrides will make the provider - override any object that might already exist in - the consumer cluster if it has the same resource - identifier as a resource created by the provider, - but is not the result of syncing. - type: boolean recreateWhenConsumerSideDeleted: - description: recreateWhenConsumerSideDeleted set to - true means the provider will recreate the object - in case the object is missing on the consumer side. - Even if the consumer mistakenly or intentionally - deletes the objet, the provider will recreate it. - If the field is set as false, the provider will - not recreate the object in case the object is deleted - on the RecreateWhenConsumerSideDeleted side. + default: true + description: "RecreateWhenConsumerSideDeleted set + to true (the default) means the provider will recreate + the object in case the object is missing on the + consumer side, but has been synchronized before. + \n If set to false, deleted provider-owned objects + get deleted on the provider side as well. \n Even + if the consumer mistakenly or intentionally deletes + the object, the provider will recreate it. If the + field is set as false, the provider will not recreate + the object in case the object is deleted on the + RecreateWhenConsumerSideDeleted side." type: boolean type: object + read: + properties: + annotations: + description: Annotations is a list of claimed annotation + key wildcard patterns that are synchronized from + the consumer side to the provider on objects that + are owned by the consumer + items: + properties: + pattern: + type: string + type: object + type: array + labels: + description: Labels is a list of claimed label key + wildcard patterns that are synchronized from the + consumer side to the provider on objects that are + owned by the consumer + items: + properties: + pattern: + type: string + type: object + type: array + labelsOnProviderOwnedObjects: + description: labelsOnProviderOwnedObjects is a list + of claimed label key wildcard patterns that are + synchronized from the consumer side to the provider + on objects owned by the provider. + items: + properties: + pattern: + type: string + type: object + type: array + overrideAnnotations: + description: OverrideAnnotations is a list of claimed + annotation key wildcard patterns that are synchronized + from the consumer side to the provider on objects + owned by the provider. + items: + properties: + pattern: + type: string + type: object + type: array + type: object required: - description: required indicates whether the APIServiceBinding + description: Required indicates whether the APIServiceBinding will work if this claim is not accepted. type: boolean resource: @@ -139,18 +182,59 @@ spec: pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string selector: - description: selector selects which resources are affected + description: Selector selects which resources are affected by this claim. properties: - name: - description: name of an object within a claimed group/resource. - It matches the metadata.name field of the underlying - object. If name is unset, all objects in bound namespaces - will be claimed. - maxLength: 253 - minLength: 1 - pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ - type: string + fieldSelectors: + description: FieldSelectors is a list of field selectors + matching selected resources, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. + items: + type: string + type: array + labelSelectors: + description: LabelSelectors is a list of label selectors + matching selected resources. label selectors follow + the same rules as kubernetes label selectors, see + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. + items: + additionalProperties: + type: string + type: object + type: array + names: + default: + - '*' + description: Names is a list of specific resource + names to select. Names matches the metadata.name + field of the underlying object. An entry of "*" + anywhere in the list means all object names of the + group/resource within the "namespaces" field are + claimed. Wildcard entries other than "*" and regular + expressions are currently unsupported. + items: + type: string + type: array + x-kubernetes-validations: + - message: only names or * are allowed + rule: self.all(n, n.matches('^[A-z]*|[*]$')) + namespaces: + default: + - '*' + description: Namespaces represents namespaces where + an object of the given group/resoruce may be managed. + Namespaces matches against the metadata.namespace + field. A value of "*" matches namespaced objects + across all namespaces. If namespaces is not set + (an empty list), matches cluster-scoped resources. + If the "names" field is unset, all objects of the + group/resource within the listed namespaces (or + cluster) will be claimed. + items: + type: string + type: array + x-kubernetes-validations: + - message: only names or * are allowed + rule: self.all(n, n.matches('^[A-z]*|[*]$')) owner: enum: - Provider @@ -158,31 +242,75 @@ spec: type: string type: object update: - description: update lists a number of claimed permissions + description: Update lists a number of claimed permissions for the provider. "field" and "preserving" are mutually exclusive. properties: alwaysRecreate: - description: alwaysRecreate, when true will make the - konnector delete the old object and create a new - one instead of updating. Useful for immutable objects. + description: "AlwaysRecreate, when true will delete + the old object and create new ones instead of updating. + Useful for immutable objects. \n This does not apply + to metadata field updates." type: boolean + annotations: + description: "Annotations is a list of claimed annotation + keys or annotation wildcard patterns that are synchronized + from the provider to the consumer for objects owned + by the provider. \n By default, no annotations are + synced." + items: + properties: + pattern: + type: string + type: object + type: array fields: - description: fields are a list of JSON Paths describing - which parts of an object the provider wants to control - in case the object is owned by the consumer as a - whole. This field cannot be set if provider owned - objects are selected in this claim, and must be - non-empty otherwise. Mutually exclusive with preservings. + description: "Fields are a list of JSON Paths describing + which parts of an object the provider wants to control. + \n This field is ignored if the owner in the claim + selector is set to \"Provider\"." items: type: string type: array + labels: + description: "Labels is a list of claimed label keys + or label wildcard patterns that are synchronized + from the provider to the consumer for objects owned + by the provider. \n By default, no labels are synced." + items: + properties: + pattern: + type: string + type: object + type: array + overrideAnnotations: + description: "OverrideAnnotations is a list of claiemd + annotation key wildcard patterns that are synchronized + from the provider to the consumer for objects owned + by the consumer. \n By default, no annotations are + synced." + items: + properties: + pattern: + type: string + type: object + type: array + overrideLabels: + description: "OverrideLabels is a list of claiemd + label key wildcard patterns that are synchronized + from the provider to the consumer for objects owned + by the consumer. \n By default, no labels are synced." + items: + properties: + pattern: + type: string + type: object + type: array preservings: - description: preserving is a list of JSON Paths describing - fields that should be preserved while updating with - the object state on the provider side. This field - cannot be set if consumer owned objects are selected - in this claim. + description: "Preserving is a list of JSON Paths describing + which parts of an object owned by the provider the + consumer keeps controlling. \n This field is ignored + if the owner in the claim selector is set to \"Consumer\"." items: type: string type: array @@ -193,6 +321,10 @@ spec: - resource - version type: object + x-kubernetes-validations: + - message: donate and adopt are mutually exclusive + rule: '!(has(self.autoDonate) && self.autoDonate && has(self.autoAdopt) + && self.autoAdopt)' type: array resource: description: 'resource is the name of the resource. Note: it diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index 7b05a7900..b6cd948cf 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -110,31 +110,32 @@ spec: type: object permissionClaims: items: - description: "PermissionClaim selects objects of a GVR that a service + 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. \n TODO fix validation +kubebuilder:validation:XValidation:rule=\"!(self.create.donate - && self.adopt)\",message=\"donate and adopt are mutually exclusive\"" + the service provider access to. properties: - adopt: - description: adopt set to true means that objects created by - the consumer are adopted by the provider. i.e. the provider + autoAdopt: + description: AutoAdopt set to true means that objects created + by the consumer are adopted by the provider. i.e. the provider will become the owner. type: boolean + autoDonate: + description: AutoDonate set to true means that a newly created + object by the provider is immediately owned by the consumer. + If false, the object stays in ownership of the provider. + type: boolean create: - description: "only for owner Provider \n create determines whether + description: "only for owner Provider \n Create determines whether the kube-bind konnector will sync matching objects from the provider side down to the consumer cluster." properties: - donate: - description: donate set to true means that a newly created - object by the provider is immediately owned by the consumer. - If false, the object stays in ownership of the provider + replaceExisting: + description: "ReplaceExisting means that an existing object + owned by the consumer will be replaced by the provider + object. \n If set to false, and a conflicting consumer + object exists, it is not touched." type: boolean type: object - global: - description: Global claims global resources for the given group/resource. - This is mutually exclusive with resourceSelector. - type: boolean group: default: "" description: group is the name of an API group. For core groups @@ -145,24 +146,67 @@ spec: description: onConflict determines how the conflicts between objects on the consumer side will be resolved. properties: - providerOverrides: - description: providerOverrides will make the provider override - any object that might already exist in the consumer cluster - if it has the same resource identifier as a resource created - by the provider, but is not the result of syncing. - type: boolean recreateWhenConsumerSideDeleted: - description: recreateWhenConsumerSideDeleted set to true - means the provider will recreate the object in case the - object is missing on the consumer side. Even if the consumer - mistakenly or intentionally deletes the objet, the provider - will recreate it. If the field is set as false, the provider - will not recreate the object in case the object is deleted - on the RecreateWhenConsumerSideDeleted side. + default: true + description: "RecreateWhenConsumerSideDeleted set to true + (the default) means the provider will recreate the object + in case the object is missing on the consumer side, but + has been synchronized before. \n If set to false, deleted + provider-owned objects get deleted on the provider side + as well. \n Even if the consumer mistakenly or intentionally + deletes the object, the provider will recreate it. If + the field is set as false, the provider will not recreate + the object in case the object is deleted on the RecreateWhenConsumerSideDeleted + side." type: boolean type: object + read: + properties: + annotations: + description: Annotations is a list of claimed annotation + key wildcard patterns that are synchronized from the consumer + side to the provider on objects that are owned by the + consumer + items: + properties: + pattern: + type: string + type: object + type: array + labels: + description: Labels is a list of claimed label key wildcard + patterns that are synchronized from the consumer side + to the provider on objects that are owned by the consumer + items: + properties: + pattern: + type: string + type: object + type: array + labelsOnProviderOwnedObjects: + description: labelsOnProviderOwnedObjects is a list of claimed + label key wildcard patterns that are synchronized from + the consumer side to the provider on objects owned by + the provider. + items: + properties: + pattern: + type: string + type: object + type: array + overrideAnnotations: + description: OverrideAnnotations is a list of claimed annotation + key wildcard patterns that are synchronized from the consumer + side to the provider on objects owned by the provider. + items: + properties: + pattern: + type: string + type: object + type: array + type: object required: - description: required indicates whether the APIServiceBinding + description: Required indicates whether the APIServiceBinding will work if this claim is not accepted. type: boolean resource: @@ -172,18 +216,56 @@ spec: pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string selector: - description: selector selects which resources are affected by + description: Selector selects which resources are affected by this claim. properties: - name: - description: name of an object within a claimed group/resource. - It matches the metadata.name field of the underlying object. - If name is unset, all objects in bound namespaces will - be claimed. - maxLength: 253 - minLength: 1 - pattern: ^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$ - type: string + fieldSelectors: + description: FieldSelectors is a list of field selectors + matching selected resources, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. + items: + type: string + type: array + labelSelectors: + description: LabelSelectors is a list of label selectors + matching selected resources. label selectors follow the + same rules as kubernetes label selectors, see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. + items: + additionalProperties: + type: string + type: object + type: array + names: + default: + - '*' + description: Names is a list of specific resource names + to select. Names matches the metadata.name field of the + underlying object. An entry of "*" anywhere in the list + means all object names of the group/resource within the + "namespaces" field are claimed. Wildcard entries other + than "*" and regular expressions are currently unsupported. + items: + type: string + type: array + x-kubernetes-validations: + - message: only names or * are allowed + rule: self.all(n, n.matches('^[A-z]*|[*]$')) + namespaces: + default: + - '*' + description: Namespaces represents namespaces where an object + of the given group/resoruce may be managed. Namespaces + matches against the metadata.namespace field. A value + of "*" matches namespaced objects across all namespaces. + If namespaces is not set (an empty list), matches cluster-scoped + resources. If the "names" field is unset, all objects + of the group/resource within the listed namespaces (or + cluster) will be claimed. + items: + type: string + type: array + x-kubernetes-validations: + - message: only names or * are allowed + rule: self.all(n, n.matches('^[A-z]*|[*]$')) owner: enum: - Provider @@ -191,29 +273,72 @@ spec: type: string type: object update: - description: update lists a number of claimed permissions for + description: Update lists a number of claimed permissions for the provider. "field" and "preserving" are mutually exclusive. properties: alwaysRecreate: - description: alwaysRecreate, when true will make the konnector - delete the old object and create a new one instead of - updating. Useful for immutable objects. + description: "AlwaysRecreate, when true will delete the + old object and create new ones instead of updating. Useful + for immutable objects. \n This does not apply to metadata + field updates." type: boolean + annotations: + description: "Annotations is a list of claimed annotation + keys or annotation wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the provider. \n By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array fields: - description: fields are a list of JSON Paths describing - which parts of an object the provider wants to control - in case the object is owned by the consumer as a whole. - This field cannot be set if provider owned objects are - selected in this claim, and must be non-empty otherwise. - Mutually exclusive with preservings. + description: "Fields are a list of JSON Paths describing + which parts of an object the provider wants to control. + \n This field is ignored if the owner in the claim selector + is set to \"Provider\"." items: type: string type: array + labels: + description: "Labels is a list of claimed label keys or + label wildcard patterns that are synchronized from the + provider to the consumer for objects owned by the provider. + \n By default, no labels are synced." + items: + properties: + pattern: + type: string + type: object + type: array + overrideAnnotations: + description: "OverrideAnnotations is a list of claiemd annotation + key wildcard patterns that are synchronized from the provider + to the consumer for objects owned by the consumer. \n + By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array + overrideLabels: + description: "OverrideLabels is a list of claiemd label + key wildcard patterns that are synchronized from the provider + to the consumer for objects owned by the consumer. \n + By default, no labels are synced." + items: + properties: + pattern: + type: string + type: object + type: array preservings: - description: preserving is a list of JSON Paths describing - fields that should be preserved while updating with the - object state on the provider side. This field cannot be - set if consumer owned objects are selected in this claim. + description: "Preserving is a list of JSON Paths describing + which parts of an object owned by the provider the consumer + keeps controlling. \n This field is ignored if the owner + in the claim selector is set to \"Consumer\"." items: type: string type: array @@ -224,6 +349,10 @@ spec: - resource - version type: object + x-kubernetes-validations: + - message: donate and adopt are mutually exclusive + rule: '!(has(self.autoDonate) && self.autoDonate && has(self.autoAdopt) + && self.autoAdopt)' type: array scope: description: scope indicates whether the defined custom resource is diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index a28c03f6a..506a3559b 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -118,93 +118,193 @@ const ( // 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. // -// TODO fix validation +kubebuilder:validation:XValidation:rule="!(self.create.donate && self.adopt)",message="donate and adopt are mutually exclusive" +// +kubebuilder:validation:XValidation:rule="!(has(self.autoDonate) && self.autoDonate && has(self.autoAdopt) && self.autoAdopt)",message="donate and adopt are mutually exclusive" type PermissionClaim struct { GroupResource `json:","` // +kubebuilder:validation:Required Version string `json:"version"` - // selector selects which resources are affected by this claim. - Selector ResourceSelector `json:"selector"` + // Selector selects which resources are affected by this claim. + // +optional + Selector *ResourceSelector `json:"selector,omitempty"` - // required indicates whether the APIServiceBinding will work if this claim is not accepted. + // Required indicates whether the APIServiceBinding will work if this claim is not accepted. Required bool `json:"required"` - // Global claims global resources for the given group/resource. - // This is mutually exclusive with resourceSelector. // +optional - Global bool `json:"global,omitempty"` + // +kubebuilder:default={} + Read *ReadOptions `json:"read,omitempty"` // only for owner Provider // - // create determines whether the kube-bind konnector will sync matching objects from the + // Create determines whether the kube-bind konnector will sync matching objects from the // provider side down to the consumer cluster. - Create *CreateOptions `json:"create"` + // + // +optional + Create *CreateOptions `json:"create,omitempty"` - // adopt set to true means that objects created by the consumer are adopted by the provider. + // AutoAdopt set to true means that objects created by the consumer are adopted by the provider. // i.e. the provider will become the owner. - Adopt bool `json:"adopt,omitempty"` + // + // +optional + AutoAdopt bool `json:"autoAdopt,omitempty"` + + // AutoDonate set to true means that a newly created object by the provider is immediately owned by the consumer. + // If false, the object stays in ownership of the provider. + // + // +optional + AutoDonate bool `json:"autoDonate,omitempty"` // onConflict determines how the conflicts between objects on the consumer side // will be resolved. + // + // +optional OnConflict *OnConflictOptions `json:"onConflict,omitempty"` - // update lists a number of claimed permissions for the provider. + // Update lists a number of claimed permissions for the provider. // "field" and "preserving" are mutually exclusive. + // + // +optional Update *UpdateOptions `json:"update,omitempty"` } +type ReadOptions struct { + // Labels is a list of claimed label key wildcard patterns + // that are synchronized from the consumer side to the provider on + // objects that are owned by the consumer + // + // +optional + Labels []Matcher `json:"labels,omitempty"` + + // labelsOnProviderOwnedObjects is a list of claimed label key wildcard + // patterns that are synchronized from the consumer side + // to the provider on objects owned by the provider. + // + // +optional + LabelsOnProviderOwnedObjects []Matcher `json:"labelsOnProviderOwnedObjects,omitempty"` + + // Annotations is a list of claimed annotation key wildcard patterns + // that are synchronized from the consumer side to the provider on + // objects that are owned by the consumer + // + // +optional + Annotations []Matcher `json:"annotations,omitempty"` + + // OverrideAnnotations is a list of claimed annotation key wildcard + // patterns that are synchronized from the consumer side + // to the provider on objects owned by the provider. + // + // +optional + OverrideAnnotations []Matcher `json:"overrideAnnotations,omitempty"` +} + +type Matcher struct { + // +optional + Pattern string `json:"pattern,omitempty"` +} + type OnConflictOptions struct { - // providerOverrides will make the provider override any object that might already exist - // in the consumer cluster if it has the same resource identifier as a resource created by the - // provider, but is not the result of syncing. - ProviderOverwrites bool `json:"providerOverrides"` - - // recreateWhenConsumerSideDeleted set to true means the provider will recreate the object - // in case the object is missing on the consumer side. Even if the consumer mistakenly or intentionally - // deletes the objet, the provider will recreate it. If the field is set as false, + // RecreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object + // in case the object is missing on the consumer side, but has been synchronized before. + // + // If set to false, deleted provider-owned objects get deleted on the provider side as well. + // + // Even if the consumer mistakenly or intentionally + // deletes the object, the provider will recreate it. If the field is set as false, // the provider will not recreate the object in case the object is deleted on the RecreateWhenConsumerSideDeleted // side. + // + // +kubebuilder:default:=true RecreateWhenConsumerSideDeleted bool `json:"recreateWhenConsumerSideDeleted"` } type CreateOptions struct { - // donate set to true means that a newly created object by the provider is immediately owned by the consumer. - // If false, the object stays in ownership of the provider - Donate bool `json:"donate,omitempty"` + // ReplaceExisting means that an existing object owned by the consumer will be replaced by the provider object. + // + // If set to false, and a conflicting consumer object exists, it is not touched. + // +optional + ReplaceExisting bool `json:"replaceExisting,omitempty"` } type UpdateOptions struct { - // fields are a list of JSON Paths describing which parts of an object the provider wants to control in case - // the object is owned by the consumer as a whole. This field cannot be set if provider owned objects - // are selected in this claim, and must be non-empty otherwise. - // Mutually exclusive with preservings. + // Fields are a list of JSON Paths describing which parts of an object the provider wants to control. + // + // This field is ignored if the owner in the claim selector is set to "Provider". Fields []string `json:"fields,omitempty"` - // preserving is a list of JSON Paths describing fields that should be preserved while updating with the - // object state on the provider side. This field cannot be set if consumer owned objects are selected in - // this claim. + // Preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. + // + // This field is ignored if the owner in the claim selector is set to "Consumer". Preserving []string `json:"preservings,omitempty"` - // alwaysRecreate, when true will make the konnector delete the old object and create a new one + // AlwaysRecreate, when true will delete the old object and create new ones // instead of updating. Useful for immutable objects. + // + // This does not apply to metadata field updates. AlwaysRecreate bool `json:"alwaysRecreate,omitempty"` + + // Labels is a list of claimed label keys or label wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. + // + // By default, no labels are synced. + // + // +optional + Labels []Matcher `json:"labels,omitempty"` + + // OverrideLabels is a list of claiemd label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. + // + // By default, no labels are synced. + // + // +optional + OverrideLabels []Matcher `json:"overrideLabels,omitempty"` + + // Annotations is a list of claimed annotation keys or annotation wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. + // + // By default, no annotations are synced. + // + // +optional + Annotations []Matcher `json:"annotations,omitempty"` + + // OverrideAnnotations is a list of claiemd annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. + // + // By default, no annotations are synced. + // + // +optional + OverrideAnnotations []Matcher `json:"overrideAnnotations,omitempty"` } type ResourceSelector struct { - // name of an object within a claimed group/resource. - // It matches the metadata.name field of the underlying object. - // If name is unset, all objects in bound namespaces will be claimed. + // Names is a list of specific resource names to select. + // Names matches the metadata.name field of the underlying object. + // An entry of "*" anywhere in the list means all object names of the group/resource within the "namespaces" field are claimed. + // Wildcard entries other than "*" and regular expressions are currently unsupported. // + // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z]*|[*]$'))",message="only names or * are allowed" + // +kubebuilder:default:={"*"} // +optional - // +kubebuilder:validation:Pattern="^([a-z0-9][-a-z0-9_.]*)?[a-z0-9]$" - // +kubebuilder:validation:MaxLength=253 - // +kubebuilder:validation:MinLength=1 - Name string `json:"name,omitempty"` + Names []string `json:"names,omitempty"` + + // Namespaces represents namespaces where an object of the given group/resoruce may be managed. + // Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all + // namespaces. If namespaces is not set (an empty list), matches cluster-scoped resources. + // If the "names" field is unset, all objects of the group/resource within the listed namespaces (or cluster) will be claimed. + // + // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z]*|[*]$'))",message="only names or * are allowed" + // +kubebuilder:default:={"*"} + // +optional + Namespaces []string `json:"namespaces,omitempty"` + + // LabelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors, + // see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. + LabelSelectors []map[string]string `json:"labelSelectors,omitempty"` + + // FieldSelectors is a list of field selectors matching selected resources, + // see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. + FieldSelectors []string `json:"fieldSelectors,omitempty"` // +kubebuilder:validation:Enum=Provider;Consumer - Owner Owner `json:"owner"` + // +optional + Owner Owner `json:"owner,omitempty"` } type Owner string diff --git a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go index 8132985fb..fed6ddb73 100644 --- a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go @@ -895,6 +895,22 @@ func (in *LocalSecretKeyRef) DeepCopy() *LocalSecretKeyRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Matcher) DeepCopyInto(out *Matcher) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Matcher. +func (in *Matcher) DeepCopy() *Matcher { + if in == nil { + return nil + } + out := new(Matcher) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NameObjectMeta) DeepCopyInto(out *NameObjectMeta) { *out = *in @@ -947,7 +963,16 @@ func (in *OnConflictOptions) DeepCopy() *OnConflictOptions { func (in *PermissionClaim) DeepCopyInto(out *PermissionClaim) { *out = *in out.GroupResource = in.GroupResource - out.Selector = in.Selector + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(ResourceSelector) + (*in).DeepCopyInto(*out) + } + if in.Read != nil { + in, out := &in.Read, &out.Read + *out = new(ReadOptions) + (*in).DeepCopyInto(*out) + } if in.Create != nil { in, out := &in.Create, &out.Create *out = new(CreateOptions) @@ -976,9 +1001,73 @@ func (in *PermissionClaim) DeepCopy() *PermissionClaim { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ReadOptions) DeepCopyInto(out *ReadOptions) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make([]Matcher, len(*in)) + copy(*out, *in) + } + if in.LabelsOnProviderOwnedObjects != nil { + in, out := &in.LabelsOnProviderOwnedObjects, &out.LabelsOnProviderOwnedObjects + *out = make([]Matcher, len(*in)) + copy(*out, *in) + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make([]Matcher, len(*in)) + copy(*out, *in) + } + if in.OverrideAnnotations != nil { + in, out := &in.OverrideAnnotations, &out.OverrideAnnotations + *out = make([]Matcher, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReadOptions. +func (in *ReadOptions) DeepCopy() *ReadOptions { + if in == nil { + return nil + } + out := new(ReadOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceSelector) DeepCopyInto(out *ResourceSelector) { *out = *in + if in.Names != nil { + in, out := &in.Names, &out.Names + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.LabelSelectors != nil { + in, out := &in.LabelSelectors, &out.LabelSelectors + *out = make([]map[string]string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + } + if in.FieldSelectors != nil { + in, out := &in.FieldSelectors, &out.FieldSelectors + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -1005,6 +1094,26 @@ func (in *UpdateOptions) DeepCopyInto(out *UpdateOptions) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make([]Matcher, len(*in)) + copy(*out, *in) + } + if in.OverrideLabels != nil { + in, out := &in.OverrideLabels, &out.OverrideLabels + *out = make([]Matcher, len(*in)) + copy(*out, *in) + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make([]Matcher, len(*in)) + copy(*out, *in) + } + if in.OverrideAnnotations != nil { + in, out := &in.OverrideAnnotations, &out.OverrideAnnotations + *out = make([]Matcher, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go index 5c7665392..de425c8d4 100644 --- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go @@ -184,19 +184,37 @@ type controller struct { } func (c *controller) isClaimed(obj *unstructured.Unstructured) bool { - if c.claim.Selector.Owner != "" { - for k, v := range obj.GetAnnotations() { - if k == annotation { - return kubebindv1alpha1.Owner(v) == c.claim.Selector.Owner - } + + var found string + for k, v := range obj.GetAnnotations() { + if k == annotation { + found = v + break } } - if c.claim.Selector.Name != "" && obj.GetName() != c.claim.Selector.Name { - return false + annotationMatches := false + if c.claim.Selector == nil || c.claim.Selector.Owner == "" { + annotationMatches = true + } else { + annotationMatches = found == "" || found == string(c.claim.Selector.Owner) } - return true + nameMatch := false + if len(c.claim.Selector.Names) == 0 { + nameMatch = true + } else { + for _, name := range c.claim.Selector.Names { + if name == obj.GetName() || name == "*" { + nameMatch = true + break + } + } + } + + // TODO namespace match + + return nameMatch && annotationMatches } func (c *controller) enqueueConsumer(logger klog.Logger, obj interface{}) { diff --git a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go index 9222dbdba..e86191728 100644 --- a/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go +++ b/pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go @@ -226,7 +226,10 @@ func (r *reconciler) ensureControllers(ctx context.Context, name string, export } var providerInf multinsinformer.GetterInformer - if claim.Global { + + syncClusterScoped := claim.Selector != nil && claim.Selector.Namespaces == nil + + if syncClusterScoped { factory := dynamicinformer.NewDynamicSharedInformerFactory(dynamicProviderClient, time.Minute*30) factory.ForResource(claimGVR).Lister() // wire the GVR up in the informer factory providerInf = multinsinformer.GetterInformerWrapper{ diff --git a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go index 7ffd78b52..3c79a1c0e 100644 --- a/pkg/kubectl/bind-apiservice/plugin/servicebindings.go +++ b/pkg/kubectl/bind-apiservice/plugin/servicebindings.go @@ -155,6 +155,10 @@ func printPermissionClaim(w io.Writer, p kubebindv1alpha1.PermissionClaim) error return err } + if err := writeCreate(&b, p); err != nil { + return err + } + if err := writeOnConflict(&b, p); err != nil { return err } @@ -174,16 +178,14 @@ func printPermissionClaim(w io.Writer, p kubebindv1alpha1.PermissionClaim) error func writeFirstLines(b *bytes.Buffer, groupResource string, claim kubebindv1alpha1.PermissionClaim) error { var err error - donate := false - if claim.Create != nil { - donate = claim.Create.Donate - } - adopt := claim.Adopt + donate := claim.AutoDonate + + adopt := claim.AutoAdopt - name := "" + var names []string var owner kubebindv1alpha1.Owner - if (claim.Selector != kubebindv1alpha1.ResourceSelector{}) { - name = claim.Selector.Name + if claim.Selector != nil { + names = claim.Selector.Names owner = claim.Selector.Owner } @@ -207,10 +209,14 @@ func writeFirstLines(b *bytes.Buffer, groupResource string, claim kubebindv1alph } var ref string - if name != "" { - ref = fmt.Sprintf(" which are referenced with:\n\tname: \"%s\"\n", name) + if len(names) > 0 { + ref = " which are referenced with:" + for _, name := range names { + ref = fmt.Sprintf("%s\n\t- name: \"%s\"", ref, name) + } + ref += "\n" } else { - ref = " " + ref += " " } _, err = fmt.Fprintf(b, "The provider wants to %s%son your cluster.\n", groupResource, ref) @@ -219,20 +225,30 @@ func writeFirstLines(b *bytes.Buffer, groupResource string, claim kubebindv1alph } -func writeOnConflict(b *bytes.Buffer, claim kubebindv1alpha1.PermissionClaim) error { +func writeCreate(b io.StringWriter, claim kubebindv1alpha1.PermissionClaim) error { var err error - if claim.OnConflict != nil { - switch { - case claim.OnConflict.ProviderOverwrites && claim.OnConflict.RecreateWhenConsumerSideDeleted: - _, err = b.WriteString("Conflicting objects will be overwritten and created objects will be recreated upon deletion.\n") - case claim.OnConflict.ProviderOverwrites: - _, err = b.WriteString("Conflicting objects will be overwritten and created objects will not be recreated upon deletion.\n") - case claim.OnConflict.RecreateWhenConsumerSideDeleted: - _, err = b.WriteString("Conflicting objects will not be overwritten and created objects will be recreated upon deletion.\n") - default: //Do nothing - } + switch { + case claim.Create == nil || !claim.Create.ReplaceExisting: + //_, err = b.WriteString("Conflicting objects will not be overwritten. ") + case claim.Create.ReplaceExisting: + _, err = b.WriteString("Conflicting objects will be replaced by the provider. ") } + + return err +} + +func writeOnConflict(b io.StringWriter, claim kubebindv1alpha1.PermissionClaim) error { + var err error + + switch { + case claim.OnConflict == nil || !claim.OnConflict.RecreateWhenConsumerSideDeleted: + //_, err = b.WriteString("Created objects will not be recreated upon deletion. ") + case claim.OnConflict.RecreateWhenConsumerSideDeleted: + _, err = b.WriteString("Created objects will be recreated upon deletion. ") + default: //Do nothing + } + return err } diff --git a/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go b/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go index 8b683a69a..48384643b 100644 --- a/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go +++ b/pkg/kubectl/bind-apiservice/plugin/servicebindings_test.go @@ -41,7 +41,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -57,7 +57,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: false, @@ -66,21 +66,21 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is optional.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,Selector.Name", + {"Owner=Provider,Selector.Names={foo}", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ - Name: "bar", + Selector: &kubebindv1alpha1.ResourceSelector{ + Names: []string{"bar"}, Owner: kubebindv1alpha1.Provider, }, Required: true, }, "The provider wants to write foo objects (apiVersion: \"v1\") which are referenced with:\n" + - "\tname: \"bar\"\n" + + "\t- name: \"bar\"\n" + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -92,7 +92,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -101,21 +101,21 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,Selector.Name,GroupResource.Group", + {"Owner=Provider,Selector.Names={bar},GroupResource.Group", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "example.com", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ - Name: "bar", + Selector: &kubebindv1alpha1.ResourceSelector{ + Names: []string{"bar"}, Owner: kubebindv1alpha1.Provider, }, Required: true, }, "The provider wants to write foo objects (apiVersion: \"example.com/v1\") which are referenced with:\n" + - "\tname: \"bar\"\n" + + "\t- name: \"bar\"\n" + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -127,7 +127,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -137,39 +137,35 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,CreateOptions.Donate=false", + {"Owner=Provider,AutoDonate=false", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, - Required: true, - Create: &kubebindv1alpha1.CreateOptions{ - Donate: false, - }, + Required: true, + AutoDonate: false, }, "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,CreateOption.Donate=true", + {"Owner=Provider,AutoDonate=true", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, - Required: true, - Create: &kubebindv1alpha1.CreateOptions{ - Donate: true, - }, + Required: true, + AutoDonate: true, }, "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + @@ -182,7 +178,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -192,42 +188,42 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,OnConflict.ProviderOverwrites=false", + {"Owner=Provider,Create.ReplaceExisting=false", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, - OnConflict: &kubebindv1alpha1.OnConflictOptions{ - ProviderOverwrites: false, + Create: &kubebindv1alpha1.CreateOptions{ + ReplaceExisting: false, }, }, "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,OnConflict.ProviderOverwrites=true", + {"Owner=Provider,Create.ReplaceExisting=true", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, - OnConflict: &kubebindv1alpha1.OnConflictOptions{ - ProviderOverwrites: true, + Create: &kubebindv1alpha1.CreateOptions{ + ReplaceExisting: true, }, }, "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + - "Conflicting objects will be overwritten and created objects will not be recreated upon deletion.\n" + + "Conflicting objects will be replaced by the provider. " + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -238,7 +234,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -257,7 +253,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -266,7 +262,7 @@ func TestHumanReadablePromt(t *testing.T) { }, }, "The provider wants to write foo objects (apiVersion: \"v1\") on your cluster.\n" + - "Conflicting objects will not be overwritten and created objects will be recreated upon deletion.\n" + + "Created objects will be recreated upon deletion. " + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -277,7 +273,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -294,7 +290,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -316,7 +312,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -338,7 +334,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, Required: true, @@ -351,20 +347,18 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,UpdateOptions.Fields,CreateOptions.Donate=true", + {"Owner=Provider,UpdateOptions.Fields,AutoDonate=true", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, - Required: true, - Create: &kubebindv1alpha1.CreateOptions{ - Donate: true, - }, + Required: true, + AutoDonate: true, Update: &kubebindv1alpha1.UpdateOptions{ Fields: []string{"foo", "bar"}, }, @@ -376,20 +370,18 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Provider,UpdateOptions.Preserving,CreateOptions.Donate=true", + {"Owner=Provider,UpdateOptions.Preserving,AutoDonate=true", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Provider, }, - Required: true, - Create: &kubebindv1alpha1.CreateOptions{ - Donate: true, - }, + Required: true, + AutoDonate: true, Update: &kubebindv1alpha1.UpdateOptions{ Preserving: []string{"foo", "bar"}, }, @@ -408,7 +400,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, @@ -417,21 +409,21 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Consumer,Selector.Name", + {"Owner=Consumer,Selector.Names={bar}", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ - Name: "bar", + Selector: &kubebindv1alpha1.ResourceSelector{ + Names: []string{"bar"}, Owner: kubebindv1alpha1.Consumer, }, Required: true, }, "The provider wants to read foo objects (apiVersion: \"v1\") which are referenced with:\n" + - "\tname: \"bar\"\n" + + "\t- name: \"bar\"\n" + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -443,7 +435,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, @@ -452,21 +444,21 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Consumer,Selector.Name,GroupResource.Group", + {"Owner=Consumer,Selector.Names={bar},GroupResource.Group", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "example.com", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ - Name: "bar", + Selector: &kubebindv1alpha1.ResourceSelector{ + Names: []string{"bar"}, Owner: kubebindv1alpha1.Consumer, }, Required: true, }, "The provider wants to read foo objects (apiVersion: \"example.com/v1\") which are referenced with:\n" + - "\tname: \"bar\"\n" + + "\t- name: \"bar\"\n" + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -478,32 +470,32 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, - Adopt: true, - Required: true, + AutoAdopt: true, + Required: true, }, "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Consumer,Selector.Name,Adopt=true", + {"Owner=Consumer,Selector.Names={bar},Adopt=true", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ - Name: "bar", + Selector: &kubebindv1alpha1.ResourceSelector{ + Names: []string{"bar"}, Owner: kubebindv1alpha1.Consumer, }, - Adopt: true, - Required: true, + AutoAdopt: true, + Required: true, }, "The provider wants to have ownership of foo objects (apiVersion: \"v1\") which are referenced with:\n" + - "\tname: \"bar\"\n" + + "\t- name: \"bar\"\n" + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", @@ -515,7 +507,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, @@ -525,42 +517,42 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Consumer,OnConflict.ProviderOverwrites=false", + {"Owner=Consumer,Create.ReplaceExisting=false", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, - OnConflict: &kubebindv1alpha1.OnConflictOptions{ - ProviderOverwrites: false, + Create: &kubebindv1alpha1.CreateOptions{ + ReplaceExisting: false, }, }, "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Owner=Consumer,OnConflict.ProviderOverwrites=true", + {"Owner=Consumer,Create.ReplaceExisting=true", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, - OnConflict: &kubebindv1alpha1.OnConflictOptions{ - ProviderOverwrites: true, + Create: &kubebindv1alpha1.CreateOptions{ + ReplaceExisting: true, }, }, "The provider wants to read foo objects (apiVersion: \"v1\") on your cluster.\n" + - "Conflicting objects will be overwritten and created objects will not be recreated upon deletion.\n" + + "Conflicting objects will be replaced by the provider. " + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, @@ -571,7 +563,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, @@ -588,7 +580,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, @@ -610,7 +602,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, @@ -632,7 +624,7 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, Required: true, @@ -652,11 +644,11 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, - Required: true, - Adopt: true, + Required: true, + AutoAdopt: true, Update: &kubebindv1alpha1.UpdateOptions{ Fields: []string{"foo", "bar"}, }, @@ -675,11 +667,11 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ + Selector: &kubebindv1alpha1.ResourceSelector{ Owner: kubebindv1alpha1.Consumer, }, - Required: true, - Adopt: true, + Required: true, + AutoAdopt: true, Update: &kubebindv1alpha1.UpdateOptions{ Preserving: []string{"foo", "bar"}, }, @@ -697,59 +689,55 @@ func TestHumanReadablePromt(t *testing.T) { Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{}, + Selector: &kubebindv1alpha1.ResourceSelector{}, Required: true, }, "The provider wants to read and write foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Selector.Owner=\"\",Selector.Name", + {"Selector.Owner=\"\",Selector.Names={bar}", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{ - Name: "bar", + Selector: &kubebindv1alpha1.ResourceSelector{ + Names: []string{"bar"}, }, Required: true, }, "The provider wants to read and write foo objects (apiVersion: \"v1\") which are referenced with:\n" + - "\tname: \"bar\"\n" + + "\t- name: \"bar\"\n" + "on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Selector={},donate=true", + {"Selector={},AutoDonate=true", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, - Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{}, - Required: true, - Create: &kubebindv1alpha1.CreateOptions{ - Donate: true, - }, + Version: "v1", + Selector: &kubebindv1alpha1.ResourceSelector{}, + Required: true, + AutoDonate: true, }, "The provider wants to create user owned foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + "Do you accept this Permission? [No,Yes]\n", }, - {"Selector={},donate=true,update.Fields=[\"spec\"]", + {"Selector={},AutoDonate=true,update.Fields=[\"spec\"]", kubebindv1alpha1.PermissionClaim{ GroupResource: kubebindv1alpha1.GroupResource{ Group: "", Resource: "foo", }, - Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{}, - Create: &kubebindv1alpha1.CreateOptions{ - Donate: true, - }, + Version: "v1", + Selector: &kubebindv1alpha1.ResourceSelector{}, + AutoDonate: true, Update: &kubebindv1alpha1.UpdateOptions{ Fields: []string{"spec"}, }, @@ -766,10 +754,10 @@ func TestHumanReadablePromt(t *testing.T) { Group: "", Resource: "foo", }, - Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{}, - Required: true, - Adopt: true, + Version: "v1", + Selector: &kubebindv1alpha1.ResourceSelector{}, + Required: true, + AutoAdopt: true, }, "The provider wants to have ownership of foo objects (apiVersion: \"v1\") on your cluster.\n" + "Accepting this Permission is required in order to proceed.\n" + @@ -781,9 +769,9 @@ func TestHumanReadablePromt(t *testing.T) { Group: "", Resource: "foo", }, - Version: "v1", - Selector: kubebindv1alpha1.ResourceSelector{}, - Adopt: true, + Version: "v1", + Selector: &kubebindv1alpha1.ResourceSelector{}, + AutoAdopt: true, Update: &kubebindv1alpha1.UpdateOptions{ Fields: []string{"spec"}, }, @@ -794,6 +782,26 @@ func TestHumanReadablePromt(t *testing.T) { "Accepting this Permission is optional.\n" + "Do you accept this Permission? [No,Yes]\n", }, + {"Owner=Provider,Selector.Names={bar,baz}", + kubebindv1alpha1.PermissionClaim{ + GroupResource: kubebindv1alpha1.GroupResource{ + Group: "", + Resource: "foo", + }, + Version: "v1", + Selector: &kubebindv1alpha1.ResourceSelector{ + Names: []string{"bar", "baz"}, + Owner: kubebindv1alpha1.Provider, + }, + Required: true, + }, + "The provider wants to write foo objects (apiVersion: \"v1\") which are referenced with:\n" + + "\t- name: \"bar\"\n" + + "\t- name: \"baz\"\n" + + "on your cluster.\n" + + "Accepting this Permission is required in order to proceed.\n" + + "Do you accept this Permission? [No,Yes]\n", + }, } for _, tt := range tests { diff --git a/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml index 4a38b9dda..b3849a002 100644 --- a/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml +++ b/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml @@ -13,3 +13,5 @@ spec: version: v1 selector: owner: Consumer + namespaces: + - "*" diff --git a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml index bfbccd344..21f88a330 100644 --- a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml +++ b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml @@ -13,3 +13,5 @@ spec: version: v1 selector: owner: Provider + namespaces: + - "*" From 9467fae69acc25e71f76c2a856b878fb467f3f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Mon, 26 Jun 2023 14:11:58 +0200 Subject: [PATCH 18/26] fixup! Adjust api types to design doc --- ...example.com_apiserviceexporttemplates.yaml | 48 +++++++------- .../crd/kube-bind.io_apiservicebindings.yaml | 50 ++++++++------- ...kube-bind.io_apiserviceexportrequests.yaml | 49 ++++++++------- .../crd/kube-bind.io_apiserviceexports.yaml | 48 +++++++------- .../v1alpha1/apiservicebinding_types.go | 62 +++++++++++-------- 5 files changed, 142 insertions(+), 115 deletions(-) diff --git a/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml b/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml index ca2ba28bb..0e9dc7089 100644 --- a/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml +++ b/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml @@ -80,7 +80,7 @@ spec: provider side down to the consumer cluster." properties: replaceExisting: - description: "ReplaceExisting means that an existing object + description: "replaceExisting means that an existing object owned by the consumer will be replaced by the provider object. \n If set to false, and a conflicting consumer object exists, it is not touched." @@ -98,7 +98,7 @@ spec: properties: recreateWhenConsumerSideDeleted: default: true - description: "RecreateWhenConsumerSideDeleted set to true + description: "recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object in case the object is missing on the consumer side, but has been synchronized before. \n If set to false, deleted @@ -111,12 +111,15 @@ spec: type: boolean type: object read: + description: read contains extra labels and annotations the + provider will read from objects on the consumer cluster. By + default no labels and annotations are read. properties: annotations: - description: Annotations is a list of claimed annotation + description: annotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer side to the provider on objects that are owned by the - consumer + consumer. items: properties: pattern: @@ -124,9 +127,9 @@ spec: type: object type: array labels: - description: Labels is a list of claimed label key wildcard + description: labels is a list of claimed label key wildcard patterns that are synchronized from the consumer side - to the provider on objects that are owned by the consumer + to the provider on objects that are owned by the consumer. items: properties: pattern: @@ -145,7 +148,7 @@ spec: type: object type: array overrideAnnotations: - description: OverrideAnnotations is a list of claimed annotation + description: overrideAnnotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer side to the provider on objects owned by the provider. items: @@ -170,13 +173,13 @@ spec: this claim. properties: fieldSelectors: - description: FieldSelectors is a list of field selectors + description: fieldSelectors is a list of field selectors matching selected resources, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. items: type: string type: array labelSelectors: - description: LabelSelectors is a list of label selectors + description: labelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors, see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. items: @@ -187,7 +190,7 @@ spec: names: default: - '*' - description: Names is a list of specific resource names + description: names is a list of specific resource names to select. Names matches the metadata.name field of the underlying object. An entry of "*" anywhere in the list means all object names of the group/resource within the @@ -198,11 +201,11 @@ spec: type: array x-kubernetes-validations: - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z]*|[*]$')) + rule: self.all(n, n.matches('^[A-z-]+|[*]$')) namespaces: default: - '*' - description: Namespaces represents namespaces where an object + description: namespaces represents namespaces where an object of the given group/resoruce may be managed. Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all namespaces. @@ -215,7 +218,7 @@ spec: type: array x-kubernetes-validations: - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z]*|[*]$')) + rule: self.all(n, n.matches('^[A-z-]+|[*]$')) owner: enum: - Provider @@ -223,17 +226,17 @@ spec: type: string type: object update: - description: Update lists a number of claimed permissions for - the provider. "field" and "preserving" are mutually exclusive. + description: update lists which updates to objects on the consumer + side are claimed. properties: alwaysRecreate: - description: "AlwaysRecreate, when true will delete the + description: "alwaysRecreate, when true will delete the old object and create new ones instead of updating. Useful for immutable objects. \n This does not apply to metadata field updates." type: boolean annotations: - description: "Annotations is a list of claimed annotation + description: "annotations is a list of claimed annotation keys or annotation wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. \n By default, no annotations are synced." @@ -244,7 +247,7 @@ spec: type: object type: array fields: - description: "Fields are a list of JSON Paths describing + description: "fields are a list of JSON Paths describing which parts of an object the provider wants to control. \n This field is ignored if the owner in the claim selector is set to \"Provider\"." @@ -252,7 +255,7 @@ spec: type: string type: array labels: - description: "Labels is a list of claimed label keys or + description: "labels is a list of claimed label keys or label wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. \n By default, no labels are synced." @@ -263,7 +266,7 @@ spec: type: object type: array overrideAnnotations: - description: "OverrideAnnotations is a list of claiemd annotation + description: "overrideAnnotations is a list of claiemd annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. \n By default, no annotations are synced." @@ -274,7 +277,7 @@ spec: type: object type: array overrideLabels: - description: "OverrideLabels is a list of claiemd label + description: "overrideLabels is a list of claiemd label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. \n By default, no labels are synced." @@ -285,7 +288,7 @@ spec: type: object type: array preservings: - description: "Preserving is a list of JSON Paths describing + description: "preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. \n This field is ignored if the owner in the claim selector is set to \"Consumer\"." @@ -294,6 +297,7 @@ spec: type: array type: object version: + minLength: 1 type: string required: - resource diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index a57e3c7c7..46d92830b 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -89,7 +89,7 @@ spec: requested by the API service provider. Individual claims can be accepted or rejected. If accepted, the API service provider gets the requested access to the specified resources in this workspace. - Access is granted per GroupResource, identity, and other properties. + Access is granted per GroupResource and other properties like selectors. items: properties: autoAdopt: @@ -108,7 +108,7 @@ spec: provider side down to the consumer cluster." properties: replaceExisting: - description: "ReplaceExisting means that an existing object + description: "replaceExisting means that an existing object owned by the consumer will be replaced by the provider object. \n If set to false, and a conflicting consumer object exists, it is not touched." @@ -126,7 +126,7 @@ spec: properties: recreateWhenConsumerSideDeleted: default: true - description: "RecreateWhenConsumerSideDeleted set to true + description: "recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object in case the object is missing on the consumer side, but has been synchronized before. \n If set to false, deleted @@ -139,12 +139,15 @@ spec: type: boolean type: object read: + description: read contains extra labels and annotations the + provider will read from objects on the consumer cluster. By + default no labels and annotations are read. properties: annotations: - description: Annotations is a list of claimed annotation + description: annotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer side to the provider on objects that are owned by the - consumer + consumer. items: properties: pattern: @@ -152,9 +155,9 @@ spec: type: object type: array labels: - description: Labels is a list of claimed label key wildcard + description: labels is a list of claimed label key wildcard patterns that are synchronized from the consumer side - to the provider on objects that are owned by the consumer + to the provider on objects that are owned by the consumer. items: properties: pattern: @@ -173,7 +176,7 @@ spec: type: object type: array overrideAnnotations: - description: OverrideAnnotations is a list of claimed annotation + description: overrideAnnotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer side to the provider on objects owned by the provider. items: @@ -198,13 +201,13 @@ spec: this claim. properties: fieldSelectors: - description: FieldSelectors is a list of field selectors + description: fieldSelectors is a list of field selectors matching selected resources, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. items: type: string type: array labelSelectors: - description: LabelSelectors is a list of label selectors + description: labelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors, see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. items: @@ -215,7 +218,7 @@ spec: names: default: - '*' - description: Names is a list of specific resource names + description: names is a list of specific resource names to select. Names matches the metadata.name field of the underlying object. An entry of "*" anywhere in the list means all object names of the group/resource within the @@ -226,11 +229,11 @@ spec: type: array x-kubernetes-validations: - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z]*|[*]$')) + rule: self.all(n, n.matches('^[A-z-]+|[*]$')) namespaces: default: - '*' - description: Namespaces represents namespaces where an object + description: namespaces represents namespaces where an object of the given group/resoruce may be managed. Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all namespaces. @@ -243,7 +246,7 @@ spec: type: array x-kubernetes-validations: - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z]*|[*]$')) + rule: self.all(n, n.matches('^[A-z-]+|[*]$')) owner: enum: - Provider @@ -256,17 +259,17 @@ spec: - Rejected type: string update: - description: Update lists a number of claimed permissions for - the provider. "field" and "preserving" are mutually exclusive. + description: update lists which updates to objects on the consumer + side are claimed. properties: alwaysRecreate: - description: "AlwaysRecreate, when true will delete the + description: "alwaysRecreate, when true will delete the old object and create new ones instead of updating. Useful for immutable objects. \n This does not apply to metadata field updates." type: boolean annotations: - description: "Annotations is a list of claimed annotation + description: "annotations is a list of claimed annotation keys or annotation wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. \n By default, no annotations are synced." @@ -277,7 +280,7 @@ spec: type: object type: array fields: - description: "Fields are a list of JSON Paths describing + description: "fields are a list of JSON Paths describing which parts of an object the provider wants to control. \n This field is ignored if the owner in the claim selector is set to \"Provider\"." @@ -285,7 +288,7 @@ spec: type: string type: array labels: - description: "Labels is a list of claimed label keys or + description: "labels is a list of claimed label keys or label wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. \n By default, no labels are synced." @@ -296,7 +299,7 @@ spec: type: object type: array overrideAnnotations: - description: "OverrideAnnotations is a list of claiemd annotation + description: "overrideAnnotations is a list of claiemd annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. \n By default, no annotations are synced." @@ -307,7 +310,7 @@ spec: type: object type: array overrideLabels: - description: "OverrideLabels is a list of claiemd label + description: "overrideLabels is a list of claiemd label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. \n By default, no labels are synced." @@ -318,7 +321,7 @@ spec: type: object type: array preservings: - description: "Preserving is a list of JSON Paths describing + description: "preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. \n This field is ignored if the owner in the claim selector is set to \"Consumer\"." @@ -327,6 +330,7 @@ spec: type: array type: object version: + minLength: 1 type: string required: - resource diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 33dd63a15..025f1aca0 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -92,7 +92,7 @@ spec: from the provider side down to the consumer cluster." properties: replaceExisting: - description: "ReplaceExisting means that an existing + description: "replaceExisting means that an existing object owned by the consumer will be replaced by the provider object. \n If set to false, and a conflicting consumer object exists, it is not touched." @@ -110,7 +110,7 @@ spec: properties: recreateWhenConsumerSideDeleted: default: true - description: "RecreateWhenConsumerSideDeleted set + description: "recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object in case the object is missing on the consumer side, but has been synchronized before. @@ -124,12 +124,15 @@ spec: type: boolean type: object read: + description: read contains extra labels and annotations + the provider will read from objects on the consumer + cluster. By default no labels and annotations are read. properties: annotations: - description: Annotations is a list of claimed annotation + description: annotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer side to the provider on objects that - are owned by the consumer + are owned by the consumer. items: properties: pattern: @@ -137,10 +140,10 @@ spec: type: object type: array labels: - description: Labels is a list of claimed label key + description: labels is a list of claimed label key wildcard patterns that are synchronized from the consumer side to the provider on objects that are - owned by the consumer + owned by the consumer. items: properties: pattern: @@ -159,7 +162,7 @@ spec: type: object type: array overrideAnnotations: - description: OverrideAnnotations is a list of claimed + description: overrideAnnotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer side to the provider on objects owned by the provider. @@ -186,13 +189,13 @@ spec: by this claim. properties: fieldSelectors: - description: FieldSelectors is a list of field selectors + description: fieldSelectors is a list of field selectors matching selected resources, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. items: type: string type: array labelSelectors: - description: LabelSelectors is a list of label selectors + description: labelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors, see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. @@ -204,7 +207,7 @@ spec: names: default: - '*' - description: Names is a list of specific resource + description: names is a list of specific resource names to select. Names matches the metadata.name field of the underlying object. An entry of "*" anywhere in the list means all object names of the @@ -216,11 +219,11 @@ spec: type: array x-kubernetes-validations: - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z]*|[*]$')) + rule: self.all(n, n.matches('^[A-z-]+|[*]$')) namespaces: default: - '*' - description: Namespaces represents namespaces where + description: namespaces represents namespaces where an object of the given group/resoruce may be managed. Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects @@ -234,7 +237,7 @@ spec: type: array x-kubernetes-validations: - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z]*|[*]$')) + rule: self.all(n, n.matches('^[A-z-]+|[*]$')) owner: enum: - Provider @@ -242,18 +245,17 @@ spec: type: string type: object update: - description: Update lists a number of claimed permissions - for the provider. "field" and "preserving" are mutually - exclusive. + description: update lists which updates to objects on + the consumer side are claimed. properties: alwaysRecreate: - description: "AlwaysRecreate, when true will delete + description: "alwaysRecreate, when true will delete the old object and create new ones instead of updating. Useful for immutable objects. \n This does not apply to metadata field updates." type: boolean annotations: - description: "Annotations is a list of claimed annotation + description: "annotations is a list of claimed annotation keys or annotation wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. \n By default, no annotations are @@ -265,7 +267,7 @@ spec: type: object type: array fields: - description: "Fields are a list of JSON Paths describing + description: "fields are a list of JSON Paths describing which parts of an object the provider wants to control. \n This field is ignored if the owner in the claim selector is set to \"Provider\"." @@ -273,7 +275,7 @@ spec: type: string type: array labels: - description: "Labels is a list of claimed label keys + description: "labels is a list of claimed label keys or label wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. \n By default, no labels are synced." @@ -284,7 +286,7 @@ spec: type: object type: array overrideAnnotations: - description: "OverrideAnnotations is a list of claiemd + description: "overrideAnnotations is a list of claiemd annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. \n By default, no annotations are @@ -296,7 +298,7 @@ spec: type: object type: array overrideLabels: - description: "OverrideLabels is a list of claiemd + description: "overrideLabels is a list of claiemd label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. \n By default, no labels are synced." @@ -307,7 +309,7 @@ spec: type: object type: array preservings: - description: "Preserving is a list of JSON Paths describing + description: "preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. \n This field is ignored if the owner in the claim selector is set to \"Consumer\"." @@ -316,6 +318,7 @@ spec: type: array type: object version: + minLength: 1 type: string required: - resource diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index b6cd948cf..4ef2ff8c7 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -130,7 +130,7 @@ spec: provider side down to the consumer cluster." properties: replaceExisting: - description: "ReplaceExisting means that an existing object + description: "replaceExisting means that an existing object owned by the consumer will be replaced by the provider object. \n If set to false, and a conflicting consumer object exists, it is not touched." @@ -148,7 +148,7 @@ spec: properties: recreateWhenConsumerSideDeleted: default: true - description: "RecreateWhenConsumerSideDeleted set to true + description: "recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object in case the object is missing on the consumer side, but has been synchronized before. \n If set to false, deleted @@ -161,12 +161,15 @@ spec: type: boolean type: object read: + description: read contains extra labels and annotations the + provider will read from objects on the consumer cluster. By + default no labels and annotations are read. properties: annotations: - description: Annotations is a list of claimed annotation + description: annotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer side to the provider on objects that are owned by the - consumer + consumer. items: properties: pattern: @@ -174,9 +177,9 @@ spec: type: object type: array labels: - description: Labels is a list of claimed label key wildcard + description: labels is a list of claimed label key wildcard patterns that are synchronized from the consumer side - to the provider on objects that are owned by the consumer + to the provider on objects that are owned by the consumer. items: properties: pattern: @@ -195,7 +198,7 @@ spec: type: object type: array overrideAnnotations: - description: OverrideAnnotations is a list of claimed annotation + description: overrideAnnotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer side to the provider on objects owned by the provider. items: @@ -220,13 +223,13 @@ spec: this claim. properties: fieldSelectors: - description: FieldSelectors is a list of field selectors + description: fieldSelectors is a list of field selectors matching selected resources, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. items: type: string type: array labelSelectors: - description: LabelSelectors is a list of label selectors + description: labelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors, see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. items: @@ -237,7 +240,7 @@ spec: names: default: - '*' - description: Names is a list of specific resource names + description: names is a list of specific resource names to select. Names matches the metadata.name field of the underlying object. An entry of "*" anywhere in the list means all object names of the group/resource within the @@ -248,11 +251,11 @@ spec: type: array x-kubernetes-validations: - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z]*|[*]$')) + rule: self.all(n, n.matches('^[A-z-]+|[*]$')) namespaces: default: - '*' - description: Namespaces represents namespaces where an object + description: namespaces represents namespaces where an object of the given group/resoruce may be managed. Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all namespaces. @@ -265,7 +268,7 @@ spec: type: array x-kubernetes-validations: - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z]*|[*]$')) + rule: self.all(n, n.matches('^[A-z-]+|[*]$')) owner: enum: - Provider @@ -273,17 +276,17 @@ spec: type: string type: object update: - description: Update lists a number of claimed permissions for - the provider. "field" and "preserving" are mutually exclusive. + description: update lists which updates to objects on the consumer + side are claimed. properties: alwaysRecreate: - description: "AlwaysRecreate, when true will delete the + description: "alwaysRecreate, when true will delete the old object and create new ones instead of updating. Useful for immutable objects. \n This does not apply to metadata field updates." type: boolean annotations: - description: "Annotations is a list of claimed annotation + description: "annotations is a list of claimed annotation keys or annotation wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. \n By default, no annotations are synced." @@ -294,7 +297,7 @@ spec: type: object type: array fields: - description: "Fields are a list of JSON Paths describing + description: "fields are a list of JSON Paths describing which parts of an object the provider wants to control. \n This field is ignored if the owner in the claim selector is set to \"Provider\"." @@ -302,7 +305,7 @@ spec: type: string type: array labels: - description: "Labels is a list of claimed label keys or + description: "labels is a list of claimed label keys or label wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. \n By default, no labels are synced." @@ -313,7 +316,7 @@ spec: type: object type: array overrideAnnotations: - description: "OverrideAnnotations is a list of claiemd annotation + description: "overrideAnnotations is a list of claiemd annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. \n By default, no annotations are synced." @@ -324,7 +327,7 @@ spec: type: object type: array overrideLabels: - description: "OverrideLabels is a list of claiemd label + description: "overrideLabels is a list of claiemd label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. \n By default, no labels are synced." @@ -335,7 +338,7 @@ spec: type: object type: array preservings: - description: "Preserving is a list of JSON Paths describing + description: "preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. \n This field is ignored if the owner in the claim selector is set to \"Consumer\"." @@ -344,6 +347,7 @@ spec: type: array type: object version: + minLength: 1 type: string required: - resource diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 506a3559b..41857e4f9 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -91,7 +91,7 @@ type APIServiceBindingSpec struct { // permissionClaims records decisions about permission claims requested by the API service provider. // Individual claims can be accepted or rejected. If accepted, the API service provider gets the // requested access to the specified resources in this workspace. Access is granted per - // GroupResource, identity, and other properties. + // GroupResource and other properties like selectors. // // +optional PermissionClaims []AcceptablePermissionClaim `json:"permissionClaims,omitempty"` @@ -123,15 +123,20 @@ type PermissionClaim struct { GroupResource `json:","` // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength:=1 Version string `json:"version"` // Selector selects which resources are affected by this claim. // +optional + // +kubebuilder:default:={} Selector *ResourceSelector `json:"selector,omitempty"` // Required indicates whether the APIServiceBinding will work if this claim is not accepted. Required bool `json:"required"` + // read contains extra labels and annotations the provider will read from objects on the consumer cluster. + // By default no labels and annotations are read. + // // +optional // +kubebuilder:default={} Read *ReadOptions `json:"read,omitempty"` @@ -160,19 +165,19 @@ type PermissionClaim struct { // will be resolved. // // +optional + // +kubebuilder:default:={} OnConflict *OnConflictOptions `json:"onConflict,omitempty"` - // Update lists a number of claimed permissions for the provider. - // "field" and "preserving" are mutually exclusive. + // update lists which updates to objects on the consumer side are claimed. // // +optional Update *UpdateOptions `json:"update,omitempty"` } type ReadOptions struct { - // Labels is a list of claimed label key wildcard patterns + // labels is a list of claimed label key wildcard patterns // that are synchronized from the consumer side to the provider on - // objects that are owned by the consumer + // objects that are owned by the consumer. // // +optional Labels []Matcher `json:"labels,omitempty"` @@ -184,14 +189,14 @@ type ReadOptions struct { // +optional LabelsOnProviderOwnedObjects []Matcher `json:"labelsOnProviderOwnedObjects,omitempty"` - // Annotations is a list of claimed annotation key wildcard patterns + // annotations is a list of claimed annotation key wildcard patterns // that are synchronized from the consumer side to the provider on - // objects that are owned by the consumer + // objects that are owned by the consumer. // // +optional Annotations []Matcher `json:"annotations,omitempty"` - // OverrideAnnotations is a list of claimed annotation key wildcard + // overrideAnnotations is a list of claimed annotation key wildcard // patterns that are synchronized from the consumer side // to the provider on objects owned by the provider. // @@ -205,7 +210,7 @@ type Matcher struct { } type OnConflictOptions struct { - // RecreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object + // recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object // in case the object is missing on the consumer side, but has been synchronized before. // // If set to false, deleted provider-owned objects get deleted on the provider side as well. @@ -220,52 +225,59 @@ type OnConflictOptions struct { } type CreateOptions struct { - // ReplaceExisting means that an existing object owned by the consumer will be replaced by the provider object. + // replaceExisting means that an existing object owned by the consumer will be replaced by the provider object. // // If set to false, and a conflicting consumer object exists, it is not touched. + // // +optional ReplaceExisting bool `json:"replaceExisting,omitempty"` } type UpdateOptions struct { - // Fields are a list of JSON Paths describing which parts of an object the provider wants to control. + // fields are a list of JSON Paths describing which parts of an object the provider wants to control. // // This field is ignored if the owner in the claim selector is set to "Provider". + // + // +optional Fields []string `json:"fields,omitempty"` - // Preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. + // preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. // // This field is ignored if the owner in the claim selector is set to "Consumer". + // + // +optional Preserving []string `json:"preservings,omitempty"` - // AlwaysRecreate, when true will delete the old object and create new ones + // alwaysRecreate, when true will delete the old object and create new ones // instead of updating. Useful for immutable objects. // // This does not apply to metadata field updates. + // + // +optional AlwaysRecreate bool `json:"alwaysRecreate,omitempty"` - // Labels is a list of claimed label keys or label wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. + // labels is a list of claimed label keys or label wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. // // By default, no labels are synced. // // +optional Labels []Matcher `json:"labels,omitempty"` - // OverrideLabels is a list of claiemd label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. + // overrideLabels is a list of claiemd label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. // // By default, no labels are synced. // // +optional OverrideLabels []Matcher `json:"overrideLabels,omitempty"` - // Annotations is a list of claimed annotation keys or annotation wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. + // annotations is a list of claimed annotation keys or annotation wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. // // By default, no annotations are synced. // // +optional Annotations []Matcher `json:"annotations,omitempty"` - // OverrideAnnotations is a list of claiemd annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. + // overrideAnnotations is a list of claiemd annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. // // By default, no annotations are synced. // @@ -274,31 +286,31 @@ type UpdateOptions struct { } type ResourceSelector struct { - // Names is a list of specific resource names to select. + // names is a list of specific resource names to select. // Names matches the metadata.name field of the underlying object. // An entry of "*" anywhere in the list means all object names of the group/resource within the "namespaces" field are claimed. // Wildcard entries other than "*" and regular expressions are currently unsupported. // - // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z]*|[*]$'))",message="only names or * are allowed" + // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z-]+|[*]$'))",message="only names or * are allowed" // +kubebuilder:default:={"*"} // +optional Names []string `json:"names,omitempty"` - // Namespaces represents namespaces where an object of the given group/resoruce may be managed. + // namespaces represents namespaces where an object of the given group/resoruce may be managed. // Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all // namespaces. If namespaces is not set (an empty list), matches cluster-scoped resources. // If the "names" field is unset, all objects of the group/resource within the listed namespaces (or cluster) will be claimed. // - // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z]*|[*]$'))",message="only names or * are allowed" + // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z-]+|[*]$'))",message="only names or * are allowed" // +kubebuilder:default:={"*"} // +optional Namespaces []string `json:"namespaces,omitempty"` - // LabelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors, + // labelSelectors is a list of label selectors matching selected resources. label selectors follow the same rules as kubernetes label selectors, // see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/. LabelSelectors []map[string]string `json:"labelSelectors,omitempty"` - // FieldSelectors is a list of field selectors matching selected resources, + // fieldSelectors is a list of field selectors matching selected resources, // see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. FieldSelectors []string `json:"fieldSelectors,omitempty"` @@ -309,10 +321,10 @@ type ResourceSelector struct { type Owner string -// Provider means that the owner of the resource is the Provider. +// provider means that the owner of the resource is the Provider. const Provider Owner = "Provider" -// Consumer means that the owner of the resource is the Consumer. +// consumer means that the owner of the resource is the Consumer. const Consumer Owner = "Consumer" type APIServiceBindingStatus struct { From 498f5957a19604da19857bb18e395ef091e7ad2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Thu, 3 Aug 2023 10:32:28 +0200 Subject: [PATCH 19/26] Update api definititions --- ...example.com_apiserviceexporttemplates.yaml | 169 +++++++++++------- .../apiserviceexporttemplate_types.go | 15 +- .../v1alpha1/zz_generated.deepcopy.go | 8 +- .../exporttemplate/index_test.go | 7 +- .../crd/kube-bind.io_apiservicebindings.yaml | 149 ++++++++------- ...kube-bind.io_apiserviceexportrequests.yaml | 158 +++++++++------- .../crd/kube-bind.io_apiserviceexports.yaml | 147 ++++++++------- .../v1alpha1/apiservicebinding_types.go | 91 ++++++---- .../v1alpha1/apiserviceexportrequest_types.go | 2 +- .../v1alpha1/zz_generated.deepcopy.go | 8 +- 10 files changed, 443 insertions(+), 311 deletions(-) diff --git a/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml b/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml index 0e9dc7089..656da5a05 100644 --- a/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml +++ b/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml @@ -48,36 +48,51 @@ spec: description: spec specifies the resource. properties: APIServiceSelector: + description: apiServiceSelector describes the groupresource and versions + of the api that will be offered to bind to consumer clusters. 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: - items: - type: string - type: array + version: + minLength: 1 + type: string + required: + - resource type: object permissionClaims: + description: permissionClaims are a list of permission claims for + the provider to read or create/update additional resources on the + consumers cluster. Empty by default. items: - description: PermissionClaim selects objects of a GVR that a service + 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: autoAdopt: - description: AutoAdopt set to true means that objects created + description: autoAdopt set to true means that objects created by the consumer are adopted by the provider. i.e. the provider - will become the owner. + will become the owner. Mutually exclusive with autoDonate. type: boolean autoDonate: - description: AutoDonate set to true means that a newly created + description: autoDonate set to true means that a newly created object by the provider is immediately owned by the consumer. - If false, the object stays in ownership of the provider. + If false, the object stays in ownership of the provider. Mutually + exclusive with autoDonate. type: boolean create: - description: "only for owner Provider \n Create determines whether - the kube-bind konnector will sync matching objects from the - provider side down to the consumer cluster." + description: create determines whether the kube-bind konnector + will sync matching objects from the provider cluster down + to the consumer cluster. only for owner Provider properties: replaceExisting: description: "replaceExisting means that an existing object @@ -94,31 +109,35 @@ spec: type: string onConflict: description: onConflict determines how the conflicts between - objects on the consumer side will be resolved. + objects on the consumer cluster will be resolved. properties: recreateWhenConsumerSideDeleted: default: true description: "recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object - in case the object is missing on the consumer side, but - has been synchronized before. \n If set to false, deleted - provider-owned objects get deleted on the provider side - as well. \n Even if the consumer mistakenly or intentionally - deletes the object, the provider will recreate it. If - the field is set as false, the provider will not recreate - the object in case the object is deleted on the RecreateWhenConsumerSideDeleted - side." + in case the object is missing on the consumer cluster, + but has been synchronized before. \n If set to false, + deleted provider-owned objects get deleted on the provider + cluster as well. \n Even if the consumer mistakenly or + intentionally deletes the object, the provider will recreate + it. If the field is set as false, the provider will not + recreate the object in case the object is deleted on the + consumer cluster." type: boolean type: object read: - description: read contains extra labels and annotations the - provider will read from objects on the consumer cluster. By - default no labels and annotations are read. + description: read claims read access to matching objects for + the provider. reading of the claimed object(s) is always claimed. + By default no labels and annotations are read. Reading of + labels and annotations can be claimed optionally by adding + labels and annotations items. If labels on consumer owned + objects that are set by the consumer are read, labelsOnProviderOwnedObjects + and annotationsOnProviderOwnedObjects can be set. properties: annotations: description: annotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer - side to the provider on objects that are owned by the + cluster to the provider on objects that are owned by the consumer. items: properties: @@ -128,7 +147,7 @@ spec: type: array labels: description: labels is a list of claimed label key wildcard - patterns that are synchronized from the consumer side + patterns that are synchronized from the consumer cluster to the provider on objects that are owned by the consumer. items: properties: @@ -139,8 +158,8 @@ spec: labelsOnProviderOwnedObjects: description: labelsOnProviderOwnedObjects is a list of claimed label key wildcard patterns that are synchronized from - the consumer side to the provider on objects owned by - the provider. + the consumer cluster to the provider on objects owned + by the provider. items: properties: pattern: @@ -150,7 +169,7 @@ spec: overrideAnnotations: description: overrideAnnotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer - side to the provider on objects owned by the provider. + cluster to the provider on objects owned by the provider. items: properties: pattern: @@ -159,8 +178,9 @@ spec: type: array type: object required: - description: Required indicates whether the APIServiceBinding - will work if this claim is not accepted. + description: required indicates whether the APIServiceBinding + will work if this claim is not accepted. If a required claim + is denied, the binding is aborted. type: boolean resource: description: 'resource is the name of the resource. Note: it @@ -169,8 +189,8 @@ spec: pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string selector: - description: Selector selects which resources are affected by - this claim. + description: selector selects which resources are being claimed. + If unset, all resources across all namespaces are being claimed. properties: fieldSelectors: description: fieldSelectors is a list of field selectors @@ -190,36 +210,49 @@ spec: names: default: - '*' - description: names is a list of specific resource names + description: "names is a list of specific resource names to select. Names matches the metadata.name field of the - underlying object. An entry of "*" anywhere in the list + underlying object. An entry of \"*\" anywhere in the list means all object names of the group/resource within the - "namespaces" field are claimed. Wildcard entries other - than "*" and regular expressions are currently unsupported. + \"namespaces\" field are claimed. Wildcard entries other + than \"*\" and regular expressions are currently unsupported. + If a resources name matches any value in names, the resource + name is considered matching. \n // +kubebuilder:validation:XValidation:rule=\"self.all(n, + n.matches('^[A-z-]+|[*]$'))\",message=\"only names or + * are allowed\"" items: type: string type: array - x-kubernetes-validations: - - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z-]+|[*]$')) namespaces: default: - '*' - description: namespaces represents namespaces where an object - of the given group/resoruce may be managed. Namespaces + description: "namespaces represents namespaces where an + object of the given group/resource may be managed. Namespaces matches against the metadata.namespace field. A value - of "*" matches namespaced objects across all namespaces. - If namespaces is not set (an empty list), matches cluster-scoped - resources. If the "names" field is unset, all objects + of \"*\" matches namespaced objects across all namespaces. + If a resources namespace matches any value in namespaces, + the resource namespace is considered matching. If the + claim is for a cluster-scoped resource, namespaces has + to explicitly be set to an empty array to prevent defaulting + to \"*\". If the \"names\" field is unset, all objects of the group/resource within the listed namespaces (or - cluster) will be claimed. + cluster) will be claimed. \n // +kubebuilder:validation:XValidation:rule=\"self.all(n, + n.matches('^[A-z-]+|[*]$'))\",message=\"only names or + * are allowed\"" items: type: string type: array - x-kubernetes-validations: - - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z-]+|[*]$')) owner: + description: owner matches the resource's owner. If an owner + selector is set, resources owned by other owners will + not be claimed. Resources without a present owner will + be considered, if configured owner could be the owner + of the object. For example, if the consumer creates a + resource that is claimed by the provider for reading. + In this case the resource will be marked as owned by the + consumer, and handled as such in further reconciliations. + An unset owner selector means objects from both sides + are considered. enum: - Provider - Consumer @@ -227,7 +260,8 @@ spec: type: object update: description: update lists which updates to objects on the consumer - side are claimed. + cluster are claimed. By default, the whole object is synced, + but metadata is not. properties: alwaysRecreate: description: "alwaysRecreate, when true will delete the @@ -246,6 +280,17 @@ spec: type: string type: object type: array + annotationsOnConsumerOwnedObjects: + description: "annotationsOnConsumerOwnedObjects is a list + of claimed annotation key wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the consumer. \n By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array fields: description: "fields are a list of JSON Paths describing which parts of an object the provider wants to control. @@ -265,29 +310,18 @@ spec: type: string type: object type: array - overrideAnnotations: - description: "overrideAnnotations is a list of claiemd annotation - key wildcard patterns that are synchronized from the provider - to the consumer for objects owned by the consumer. \n - By default, no annotations are synced." - items: - properties: - pattern: - type: string - type: object - type: array - overrideLabels: - description: "overrideLabels is a list of claiemd label - key wildcard patterns that are synchronized from the provider - to the consumer for objects owned by the consumer. \n - By default, no labels are synced." + labelsOnConsumerOwnedObjects: + description: "labelsOnConsumerOwnedObjects is a list of + claimed label key wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the consumer. \n By default, no labels are synced." items: properties: pattern: type: string type: object type: array - preservings: + preserving: description: "preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. \n This field is ignored if the owner @@ -297,6 +331,7 @@ spec: type: array type: object version: + description: version is the version of the claimed resource. minLength: 1 type: string required: diff --git a/contrib/example-backend/apis/examplebackend/v1alpha1/apiserviceexporttemplate_types.go b/contrib/example-backend/apis/examplebackend/v1alpha1/apiserviceexporttemplate_types.go index 60e05bb2d..c378242f7 100644 --- a/contrib/example-backend/apis/examplebackend/v1alpha1/apiserviceexporttemplate_types.go +++ b/contrib/example-backend/apis/examplebackend/v1alpha1/apiserviceexporttemplate_types.go @@ -47,7 +47,14 @@ type APIServiceExportTemplate struct { } type APIServiceExportTemplateSpec struct { + // apiServiceSelector describes the groupresource and versions of the api that will be offered to bind to consumer clusters. + // + // +required APIServiceSelector APIServiceSelector `json:"APIServiceSelector"` + + // permissionClaims are a list of permission claims for the provider to read or create/update additional resources on the + // consumers cluster. Empty by default. + // // +optional PermissionClaims []v1alpha1.PermissionClaim `json:"permissionClaims,omitempty"` } @@ -55,9 +62,11 @@ type APIServiceExportTemplateSpec struct { type APIServiceExportTemplateStatus struct{} type APIServiceSelector struct { - Group string `json:"group"` - Resource string `json:"resource"` - Versions []string `json:"versions"` + v1alpha1.GroupResource `json:","` + + // +required + // +kubebuilder:validation:MinLength:=1 + Version string `json:"version"` } // APIServiceExportRequestList is the list of APIServiceExportRequest. diff --git a/contrib/example-backend/apis/examplebackend/v1alpha1/zz_generated.deepcopy.go b/contrib/example-backend/apis/examplebackend/v1alpha1/zz_generated.deepcopy.go index 5b23c5f21..718cb9e13 100644 --- a/contrib/example-backend/apis/examplebackend/v1alpha1/zz_generated.deepcopy.go +++ b/contrib/example-backend/apis/examplebackend/v1alpha1/zz_generated.deepcopy.go @@ -91,7 +91,7 @@ func (in *APIServiceExportTemplateList) DeepCopyObject() runtime.Object { // 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 - in.APIServiceSelector.DeepCopyInto(&out.APIServiceSelector) + out.APIServiceSelector = in.APIServiceSelector if in.PermissionClaims != nil { in, out := &in.PermissionClaims, &out.PermissionClaims *out = make([]kubebindv1alpha1.PermissionClaim, len(*in)) @@ -131,11 +131,7 @@ func (in *APIServiceExportTemplateStatus) DeepCopy() *APIServiceExportTemplateSt // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *APIServiceSelector) DeepCopyInto(out *APIServiceSelector) { *out = *in - if in.Versions != nil { - in, out := &in.Versions, &out.Versions - *out = make([]string, len(*in)) - copy(*out, *in) - } + out.GroupResource = in.GroupResource return } diff --git a/contrib/example-backend/exporttemplate/index_test.go b/contrib/example-backend/exporttemplate/index_test.go index 680967ee8..7e100e351 100644 --- a/contrib/example-backend/exporttemplate/index_test.go +++ b/contrib/example-backend/exporttemplate/index_test.go @@ -28,6 +28,7 @@ import ( "github.com/kube-bind/kube-bind/contrib/example-backend/apis/examplebackend/v1alpha1" templates "github.com/kube-bind/kube-bind/contrib/example-backend/client/clientset/versioned/fake" + kubebindv1alpha1 "github.com/kube-bind/kube-bind/pkg/apis/kubebind/v1alpha1" ) var mangodb = apiextensions.CustomResourceDefinition{ @@ -61,8 +62,10 @@ var dummy = apiextensions.CustomResourceDefinition{ var export = v1alpha1.APIServiceExportTemplate{ Spec: v1alpha1.APIServiceExportTemplateSpec{ APIServiceSelector: v1alpha1.APIServiceSelector{ - Resource: "mangodbs", - Group: "mangodb.com", + GroupResource: kubebindv1alpha1.GroupResource{ + Resource: "mangodbs", + Group: "mangodb.com", + }, }, }, ObjectMeta: v1.ObjectMeta{ diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index 46d92830b..bc67a56cf 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -91,21 +91,25 @@ spec: the requested access to the specified resources in this workspace. Access is granted per GroupResource and other properties like selectors. items: + description: acceptablePermissionClaim is a permission claim that + stores the users acceptance in the field state. Only accepted + permission claims are reconciled. properties: autoAdopt: - description: AutoAdopt set to true means that objects created + description: autoAdopt set to true means that objects created by the consumer are adopted by the provider. i.e. the provider - will become the owner. + will become the owner. Mutually exclusive with autoDonate. type: boolean autoDonate: - description: AutoDonate set to true means that a newly created + description: autoDonate set to true means that a newly created object by the provider is immediately owned by the consumer. - If false, the object stays in ownership of the provider. + If false, the object stays in ownership of the provider. Mutually + exclusive with autoDonate. type: boolean create: - description: "only for owner Provider \n Create determines whether - the kube-bind konnector will sync matching objects from the - provider side down to the consumer cluster." + description: create determines whether the kube-bind konnector + will sync matching objects from the provider cluster down + to the consumer cluster. only for owner Provider properties: replaceExisting: description: "replaceExisting means that an existing object @@ -122,31 +126,35 @@ spec: type: string onConflict: description: onConflict determines how the conflicts between - objects on the consumer side will be resolved. + objects on the consumer cluster will be resolved. properties: recreateWhenConsumerSideDeleted: default: true description: "recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object - in case the object is missing on the consumer side, but - has been synchronized before. \n If set to false, deleted - provider-owned objects get deleted on the provider side - as well. \n Even if the consumer mistakenly or intentionally - deletes the object, the provider will recreate it. If - the field is set as false, the provider will not recreate - the object in case the object is deleted on the RecreateWhenConsumerSideDeleted - side." + in case the object is missing on the consumer cluster, + but has been synchronized before. \n If set to false, + deleted provider-owned objects get deleted on the provider + cluster as well. \n Even if the consumer mistakenly or + intentionally deletes the object, the provider will recreate + it. If the field is set as false, the provider will not + recreate the object in case the object is deleted on the + consumer cluster." type: boolean type: object read: - description: read contains extra labels and annotations the - provider will read from objects on the consumer cluster. By - default no labels and annotations are read. + description: read claims read access to matching objects for + the provider. reading of the claimed object(s) is always claimed. + By default no labels and annotations are read. Reading of + labels and annotations can be claimed optionally by adding + labels and annotations items. If labels on consumer owned + objects that are set by the consumer are read, labelsOnProviderOwnedObjects + and annotationsOnProviderOwnedObjects can be set. properties: annotations: description: annotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer - side to the provider on objects that are owned by the + cluster to the provider on objects that are owned by the consumer. items: properties: @@ -156,7 +164,7 @@ spec: type: array labels: description: labels is a list of claimed label key wildcard - patterns that are synchronized from the consumer side + patterns that are synchronized from the consumer cluster to the provider on objects that are owned by the consumer. items: properties: @@ -167,8 +175,8 @@ spec: labelsOnProviderOwnedObjects: description: labelsOnProviderOwnedObjects is a list of claimed label key wildcard patterns that are synchronized from - the consumer side to the provider on objects owned by - the provider. + the consumer cluster to the provider on objects owned + by the provider. items: properties: pattern: @@ -178,7 +186,7 @@ spec: overrideAnnotations: description: overrideAnnotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer - side to the provider on objects owned by the provider. + cluster to the provider on objects owned by the provider. items: properties: pattern: @@ -187,8 +195,9 @@ spec: type: array type: object required: - description: Required indicates whether the APIServiceBinding - will work if this claim is not accepted. + description: required indicates whether the APIServiceBinding + will work if this claim is not accepted. If a required claim + is denied, the binding is aborted. type: boolean resource: description: 'resource is the name of the resource. Note: it @@ -197,8 +206,8 @@ spec: pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string selector: - description: Selector selects which resources are affected by - this claim. + description: selector selects which resources are being claimed. + If unset, all resources across all namespaces are being claimed. properties: fieldSelectors: description: fieldSelectors is a list of field selectors @@ -218,49 +227,64 @@ spec: names: default: - '*' - description: names is a list of specific resource names + description: "names is a list of specific resource names to select. Names matches the metadata.name field of the - underlying object. An entry of "*" anywhere in the list + underlying object. An entry of \"*\" anywhere in the list means all object names of the group/resource within the - "namespaces" field are claimed. Wildcard entries other - than "*" and regular expressions are currently unsupported. + \"namespaces\" field are claimed. Wildcard entries other + than \"*\" and regular expressions are currently unsupported. + If a resources name matches any value in names, the resource + name is considered matching. \n // +kubebuilder:validation:XValidation:rule=\"self.all(n, + n.matches('^[A-z-]+|[*]$'))\",message=\"only names or + * are allowed\"" items: type: string type: array - x-kubernetes-validations: - - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z-]+|[*]$')) namespaces: default: - '*' - description: namespaces represents namespaces where an object - of the given group/resoruce may be managed. Namespaces + description: "namespaces represents namespaces where an + object of the given group/resource may be managed. Namespaces matches against the metadata.namespace field. A value - of "*" matches namespaced objects across all namespaces. - If namespaces is not set (an empty list), matches cluster-scoped - resources. If the "names" field is unset, all objects + of \"*\" matches namespaced objects across all namespaces. + If a resources namespace matches any value in namespaces, + the resource namespace is considered matching. If the + claim is for a cluster-scoped resource, namespaces has + to explicitly be set to an empty array to prevent defaulting + to \"*\". If the \"names\" field is unset, all objects of the group/resource within the listed namespaces (or - cluster) will be claimed. + cluster) will be claimed. \n // +kubebuilder:validation:XValidation:rule=\"self.all(n, + n.matches('^[A-z-]+|[*]$'))\",message=\"only names or + * are allowed\"" items: type: string type: array - x-kubernetes-validations: - - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z-]+|[*]$')) owner: + description: owner matches the resource's owner. If an owner + selector is set, resources owned by other owners will + not be claimed. Resources without a present owner will + be considered, if configured owner could be the owner + of the object. For example, if the consumer creates a + resource that is claimed by the provider for reading. + In this case the resource will be marked as owned by the + consumer, and handled as such in further reconciliations. + An unset owner selector means objects from both sides + are considered. enum: - Provider - Consumer type: string type: object state: + description: state indicates if the claim is accepted or rejected. enum: - Accepted - Rejected type: string update: description: update lists which updates to objects on the consumer - side are claimed. + cluster are claimed. By default, the whole object is synced, + but metadata is not. properties: alwaysRecreate: description: "alwaysRecreate, when true will delete the @@ -279,6 +303,17 @@ spec: type: string type: object type: array + annotationsOnConsumerOwnedObjects: + description: "annotationsOnConsumerOwnedObjects is a list + of claimed annotation key wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the consumer. \n By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array fields: description: "fields are a list of JSON Paths describing which parts of an object the provider wants to control. @@ -298,29 +333,18 @@ spec: type: string type: object type: array - overrideAnnotations: - description: "overrideAnnotations is a list of claiemd annotation - key wildcard patterns that are synchronized from the provider - to the consumer for objects owned by the consumer. \n - By default, no annotations are synced." - items: - properties: - pattern: - type: string - type: object - type: array - overrideLabels: - description: "overrideLabels is a list of claiemd label - key wildcard patterns that are synchronized from the provider - to the consumer for objects owned by the consumer. \n - By default, no labels are synced." + labelsOnConsumerOwnedObjects: + description: "labelsOnConsumerOwnedObjects is a list of + claimed label key wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the consumer. \n By default, no labels are synced." items: properties: pattern: type: string type: object type: array - preservings: + preserving: description: "preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. \n This field is ignored if the owner @@ -330,6 +354,7 @@ spec: type: array type: object version: + description: version is the version of the claimed resource. minLength: 1 type: string required: diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 025f1aca0..bbddb1d1d 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -65,31 +65,32 @@ spec: type: string permissionClaims: description: permissionClaims records decisions about permission - claims requested by the API service provider. Individual claims + claims requested by the service provider. Individual claims can be accepted or rejected. If accepted, the API service provider gets the requested access to the specified resources in this workspace. Access is granted per GroupResource, identity, and other properties. items: - description: PermissionClaim selects objects of a GVR that + 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: autoAdopt: - description: AutoAdopt set to true means that objects + description: autoAdopt set to true means that objects created by the consumer are adopted by the provider. - i.e. the provider will become the owner. + i.e. the provider will become the owner. Mutually exclusive + with autoDonate. type: boolean autoDonate: - description: AutoDonate set to true means that a newly + description: autoDonate set to true means that a newly created object by the provider is immediately owned by the consumer. If false, the object stays in ownership - of the provider. + of the provider. Mutually exclusive with autoDonate. type: boolean create: - description: "only for owner Provider \n Create determines - whether the kube-bind konnector will sync matching objects - from the provider side down to the consumer cluster." + description: create determines whether the kube-bind konnector + will sync matching objects from the provider cluster + down to the consumer cluster. only for owner Provider properties: replaceExisting: description: "replaceExisting means that an existing @@ -106,33 +107,38 @@ spec: type: string onConflict: description: onConflict determines how the conflicts between - objects on the consumer side will be resolved. + objects on the consumer cluster will be resolved. properties: recreateWhenConsumerSideDeleted: default: true description: "recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object in case the object is missing on the - consumer side, but has been synchronized before. + consumer cluster, but has been synchronized before. \n If set to false, deleted provider-owned objects - get deleted on the provider side as well. \n Even - if the consumer mistakenly or intentionally deletes - the object, the provider will recreate it. If the - field is set as false, the provider will not recreate - the object in case the object is deleted on the - RecreateWhenConsumerSideDeleted side." + get deleted on the provider cluster as well. \n + Even if the consumer mistakenly or intentionally + deletes the object, the provider will recreate it. + If the field is set as false, the provider will + not recreate the object in case the object is deleted + on the consumer cluster." type: boolean type: object read: - description: read contains extra labels and annotations - the provider will read from objects on the consumer - cluster. By default no labels and annotations are read. + description: read claims read access to matching objects + for the provider. reading of the claimed object(s) is + always claimed. By default no labels and annotations + are read. Reading of labels and annotations can be claimed + optionally by adding labels and annotations items. If + labels on consumer owned objects that are set by the + consumer are read, labelsOnProviderOwnedObjects and + annotationsOnProviderOwnedObjects can be set. properties: annotations: description: annotations is a list of claimed annotation key wildcard patterns that are synchronized from - the consumer side to the provider on objects that - are owned by the consumer. + the consumer cluster to the provider on objects + that are owned by the consumer. items: properties: pattern: @@ -142,8 +148,8 @@ spec: labels: description: labels is a list of claimed label key wildcard patterns that are synchronized from the - consumer side to the provider on objects that are - owned by the consumer. + consumer cluster to the provider on objects that + are owned by the consumer. items: properties: pattern: @@ -153,7 +159,7 @@ spec: labelsOnProviderOwnedObjects: description: labelsOnProviderOwnedObjects is a list of claimed label key wildcard patterns that are - synchronized from the consumer side to the provider + synchronized from the consumer cluster to the provider on objects owned by the provider. items: properties: @@ -164,7 +170,7 @@ spec: overrideAnnotations: description: overrideAnnotations is a list of claimed annotation key wildcard patterns that are synchronized - from the consumer side to the provider on objects + from the consumer cluster to the provider on objects owned by the provider. items: properties: @@ -174,8 +180,9 @@ spec: type: array type: object required: - description: Required indicates whether the APIServiceBinding - will work if this claim is not accepted. + description: required indicates whether the APIServiceBinding + will work if this claim is not accepted. If a required + claim is denied, the binding is aborted. type: boolean resource: description: 'resource is the name of the resource. Note: @@ -185,8 +192,9 @@ spec: pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string selector: - description: Selector selects which resources are affected - by this claim. + description: selector selects which resources are being + claimed. If unset, all resources across all namespaces + are being claimed. properties: fieldSelectors: description: fieldSelectors is a list of field selectors @@ -207,38 +215,51 @@ spec: names: default: - '*' - description: names is a list of specific resource + description: "names is a list of specific resource names to select. Names matches the metadata.name - field of the underlying object. An entry of "*" + field of the underlying object. An entry of \"*\" anywhere in the list means all object names of the - group/resource within the "namespaces" field are - claimed. Wildcard entries other than "*" and regular - expressions are currently unsupported. + group/resource within the \"namespaces\" field are + claimed. Wildcard entries other than \"*\" and regular + expressions are currently unsupported. If a resources + name matches any value in names, the resource name + is considered matching. \n // +kubebuilder:validation:XValidation:rule=\"self.all(n, + n.matches('^[A-z-]+|[*]$'))\",message=\"only names + or * are allowed\"" items: type: string type: array - x-kubernetes-validations: - - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z-]+|[*]$')) namespaces: default: - '*' - description: namespaces represents namespaces where - an object of the given group/resoruce may be managed. + description: "namespaces represents namespaces where + an object of the given group/resource may be managed. Namespaces matches against the metadata.namespace - field. A value of "*" matches namespaced objects - across all namespaces. If namespaces is not set - (an empty list), matches cluster-scoped resources. - If the "names" field is unset, all objects of the + field. A value of \"*\" matches namespaced objects + across all namespaces. If a resources namespace + matches any value in namespaces, the resource namespace + is considered matching. If the claim is for a cluster-scoped + resource, namespaces has to explicitly be set to + an empty array to prevent defaulting to \"*\". If + the \"names\" field is unset, all objects of the group/resource within the listed namespaces (or - cluster) will be claimed. + cluster) will be claimed. \n // +kubebuilder:validation:XValidation:rule=\"self.all(n, + n.matches('^[A-z-]+|[*]$'))\",message=\"only names + or * are allowed\"" items: type: string type: array - x-kubernetes-validations: - - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z-]+|[*]$')) owner: + description: owner matches the resource's owner. If + an owner selector is set, resources owned by other + owners will not be claimed. Resources without a + present owner will be considered, if configured + owner could be the owner of the object. For example, + if the consumer creates a resource that is claimed + by the provider for reading. In this case the resource + will be marked as owned by the consumer, and handled + as such in further reconciliations. An unset owner + selector means objects from both sides are considered. enum: - Provider - Consumer @@ -246,7 +267,8 @@ spec: type: object update: description: update lists which updates to objects on - the consumer side are claimed. + the consumer cluster are claimed. By default, the whole + object is synced, but metadata is not. properties: alwaysRecreate: description: "alwaysRecreate, when true will delete @@ -266,6 +288,18 @@ spec: type: string type: object type: array + annotationsOnConsumerOwnedObjects: + description: "annotationsOnConsumerOwnedObjects is + a list of claimed annotation key wildcard patterns + that are synchronized from the provider to the consumer + for objects owned by the consumer. \n By default, + no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array fields: description: "fields are a list of JSON Paths describing which parts of an object the provider wants to control. @@ -285,30 +319,19 @@ spec: type: string type: object type: array - overrideAnnotations: - description: "overrideAnnotations is a list of claiemd - annotation key wildcard patterns that are synchronized - from the provider to the consumer for objects owned - by the consumer. \n By default, no annotations are - synced." - items: - properties: - pattern: - type: string - type: object - type: array - overrideLabels: - description: "overrideLabels is a list of claiemd - label key wildcard patterns that are synchronized - from the provider to the consumer for objects owned - by the consumer. \n By default, no labels are synced." + labelsOnConsumerOwnedObjects: + description: "labelsOnConsumerOwnedObjects is a list + of claimed label key wildcard patterns that are + synchronized from the provider to the consumer for + objects owned by the consumer. \n By default, no + labels are synced." items: properties: pattern: type: string type: object type: array - preservings: + preserving: description: "preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. \n This field is ignored @@ -318,6 +341,7 @@ spec: type: array type: object version: + description: version is the version of the claimed resource. minLength: 1 type: string required: diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index 4ef2ff8c7..d99e6bfc6 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -110,24 +110,25 @@ spec: type: object permissionClaims: items: - description: PermissionClaim selects objects of a GVR that a service + 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: autoAdopt: - description: AutoAdopt set to true means that objects created + description: autoAdopt set to true means that objects created by the consumer are adopted by the provider. i.e. the provider - will become the owner. + will become the owner. Mutually exclusive with autoDonate. type: boolean autoDonate: - description: AutoDonate set to true means that a newly created + description: autoDonate set to true means that a newly created object by the provider is immediately owned by the consumer. - If false, the object stays in ownership of the provider. + If false, the object stays in ownership of the provider. Mutually + exclusive with autoDonate. type: boolean create: - description: "only for owner Provider \n Create determines whether - the kube-bind konnector will sync matching objects from the - provider side down to the consumer cluster." + description: create determines whether the kube-bind konnector + will sync matching objects from the provider cluster down + to the consumer cluster. only for owner Provider properties: replaceExisting: description: "replaceExisting means that an existing object @@ -144,31 +145,35 @@ spec: type: string onConflict: description: onConflict determines how the conflicts between - objects on the consumer side will be resolved. + objects on the consumer cluster will be resolved. properties: recreateWhenConsumerSideDeleted: default: true description: "recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object - in case the object is missing on the consumer side, but - has been synchronized before. \n If set to false, deleted - provider-owned objects get deleted on the provider side - as well. \n Even if the consumer mistakenly or intentionally - deletes the object, the provider will recreate it. If - the field is set as false, the provider will not recreate - the object in case the object is deleted on the RecreateWhenConsumerSideDeleted - side." + in case the object is missing on the consumer cluster, + but has been synchronized before. \n If set to false, + deleted provider-owned objects get deleted on the provider + cluster as well. \n Even if the consumer mistakenly or + intentionally deletes the object, the provider will recreate + it. If the field is set as false, the provider will not + recreate the object in case the object is deleted on the + consumer cluster." type: boolean type: object read: - description: read contains extra labels and annotations the - provider will read from objects on the consumer cluster. By - default no labels and annotations are read. + description: read claims read access to matching objects for + the provider. reading of the claimed object(s) is always claimed. + By default no labels and annotations are read. Reading of + labels and annotations can be claimed optionally by adding + labels and annotations items. If labels on consumer owned + objects that are set by the consumer are read, labelsOnProviderOwnedObjects + and annotationsOnProviderOwnedObjects can be set. properties: annotations: description: annotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer - side to the provider on objects that are owned by the + cluster to the provider on objects that are owned by the consumer. items: properties: @@ -178,7 +183,7 @@ spec: type: array labels: description: labels is a list of claimed label key wildcard - patterns that are synchronized from the consumer side + patterns that are synchronized from the consumer cluster to the provider on objects that are owned by the consumer. items: properties: @@ -189,8 +194,8 @@ spec: labelsOnProviderOwnedObjects: description: labelsOnProviderOwnedObjects is a list of claimed label key wildcard patterns that are synchronized from - the consumer side to the provider on objects owned by - the provider. + the consumer cluster to the provider on objects owned + by the provider. items: properties: pattern: @@ -200,7 +205,7 @@ spec: overrideAnnotations: description: overrideAnnotations is a list of claimed annotation key wildcard patterns that are synchronized from the consumer - side to the provider on objects owned by the provider. + cluster to the provider on objects owned by the provider. items: properties: pattern: @@ -209,8 +214,9 @@ spec: type: array type: object required: - description: Required indicates whether the APIServiceBinding - will work if this claim is not accepted. + description: required indicates whether the APIServiceBinding + will work if this claim is not accepted. If a required claim + is denied, the binding is aborted. type: boolean resource: description: 'resource is the name of the resource. Note: it @@ -219,8 +225,8 @@ spec: pattern: ^[a-z][-a-z0-9]*[a-z0-9]$ type: string selector: - description: Selector selects which resources are affected by - this claim. + description: selector selects which resources are being claimed. + If unset, all resources across all namespaces are being claimed. properties: fieldSelectors: description: fieldSelectors is a list of field selectors @@ -240,36 +246,49 @@ spec: names: default: - '*' - description: names is a list of specific resource names + description: "names is a list of specific resource names to select. Names matches the metadata.name field of the - underlying object. An entry of "*" anywhere in the list + underlying object. An entry of \"*\" anywhere in the list means all object names of the group/resource within the - "namespaces" field are claimed. Wildcard entries other - than "*" and regular expressions are currently unsupported. + \"namespaces\" field are claimed. Wildcard entries other + than \"*\" and regular expressions are currently unsupported. + If a resources name matches any value in names, the resource + name is considered matching. \n // +kubebuilder:validation:XValidation:rule=\"self.all(n, + n.matches('^[A-z-]+|[*]$'))\",message=\"only names or + * are allowed\"" items: type: string type: array - x-kubernetes-validations: - - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z-]+|[*]$')) namespaces: default: - '*' - description: namespaces represents namespaces where an object - of the given group/resoruce may be managed. Namespaces + description: "namespaces represents namespaces where an + object of the given group/resource may be managed. Namespaces matches against the metadata.namespace field. A value - of "*" matches namespaced objects across all namespaces. - If namespaces is not set (an empty list), matches cluster-scoped - resources. If the "names" field is unset, all objects + of \"*\" matches namespaced objects across all namespaces. + If a resources namespace matches any value in namespaces, + the resource namespace is considered matching. If the + claim is for a cluster-scoped resource, namespaces has + to explicitly be set to an empty array to prevent defaulting + to \"*\". If the \"names\" field is unset, all objects of the group/resource within the listed namespaces (or - cluster) will be claimed. + cluster) will be claimed. \n // +kubebuilder:validation:XValidation:rule=\"self.all(n, + n.matches('^[A-z-]+|[*]$'))\",message=\"only names or + * are allowed\"" items: type: string type: array - x-kubernetes-validations: - - message: only names or * are allowed - rule: self.all(n, n.matches('^[A-z-]+|[*]$')) owner: + description: owner matches the resource's owner. If an owner + selector is set, resources owned by other owners will + not be claimed. Resources without a present owner will + be considered, if configured owner could be the owner + of the object. For example, if the consumer creates a + resource that is claimed by the provider for reading. + In this case the resource will be marked as owned by the + consumer, and handled as such in further reconciliations. + An unset owner selector means objects from both sides + are considered. enum: - Provider - Consumer @@ -277,7 +296,8 @@ spec: type: object update: description: update lists which updates to objects on the consumer - side are claimed. + cluster are claimed. By default, the whole object is synced, + but metadata is not. properties: alwaysRecreate: description: "alwaysRecreate, when true will delete the @@ -296,6 +316,17 @@ spec: type: string type: object type: array + annotationsOnConsumerOwnedObjects: + description: "annotationsOnConsumerOwnedObjects is a list + of claimed annotation key wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the consumer. \n By default, no annotations are synced." + items: + properties: + pattern: + type: string + type: object + type: array fields: description: "fields are a list of JSON Paths describing which parts of an object the provider wants to control. @@ -315,29 +346,18 @@ spec: type: string type: object type: array - overrideAnnotations: - description: "overrideAnnotations is a list of claiemd annotation - key wildcard patterns that are synchronized from the provider - to the consumer for objects owned by the consumer. \n - By default, no annotations are synced." - items: - properties: - pattern: - type: string - type: object - type: array - overrideLabels: - description: "overrideLabels is a list of claiemd label - key wildcard patterns that are synchronized from the provider - to the consumer for objects owned by the consumer. \n - By default, no labels are synced." + labelsOnConsumerOwnedObjects: + description: "labelsOnConsumerOwnedObjects is a list of + claimed label key wildcard patterns that are synchronized + from the provider to the consumer for objects owned by + the consumer. \n By default, no labels are synced." items: properties: pattern: type: string type: object type: array - preservings: + preserving: description: "preserving is a list of JSON Paths describing which parts of an object owned by the provider the consumer keeps controlling. \n This field is ignored if the owner @@ -347,6 +367,7 @@ spec: type: array type: object version: + description: version is the version of the claimed resource. minLength: 1 type: string required: diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 41857e4f9..47c37ae9b 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -97,11 +97,12 @@ type APIServiceBindingSpec struct { PermissionClaims []AcceptablePermissionClaim `json:"permissionClaims,omitempty"` } +// acceptablePermissionClaim is a permission claim that stores the users acceptance in the field state. Only accepted permission claims are reconciled. type AcceptablePermissionClaim struct { PermissionClaim `json:",inline"` // state indicates if the claim is accepted or rejected. - + // // +required // +kubebuilder:validation:Required // +kubebuilder:validation:Enum=Accepted;Rejected @@ -115,60 +116,69 @@ const ( ClaimRejected AcceptablePermissionClaimState = "Rejected" ) -// PermissionClaim selects objects of a GVR that a service provider may +// 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. // // +kubebuilder:validation:XValidation:rule="!(has(self.autoDonate) && self.autoDonate && has(self.autoAdopt) && self.autoAdopt)",message="donate and adopt are mutually exclusive" type PermissionClaim struct { GroupResource `json:","` + // version is the version of the claimed resource. + // // +kubebuilder:validation:Required // +kubebuilder:validation:MinLength:=1 Version string `json:"version"` - // Selector selects which resources are affected by this claim. + // selector selects which resources are being claimed. + // If unset, all resources across all namespaces are being claimed. + // // +optional // +kubebuilder:default:={} Selector *ResourceSelector `json:"selector,omitempty"` - // Required indicates whether the APIServiceBinding will work if this claim is not accepted. + // required indicates whether the APIServiceBinding will work if this claim is not accepted. If a required claim is denied, the binding is aborted. Required bool `json:"required"` - // read contains extra labels and annotations the provider will read from objects on the consumer cluster. - // By default no labels and annotations are read. + // read claims read access to matching objects for the provider. + // reading of the claimed object(s) is always claimed. + // By default no labels and annotations are read. Reading of labels and annotations can be claimed + // optionally by adding labels and annotations items. + // If labels on consumer owned objects that are set by the consumer are read, labelsOnProviderOwnedObjects and + // annotationsOnProviderOwnedObjects can be set. // // +optional // +kubebuilder:default={} Read *ReadOptions `json:"read,omitempty"` + // create determines whether the kube-bind konnector will sync matching objects from the + // provider cluster down to the consumer cluster. // only for owner Provider // - // Create determines whether the kube-bind konnector will sync matching objects from the - // provider side down to the consumer cluster. - // // +optional Create *CreateOptions `json:"create,omitempty"` - // AutoAdopt set to true means that objects created by the consumer are adopted by the provider. + // autoAdopt set to true means that objects created by the consumer are adopted by the provider. // i.e. the provider will become the owner. + // Mutually exclusive with autoDonate. // // +optional AutoAdopt bool `json:"autoAdopt,omitempty"` - // AutoDonate set to true means that a newly created object by the provider is immediately owned by the consumer. + // autoDonate set to true means that a newly created object by the provider is immediately owned by the consumer. // If false, the object stays in ownership of the provider. + // Mutually exclusive with autoDonate. // // +optional AutoDonate bool `json:"autoDonate,omitempty"` - // onConflict determines how the conflicts between objects on the consumer side - // will be resolved. + // onConflict determines how the conflicts between objects on the consumer cluster will be resolved. // // +optional // +kubebuilder:default:={} OnConflict *OnConflictOptions `json:"onConflict,omitempty"` - // update lists which updates to objects on the consumer side are claimed. + // update lists which updates to objects on the consumer cluster are claimed. + // By default, the whole object is synced, but metadata is not. // // +optional Update *UpdateOptions `json:"update,omitempty"` @@ -176,28 +186,28 @@ type PermissionClaim struct { type ReadOptions struct { // labels is a list of claimed label key wildcard patterns - // that are synchronized from the consumer side to the provider on + // that are synchronized from the consumer cluster to the provider on // objects that are owned by the consumer. // // +optional Labels []Matcher `json:"labels,omitempty"` // labelsOnProviderOwnedObjects is a list of claimed label key wildcard - // patterns that are synchronized from the consumer side + // patterns that are synchronized from the consumer cluster // to the provider on objects owned by the provider. // // +optional LabelsOnProviderOwnedObjects []Matcher `json:"labelsOnProviderOwnedObjects,omitempty"` // annotations is a list of claimed annotation key wildcard patterns - // that are synchronized from the consumer side to the provider on + // that are synchronized from the consumer cluster to the provider on // objects that are owned by the consumer. // // +optional Annotations []Matcher `json:"annotations,omitempty"` // overrideAnnotations is a list of claimed annotation key wildcard - // patterns that are synchronized from the consumer side + // patterns that are synchronized from the consumer cluster // to the provider on objects owned by the provider. // // +optional @@ -211,14 +221,13 @@ type Matcher struct { type OnConflictOptions struct { // recreateWhenConsumerSideDeleted set to true (the default) means the provider will recreate the object - // in case the object is missing on the consumer side, but has been synchronized before. + // in case the object is missing on the consumer cluster, but has been synchronized before. // - // If set to false, deleted provider-owned objects get deleted on the provider side as well. + // If set to false, deleted provider-owned objects get deleted on the provider cluster as well. // // Even if the consumer mistakenly or intentionally // deletes the object, the provider will recreate it. If the field is set as false, - // the provider will not recreate the object in case the object is deleted on the RecreateWhenConsumerSideDeleted - // side. + // the provider will not recreate the object in case the object is deleted on the consumer cluster. // // +kubebuilder:default:=true RecreateWhenConsumerSideDeleted bool `json:"recreateWhenConsumerSideDeleted"` @@ -246,7 +255,7 @@ type UpdateOptions struct { // This field is ignored if the owner in the claim selector is set to "Consumer". // // +optional - Preserving []string `json:"preservings,omitempty"` + Preserving []string `json:"preserving,omitempty"` // alwaysRecreate, when true will delete the old object and create new ones // instead of updating. Useful for immutable objects. @@ -263,12 +272,12 @@ type UpdateOptions struct { // +optional Labels []Matcher `json:"labels,omitempty"` - // overrideLabels is a list of claiemd label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. + // labelsOnConsumerOwnedObjects is a list of claimed label key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. // // By default, no labels are synced. // // +optional - OverrideLabels []Matcher `json:"overrideLabels,omitempty"` + LabelsOnConsumerOwnedObjects []Matcher `json:"labelsOnConsumerOwnedObjects,omitempty"` // annotations is a list of claimed annotation keys or annotation wildcard patterns that are synchronized from the provider to the consumer for objects owned by the provider. // @@ -277,12 +286,12 @@ type UpdateOptions struct { // +optional Annotations []Matcher `json:"annotations,omitempty"` - // overrideAnnotations is a list of claiemd annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. + // annotationsOnConsumerOwnedObjects is a list of claimed annotation key wildcard patterns that are synchronized from the provider to the consumer for objects owned by the consumer. // // By default, no annotations are synced. // // +optional - OverrideAnnotations []Matcher `json:"overrideAnnotations,omitempty"` + AnnotationsOnConsumerOwnedObjects []Matcher `json:"annotationsOnConsumerOwnedObjects,omitempty"` } type ResourceSelector struct { @@ -290,18 +299,20 @@ type ResourceSelector struct { // Names matches the metadata.name field of the underlying object. // An entry of "*" anywhere in the list means all object names of the group/resource within the "namespaces" field are claimed. // Wildcard entries other than "*" and regular expressions are currently unsupported. + // If a resources name matches any value in names, the resource name is considered matching. // - // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z-]+|[*]$'))",message="only names or * are allowed" + // // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z-]+|[*]$'))",message="only names or * are allowed" // +kubebuilder:default:={"*"} // +optional Names []string `json:"names,omitempty"` - // namespaces represents namespaces where an object of the given group/resoruce may be managed. - // Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all - // namespaces. If namespaces is not set (an empty list), matches cluster-scoped resources. + // namespaces represents namespaces where an object of the given group/resource may be managed. + // Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all namespaces. + // If a resources namespace matches any value in namespaces, the resource namespace is considered matching. + // If the claim is for a cluster-scoped resource, namespaces has to explicitly be set to an empty array to prevent defaulting to "*". // If the "names" field is unset, all objects of the group/resource within the listed namespaces (or cluster) will be claimed. // - // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z-]+|[*]$'))",message="only names or * are allowed" + // // +kubebuilder:validation:XValidation:rule="self.all(n, n.matches('^[A-z-]+|[*]$'))",message="only names or * are allowed" // +kubebuilder:default:={"*"} // +optional Namespaces []string `json:"namespaces,omitempty"` @@ -314,6 +325,12 @@ type ResourceSelector struct { // see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/. FieldSelectors []string `json:"fieldSelectors,omitempty"` + // owner matches the resource's owner. If an owner selector is set, resources owned by other owners will not be claimed. + // Resources without a present owner will be considered, if configured owner could be the owner of the object. + // For example, if the consumer creates a resource that is claimed by the provider for reading. In this case the resource + // will be marked as owned by the consumer, and handled as such in further reconciliations. + // An unset owner selector means objects from both sides are considered. + // // +kubebuilder:validation:Enum=Provider;Consumer // +optional Owner Owner `json:"owner,omitempty"` @@ -321,11 +338,13 @@ type ResourceSelector struct { type Owner string -// provider means that the owner of the resource is the Provider. -const Provider Owner = "Provider" +const ( + // provider means that the owner of the resource is the Provider. + Provider Owner = "Provider" -// consumer means that the owner of the resource is the Consumer. -const Consumer Owner = "Consumer" + // consumer means that the owner of the resource is the Consumer. + Consumer Owner = "Consumer" +) type APIServiceBindingStatus struct { // providerPrettyName is the pretty name of the service provider cluster. This diff --git a/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go b/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go index 3b2d9df85..a0d329825 100644 --- a/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go @@ -111,7 +111,7 @@ type APIServiceExportRequestResource struct { // a sensible default is chosen by the service provider. Versions []string `json:"versions,omitempty"` - // permissionClaims records decisions about permission claims requested by the API service provider. + // permissionClaims records decisions about permission claims requested by the service provider. // Individual claims can be accepted or rejected. If accepted, the API service provider gets the // requested access to the specified resources in this workspace. Access is granted per // GroupResource, identity, and other properties. diff --git a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go index fed6ddb73..b1bb2ca50 100644 --- a/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/kubebind/v1alpha1/zz_generated.deepcopy.go @@ -1099,8 +1099,8 @@ func (in *UpdateOptions) DeepCopyInto(out *UpdateOptions) { *out = make([]Matcher, len(*in)) copy(*out, *in) } - if in.OverrideLabels != nil { - in, out := &in.OverrideLabels, &out.OverrideLabels + if in.LabelsOnConsumerOwnedObjects != nil { + in, out := &in.LabelsOnConsumerOwnedObjects, &out.LabelsOnConsumerOwnedObjects *out = make([]Matcher, len(*in)) copy(*out, *in) } @@ -1109,8 +1109,8 @@ func (in *UpdateOptions) DeepCopyInto(out *UpdateOptions) { *out = make([]Matcher, len(*in)) copy(*out, *in) } - if in.OverrideAnnotations != nil { - in, out := &in.OverrideAnnotations, &out.OverrideAnnotations + if in.AnnotationsOnConsumerOwnedObjects != nil { + in, out := &in.AnnotationsOnConsumerOwnedObjects, &out.AnnotationsOnConsumerOwnedObjects *out = make([]Matcher, len(*in)) copy(*out, *in) } From da72fcc71cfe55a5235c60f2add0a7e6989b7d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 18 Oct 2023 14:47:57 +0200 Subject: [PATCH 20/26] Rename example-backend api group Rename api group for backend configuration Custom Resources from example.com to example-backend.kube-bind.io --- ...ample-backend.kube-bind.io_apiserviceexporttemplates.yaml} | 4 ++-- contrib/example-backend/apis/examplebackend/v1alpha1/doc.go | 2 +- .../example-backend/apis/examplebackend/v1alpha1/register.go | 2 +- .../typed/examplebackend/v1alpha1/examplebackend_client.go | 2 +- .../v1alpha1/fake/fake_apiserviceexporttemplate.go | 4 ++-- .../client/informers/externalversions/generic.go | 2 +- test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml | 2 +- test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) rename contrib/deploy/crd/{example.com_apiserviceexporttemplates.yaml => example-backend.kube-bind.io_apiserviceexporttemplates.yaml} (99%) diff --git a/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml b/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml similarity index 99% rename from contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml rename to contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml index 656da5a05..2d34bd2ad 100644 --- a/contrib/deploy/crd/example.com_apiserviceexporttemplates.yaml +++ b/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml @@ -5,9 +5,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.10.0 creationTimestamp: null - name: apiserviceexporttemplates.example.com + name: apiserviceexporttemplates.example-backend.kube-bind.io spec: - group: example.com + group: example-backend.kube-bind.io names: categories: - kube-bindings diff --git a/contrib/example-backend/apis/examplebackend/v1alpha1/doc.go b/contrib/example-backend/apis/examplebackend/v1alpha1/doc.go index 7dea12136..8fab39db3 100644 --- a/contrib/example-backend/apis/examplebackend/v1alpha1/doc.go +++ b/contrib/example-backend/apis/examplebackend/v1alpha1/doc.go @@ -16,7 +16,7 @@ limitations under the License. // Package v1alpha1 defines the v1alpha1 version of the Example Backend API // -// +groupName=example.com +// +groupName=example-backend.kube-bind.io // +groupGoName=ExampleBackend // +k8s:deepcopy-gen=package,register // +kubebuilder:validation:Optional diff --git a/contrib/example-backend/apis/examplebackend/v1alpha1/register.go b/contrib/example-backend/apis/examplebackend/v1alpha1/register.go index 302da5d0a..5d9f73ccc 100644 --- a/contrib/example-backend/apis/examplebackend/v1alpha1/register.go +++ b/contrib/example-backend/apis/examplebackend/v1alpha1/register.go @@ -29,7 +29,7 @@ var ( const ( // GroupName is the group name used in this package - GroupName = "example.com" + GroupName = "example-backend.kube-bind.io" // GroupVersion is the group version used in this package GroupVersion = "v1alpha1" diff --git a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/examplebackend_client.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/examplebackend_client.go index 49ff92a75..0f670149e 100644 --- a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/examplebackend_client.go +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/examplebackend_client.go @@ -32,7 +32,7 @@ type ExampleBackendV1alpha1Interface interface { APIServiceExportTemplatesGetter } -// ExampleBackendV1alpha1Client is used to interact with features provided by the example.com group. +// ExampleBackendV1alpha1Client is used to interact with features provided by the example-backend.kube-bind.io group. type ExampleBackendV1alpha1Client struct { restClient rest.Interface } diff --git a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_apiserviceexporttemplate.go b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_apiserviceexporttemplate.go index 6c6755ddf..f87a07e0e 100644 --- a/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_apiserviceexporttemplate.go +++ b/contrib/example-backend/client/clientset/versioned/typed/examplebackend/v1alpha1/fake/fake_apiserviceexporttemplate.go @@ -37,9 +37,9 @@ type FakeAPIServiceExportTemplates struct { ns string } -var apiserviceexporttemplatesResource = schema.GroupVersionResource{Group: "example.com", Version: "v1alpha1", Resource: "apiserviceexporttemplates"} +var apiserviceexporttemplatesResource = schema.GroupVersionResource{Group: "example-backend.kube-bind.io", Version: "v1alpha1", Resource: "apiserviceexporttemplates"} -var apiserviceexporttemplatesKind = schema.GroupVersionKind{Group: "example.com", Version: "v1alpha1", Kind: "APIServiceExportTemplate"} +var apiserviceexporttemplatesKind = schema.GroupVersionKind{Group: "example-backend.kube-bind.io", Version: "v1alpha1", Kind: "APIServiceExportTemplate"} // Get takes name of the aPIServiceExportTemplate, and returns the corresponding aPIServiceExportTemplate object, and an error if there is any. func (c *FakeAPIServiceExportTemplates) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.APIServiceExportTemplate, err error) { diff --git a/contrib/example-backend/client/informers/externalversions/generic.go b/contrib/example-backend/client/informers/externalversions/generic.go index 6fa70cab5..a1e5634be 100644 --- a/contrib/example-backend/client/informers/externalversions/generic.go +++ b/contrib/example-backend/client/informers/externalversions/generic.go @@ -53,7 +53,7 @@ func (f *genericInformer) Lister() cache.GenericLister { // TODO extend this to unknown resources with a client pool func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { - // Group=example.com, Version=v1alpha1 + // Group=example-backend.kube-bind.io, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithResource("apiserviceexporttemplates"): return &genericInformer{resource: resource.GroupResource(), informer: f.ExampleBackend().V1alpha1().APIServiceExportTemplates().Informer()}, nil diff --git a/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml index b3849a002..fd1b3de84 100644 --- a/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml +++ b/test/e2e/bind/fixtures/consumer/exporttemplate-mangodb.yaml @@ -1,5 +1,5 @@ kind: APIServiceExportTemplate -apiVersion: example.com/v1alpha1 +apiVersion: example-backend.kube-bind.io/v1alpha1 metadata: name: "mangodbs" namespace: default diff --git a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml index 21f88a330..ee8fa9f04 100644 --- a/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml +++ b/test/e2e/bind/fixtures/provider/exporttemplate-mangodb.yaml @@ -1,5 +1,5 @@ kind: APIServiceExportTemplate -apiVersion: example.com/v1alpha1 +apiVersion: example-backend.kube-bind.io/v1alpha1 metadata: name: "mangodbs" namespace: default From d2e061d6793705cd2a55f844d8b7806e0c4810e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 18 Oct 2023 14:54:39 +0200 Subject: [PATCH 21/26] Rename function to american spelling --- .../serviceexportrequest/serviceexportrequest_controller.go | 2 +- contrib/example-backend/exporttemplate/index.go | 2 +- contrib/example-backend/server.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go index 385a269c4..083060c92 100644 --- a/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go +++ b/contrib/example-backend/controllers/serviceexportrequest/serviceexportrequest_controller.go @@ -102,7 +102,7 @@ func NewController( deleteServiceExportRequest: func(ctx context.Context, ns, name string) error { return bindClient.KubeBindV1alpha1().APIServiceExportRequests(ns).Delete(ctx, name, metav1.DeleteOptions{}) }, - crds: exporttemplate.NewCatalogue(config), + crds: exporttemplate.NewCatalog(config), }, commit: committer.NewCommitter[*kubebindv1alpha1.APIServiceExportRequest, *kubebindv1alpha1.APIServiceExportRequestSpec, *kubebindv1alpha1.APIServiceExportRequestStatus]( diff --git a/contrib/example-backend/exporttemplate/index.go b/contrib/example-backend/exporttemplate/index.go index 74c1a68ad..a1e29a7dc 100644 --- a/contrib/example-backend/exporttemplate/index.go +++ b/contrib/example-backend/exporttemplate/index.go @@ -38,7 +38,7 @@ type Index struct { clusterNs string } -func NewCatalogue(r *rest.Config) Index { +func NewCatalog(r *rest.Config) Index { crdClient := crd.NewForConfigOrDie(r) templateClient := templates.NewForConfigOrDie(r) diff --git a/contrib/example-backend/server.go b/contrib/example-backend/server.go index 830909c11..1ef1a3be1 100644 --- a/contrib/example-backend/server.go +++ b/contrib/example-backend/server.go @@ -107,7 +107,7 @@ func NewServer(config *Config) (*Server, error) { } } - ind := exporttemplate.NewCatalogue(config.ClientConfig) + ind := exporttemplate.NewCatalog(config.ClientConfig) handler, err := examplehttp.NewHandler( s.OIDC, config.Options.OIDC.AuthorizeURL, From 3477587cb585f5bb82b00089294862ba4eaaf4a9 Mon Sep 17 00:00:00 2001 From: lhaendler <94445906+lhaendler@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:00:42 +0200 Subject: [PATCH 22/26] Update pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go Co-authored-by: Dr. Stefan Schimanski --- ...end.kube-bind.io_apiserviceexporttemplates.yaml | 8 ++++---- deploy/crd/kube-bind.io_apiservicebindings.yaml | 8 ++++---- .../crd/kube-bind.io_apiserviceexportrequests.yaml | 14 +++++++------- deploy/crd/kube-bind.io_apiserviceexports.yaml | 8 ++++---- .../kubebind/v1alpha1/apiservicebinding_types.go | 6 +++--- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml b/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml index 2d34bd2ad..68f9daea9 100644 --- a/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml +++ b/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml @@ -127,10 +127,10 @@ spec: type: object read: description: read claims read access to matching objects for - the provider. reading of the claimed object(s) is always claimed. - By default no labels and annotations are read. Reading of - labels and annotations can be claimed optionally by adding - labels and annotations items. If labels on consumer owned + the provider. Reading of the claimed object(s) is always claimed. + By default, no labels and annotations can be read by the provider. + Reading of labels and annotations can be claimed in addition + by specifying them explicitly. If labels on consumer owned objects that are set by the consumer are read, labelsOnProviderOwnedObjects and annotationsOnProviderOwnedObjects can be set. properties: diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index bc67a56cf..0526187ff 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -144,10 +144,10 @@ spec: type: object read: description: read claims read access to matching objects for - the provider. reading of the claimed object(s) is always claimed. - By default no labels and annotations are read. Reading of - labels and annotations can be claimed optionally by adding - labels and annotations items. If labels on consumer owned + the provider. Reading of the claimed object(s) is always claimed. + By default, no labels and annotations can be read by the provider. + Reading of labels and annotations can be claimed in addition + by specifying them explicitly. If labels on consumer owned objects that are set by the consumer are read, labelsOnProviderOwnedObjects and annotationsOnProviderOwnedObjects can be set. properties: diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index bbddb1d1d..ab624a6ba 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -126,13 +126,13 @@ spec: type: object read: description: read claims read access to matching objects - for the provider. reading of the claimed object(s) is - always claimed. By default no labels and annotations - are read. Reading of labels and annotations can be claimed - optionally by adding labels and annotations items. If - labels on consumer owned objects that are set by the - consumer are read, labelsOnProviderOwnedObjects and - annotationsOnProviderOwnedObjects can be set. + for the provider. Reading of the claimed object(s) is + always claimed. By default, no labels and annotations + can be read by the provider. Reading of labels and annotations + can be claimed in addition by specifying them explicitly. + If labels on consumer owned objects that are set by + the consumer are read, labelsOnProviderOwnedObjects + and annotationsOnProviderOwnedObjects can be set. properties: annotations: description: annotations is a list of claimed annotation diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index d99e6bfc6..e4bfbc48a 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -163,10 +163,10 @@ spec: type: object read: description: read claims read access to matching objects for - the provider. reading of the claimed object(s) is always claimed. - By default no labels and annotations are read. Reading of - labels and annotations can be claimed optionally by adding - labels and annotations items. If labels on consumer owned + the provider. Reading of the claimed object(s) is always claimed. + By default, no labels and annotations can be read by the provider. + Reading of labels and annotations can be claimed in addition + by specifying them explicitly. If labels on consumer owned objects that are set by the consumer are read, labelsOnProviderOwnedObjects and annotationsOnProviderOwnedObjects can be set. properties: diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 47c37ae9b..1e68ee027 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -140,9 +140,9 @@ type PermissionClaim struct { Required bool `json:"required"` // read claims read access to matching objects for the provider. - // reading of the claimed object(s) is always claimed. - // By default no labels and annotations are read. Reading of labels and annotations can be claimed - // optionally by adding labels and annotations items. + // Reading of the claimed object(s) is always claimed. + // By default, no labels and annotations can be read by the provider. + // Reading of labels and annotations can be claimed in addition by specifying them explicitly. // If labels on consumer owned objects that are set by the consumer are read, labelsOnProviderOwnedObjects and // annotationsOnProviderOwnedObjects can be set. // From 0d12467b06f12a8d57df76ccef1447a135f5bfbc Mon Sep 17 00:00:00 2001 From: lhaendler <94445906+lhaendler@users.noreply.github.com> Date: Wed, 18 Oct 2023 15:03:15 +0200 Subject: [PATCH 23/26] Update pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go Co-authored-by: Dr. Stefan Schimanski --- ...xample-backend.kube-bind.io_apiserviceexporttemplates.yaml | 4 ++-- deploy/crd/kube-bind.io_apiservicebindings.yaml | 4 ++-- deploy/crd/kube-bind.io_apiserviceexportrequests.yaml | 2 +- deploy/crd/kube-bind.io_apiserviceexports.yaml | 4 ++-- pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml b/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml index 68f9daea9..973b98cf4 100644 --- a/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml +++ b/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml @@ -97,8 +97,8 @@ spec: replaceExisting: description: "replaceExisting means that an existing object owned by the consumer will be replaced by the provider - object. \n If set to false, and a conflicting consumer - object exists, it is not touched." + object. \n If not true, and a conflicting consumer object + exists, it is not touched." type: boolean type: object group: diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index 0526187ff..3ae4f79a3 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -114,8 +114,8 @@ spec: replaceExisting: description: "replaceExisting means that an existing object owned by the consumer will be replaced by the provider - object. \n If set to false, and a conflicting consumer - object exists, it is not touched." + object. \n If not true, and a conflicting consumer object + exists, it is not touched." type: boolean type: object group: diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index ab624a6ba..4cc667492 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -95,7 +95,7 @@ spec: replaceExisting: description: "replaceExisting means that an existing object owned by the consumer will be replaced by - the provider object. \n If set to false, and a conflicting + the provider object. \n If not true, and a conflicting consumer object exists, it is not touched." type: boolean type: object diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index e4bfbc48a..299177a5c 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -133,8 +133,8 @@ spec: replaceExisting: description: "replaceExisting means that an existing object owned by the consumer will be replaced by the provider - object. \n If set to false, and a conflicting consumer - object exists, it is not touched." + object. \n If not true, and a conflicting consumer object + exists, it is not touched." type: boolean type: object group: diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 1e68ee027..3d9d69121 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -236,7 +236,7 @@ type OnConflictOptions struct { type CreateOptions struct { // replaceExisting means that an existing object owned by the consumer will be replaced by the provider object. // - // If set to false, and a conflicting consumer object exists, it is not touched. + // If not true, and a conflicting consumer object exists, it is not touched. // // +optional ReplaceExisting bool `json:"replaceExisting,omitempty"` From b8618b12305ce2461013922f3c8f08e3fb9edc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 18 Oct 2023 15:36:09 +0200 Subject: [PATCH 24/26] Remove redundant paragraph --- ...ple-backend.kube-bind.io_apiserviceexporttemplates.yaml | 6 +----- deploy/crd/kube-bind.io_apiservicebindings.yaml | 6 +----- deploy/crd/kube-bind.io_apiserviceexportrequests.yaml | 7 +------ deploy/crd/kube-bind.io_apiserviceexports.yaml | 6 +----- pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go | 4 ---- 5 files changed, 4 insertions(+), 25 deletions(-) diff --git a/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml b/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml index 973b98cf4..d2e71d7e9 100644 --- a/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml +++ b/contrib/deploy/crd/example-backend.kube-bind.io_apiserviceexporttemplates.yaml @@ -118,11 +118,7 @@ spec: in case the object is missing on the consumer cluster, but has been synchronized before. \n If set to false, deleted provider-owned objects get deleted on the provider - cluster as well. \n Even if the consumer mistakenly or - intentionally deletes the object, the provider will recreate - it. If the field is set as false, the provider will not - recreate the object in case the object is deleted on the - consumer cluster." + cluster as well." type: boolean type: object read: diff --git a/deploy/crd/kube-bind.io_apiservicebindings.yaml b/deploy/crd/kube-bind.io_apiservicebindings.yaml index 3ae4f79a3..d1a27c04e 100644 --- a/deploy/crd/kube-bind.io_apiservicebindings.yaml +++ b/deploy/crd/kube-bind.io_apiservicebindings.yaml @@ -135,11 +135,7 @@ spec: in case the object is missing on the consumer cluster, but has been synchronized before. \n If set to false, deleted provider-owned objects get deleted on the provider - cluster as well. \n Even if the consumer mistakenly or - intentionally deletes the object, the provider will recreate - it. If the field is set as false, the provider will not - recreate the object in case the object is deleted on the - consumer cluster." + cluster as well." type: boolean type: object read: diff --git a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml index 4cc667492..509cd1784 100644 --- a/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexportrequests.yaml @@ -116,12 +116,7 @@ spec: the object in case the object is missing on the consumer cluster, but has been synchronized before. \n If set to false, deleted provider-owned objects - get deleted on the provider cluster as well. \n - Even if the consumer mistakenly or intentionally - deletes the object, the provider will recreate it. - If the field is set as false, the provider will - not recreate the object in case the object is deleted - on the consumer cluster." + get deleted on the provider cluster as well." type: boolean type: object read: diff --git a/deploy/crd/kube-bind.io_apiserviceexports.yaml b/deploy/crd/kube-bind.io_apiserviceexports.yaml index 299177a5c..f1a8d6c33 100644 --- a/deploy/crd/kube-bind.io_apiserviceexports.yaml +++ b/deploy/crd/kube-bind.io_apiserviceexports.yaml @@ -154,11 +154,7 @@ spec: in case the object is missing on the consumer cluster, but has been synchronized before. \n If set to false, deleted provider-owned objects get deleted on the provider - cluster as well. \n Even if the consumer mistakenly or - intentionally deletes the object, the provider will recreate - it. If the field is set as false, the provider will not - recreate the object in case the object is deleted on the - consumer cluster." + cluster as well." type: boolean type: object read: diff --git a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go index 3d9d69121..cc306946a 100644 --- a/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go +++ b/pkg/apis/kubebind/v1alpha1/apiservicebinding_types.go @@ -225,10 +225,6 @@ type OnConflictOptions struct { // // If set to false, deleted provider-owned objects get deleted on the provider cluster as well. // - // Even if the consumer mistakenly or intentionally - // deletes the object, the provider will recreate it. If the field is set as false, - // the provider will not recreate the object in case the object is deleted on the consumer cluster. - // // +kubebuilder:default:=true RecreateWhenConsumerSideDeleted bool `json:"recreateWhenConsumerSideDeleted"` } From b317fda54e76e19d8dfc4b5bace02fa73aa379f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 18 Oct 2023 20:10:54 +0200 Subject: [PATCH 25/26] fixup! Add fields for claimed resources --- pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go b/pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go index 4524ede7f..8b7cade28 100644 --- a/pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go +++ b/pkg/apis/kubebind/v1alpha1/helpers/serviceexport.go @@ -90,10 +90,6 @@ func CRDToServiceExport(crd *apiextensionsv1.CustomResourceDefinition) (*kubebin if !crdVersion.Served { continue } - // TODO - if onlyFirstServingVersion && !crdVersion.Storage { - continue - } apiResourceVersion := kubebindv1alpha1.APIServiceExportVersion{ Name: crdVersion.Name, From 1814fb5116fc1f802e2c4fc693df7906aeaed480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lenny=20H=C3=A4ndler?= Date: Wed, 8 Nov 2023 14:39:44 +0100 Subject: [PATCH 26/26] Fix: nil check in claimed_resources_controller A missing nil check for the Selector led to controller crashes --- .../cluster/claimedresources/claimedresources_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go index de425c8d4..5b948941f 100644 --- a/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go +++ b/pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go @@ -201,7 +201,7 @@ func (c *controller) isClaimed(obj *unstructured.Unstructured) bool { } nameMatch := false - if len(c.claim.Selector.Names) == 0 { + if c.claim.Selector == nil || len(c.claim.Selector.Names) == 0 { nameMatch = true } else { for _, name := range c.claim.Selector.Names {