From ea36ca8d30a35063d19867c72541a8053d18d73c Mon Sep 17 00:00:00 2001 From: I501080 Date: Wed, 20 Nov 2024 11:08:02 +0200 Subject: [PATCH 01/74] subscribe to secret change --- api/v1/types.go | 3 +- controllers/serviceinstance_controller.go | 60 +++++++++++++++++++++-- internal/utils/controller_util.go | 9 ++++ internal/utils/controller_util_test.go | 19 +++++++ 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/api/v1/types.go b/api/v1/types.go index 271eb7a5..2ba70755 100644 --- a/api/v1/types.go +++ b/api/v1/types.go @@ -5,7 +5,8 @@ type ParametersFromSource struct { // The Secret key to select from. // The value must be a JSON object. // +optional - SecretKeyRef *SecretKeyReference `json:"secretKeyRef,omitempty"` + SecretKeyRef *SecretKeyReference `json:"secretKeyRef,omitempty"` + SubscribeToChanges *bool `json:"subscribeToChanges,omitempty"` } // SecretKeyReference references a key of a Secret. diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 2cb1d798..a166ce69 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -22,7 +22,14 @@ import ( "encoding/hex" "encoding/json" "fmt" + corev1 "k8s.io/api/core/v1" "net/http" + "reflect" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "k8s.io/utils/ptr" @@ -162,10 +169,34 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } func (r *ServiceInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). + c := ctrl.NewControllerManagedBy(mgr). For(&v1.ServiceInstance{}). - WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}). - Complete(r) + WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}) + + secretPredicate := SecretPredicate{ + Funcs: predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return isSecretDataChanged(e) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + }, + } + + c.Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.findRequestsForSecret), + builder.WithPredicates(secretPredicate), + ) + + return c.Complete(r) } func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) { @@ -721,3 +752,26 @@ func getErrorMsgFromLastOperation(status *smClientTypes.Operation) string { } return errMsg } + +type SecretPredicate struct { + predicate.Funcs +} + +func (r *ServiceInstanceReconciler) findRequestsForSecret(ctx context.Context, secret client.Object) []reconcile.Request { + instancesToUpdate := make([]reconcile.Request, 0) + // TODO + return instancesToUpdate +} + +func isSecretDataChanged(e event.UpdateEvent) bool { + // Type assert to *v1.Secret + oldSecret, okOld := e.ObjectOld.(*corev1.Secret) + newSecret, okNew := e.ObjectNew.(*corev1.Secret) + if !okOld || !okNew { + // If the objects are not Secrets, skip the event + return false + } + + // Compare the Data field (byte slices) + return !reflect.DeepEqual(oldSecret.Data, newSecret.Data) +} diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index d368ae0e..0e62968b 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -146,6 +146,15 @@ func HandleError(ctx context.Context, k8sClient client.Client, operationType smC return MarkAsNonTransientError(ctx, k8sClient, operationType, err, resource) } +// ParseNamespacedName converts a "namespace/name" string to a types.NamespacedName object. +func ParseNamespacedName(input string) (apimachinerytypes.NamespacedName, error) { + parts := strings.SplitN(input, "/", 2) + if len(parts) != 2 { + return apimachinerytypes.NamespacedName{}, fmt.Errorf("invalid format: expected 'namespace/name', got '%s'", input) + } + return apimachinerytypes.NamespacedName{Namespace: parts[0], Name: parts[1]}, nil +} + func handleRateLimitError(smError *sm.ServiceManagerError, log logr.Logger) (ctrl.Result, error) { retryAfterStr := smError.ResponseHeaders.Get("Retry-After") if len(retryAfterStr) > 0 { diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index 2b4ede86..435b3531 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -2,6 +2,7 @@ package utils import ( "encoding/json" + "k8s.io/apimachinery/pkg/types" "net/http" v1 "github.com/SAP/sap-btp-service-operator/api/v1" @@ -178,4 +179,22 @@ var _ = Describe("Controller Util", func() { Expect(got).To(Equal(expected)) }) }) + + Context("ParseNamespacedName", func() { + It("should return correct namespace and name", func() { + nsName, err := ParseNamespacedName(types.NamespacedName{ + Namespace: "namespace", + Name: "name", + }.String()) + Expect(err).ToNot(HaveOccurred()) + Expect(nsName.Namespace).To(Equal("namespace")) + Expect(nsName.Name).To(Equal("name")) + }) + + It("should return error if not a valid namespaced name", func() { + _, err := ParseNamespacedName("namespaceName") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("invalid format: expected 'namespace/name")) + }) + }) }) From bf5a574867a4b05abeaa2545bf5b37d28b8349ff Mon Sep 17 00:00:00 2001 From: I501080 Date: Wed, 20 Nov 2024 13:14:22 +0200 Subject: [PATCH 02/74] subscribe to secret change --- internal/utils/controller_util_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index 435b3531..c392c76f 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -194,7 +194,7 @@ var _ = Describe("Controller Util", func() { It("should return error if not a valid namespaced name", func() { _, err := ParseNamespacedName("namespaceName") Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("invalid format: expected 'namespace/name")) + Expect(err.Error()).To(ContainSubstring("invalid format: expected 'namespace/name")) }) }) }) From 2dc23ccb34abc8ef12a4b52b37eac320bad39294 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 21 Nov 2024 07:47:16 +0200 Subject: [PATCH 03/74] remove rate limit error from state --- api/v1/serviceinstance_types.go | 2 ++ api/v1/types.go | 3 +-- api/v1/zz_generated.deepcopy.go | 5 +++++ .../services.cloud.sap.com_serviceinstances.yaml | 2 ++ controllers/servicebinding_controller_test.go | 5 +++-- controllers/serviceinstance_controller.go | 11 +++++++++-- internal/utils/controller_util_test.go | 3 ++- 7 files changed, 24 insertions(+), 7 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index e6e967ff..37cac48b 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -72,6 +72,8 @@ type ServiceInstanceSpec struct { // +optional ParametersFrom []ParametersFromSource `json:"parametersFrom,omitempty"` + // +optional + SubscribeToSecretChanges *bool `json:"subscribeToChanges,omitempty"` // List of custom tags describing the ServiceInstance, will be copied to `ServiceBinding` secret in the key called `tags`. // +optional CustomTags []string `json:"customTags,omitempty"` diff --git a/api/v1/types.go b/api/v1/types.go index 2ba70755..271eb7a5 100644 --- a/api/v1/types.go +++ b/api/v1/types.go @@ -5,8 +5,7 @@ type ParametersFromSource struct { // The Secret key to select from. // The value must be a JSON object. // +optional - SecretKeyRef *SecretKeyReference `json:"secretKeyRef,omitempty"` - SubscribeToChanges *bool `json:"subscribeToChanges,omitempty"` + SecretKeyRef *SecretKeyReference `json:"secretKeyRef,omitempty"` } // SecretKeyReference references a key of a Secret. diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 03fd1a7c..ea90a066 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -287,6 +287,11 @@ func (in *ServiceInstanceSpec) DeepCopyInto(out *ServiceInstanceSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.SubscribeToSecretChanges != nil { + in, out := &in.SubscribeToSecretChanges, &out.SubscribeToSecretChanges + *out = new(bool) + **out = **in + } if in.CustomTags != nil { in, out := &in.CustomTags, &out.CustomTags *out = make([]string, len(*in)) diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index 745cfde5..c6ea2391 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -140,6 +140,8 @@ spec: shared: description: Indicates the desired shared state type: boolean + subscribeToChanges: + type: boolean userInfo: description: |- UserInfo contains information about the user that last modified this diff --git a/controllers/servicebinding_controller_test.go b/controllers/servicebinding_controller_test.go index 1d1033e9..5350e59a 100644 --- a/controllers/servicebinding_controller_test.go +++ b/controllers/servicebinding_controller_test.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" "errors" - "github.com/lithammer/dedent" - authv1 "k8s.io/api/authentication/v1" "net/http" "strings" + "github.com/lithammer/dedent" + authv1 "k8s.io/api/authentication/v1" + "github.com/SAP/sap-btp-service-operator/api/common" "github.com/SAP/sap-btp-service-operator/internal/utils" "k8s.io/utils/pointer" diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index a166ce69..c7bdb923 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -22,9 +22,10 @@ import ( "encoding/hex" "encoding/json" "fmt" - corev1 "k8s.io/api/core/v1" "net/http" "reflect" + + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -605,7 +606,10 @@ func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool } return false } - + if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges { + log.Info("instance is not in final state, SubscribeToSecretChanges is true") + return false + } log.Info(fmt.Sprintf("instance is in final state (generation: %d)", serviceInstance.Generation)) return true } @@ -620,6 +624,9 @@ func updateRequired(serviceInstance *v1.ServiceInstance) bool { if cond != nil && cond.Reason == common.UpdateInProgress { //in case of transient error occurred return true } + if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges { + return true + } return getSpecHash(serviceInstance) != serviceInstance.Status.HashedSpec } diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index c392c76f..ccb0ddaa 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -2,9 +2,10 @@ package utils import ( "encoding/json" - "k8s.io/apimachinery/pkg/types" "net/http" + "k8s.io/apimachinery/pkg/types" + v1 "github.com/SAP/sap-btp-service-operator/api/v1" "github.com/SAP/sap-btp-service-operator/client/sm" "github.com/go-logr/logr" From b61824c0c96510351659e15f90a1436e8773016b Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 21 Nov 2024 11:45:34 +0200 Subject: [PATCH 04/74] remove rate limit error from state --- api/common/consts.go | 2 + controllers/serviceinstance_controller.go | 63 +++++++++++++++++++++-- internal/utils/parameters.go | 43 +++++++++------- 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/api/common/consts.go b/api/common/consts.go index dd1a2166..f0380960 100644 --- a/api/common/consts.go +++ b/api/common/consts.go @@ -3,6 +3,8 @@ package common const ( ManagedByBTPOperatorLabel = "services.cloud.sap.com/managed-by-sap-btp-operator" ClusterSecretLabel = "services.cloud.sap.com/cluster-secret" + InstanceSecretLabel = "services.cloud.sap.com/secretRef" + WatchSecretLabel = "services.cloud.sap.com/watchSecret" NamespaceLabel = "_namespace" K8sNameLabel = "_k8sname" diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index c7bdb923..118a616a 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -22,8 +22,10 @@ import ( "encoding/hex" "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/types" "net/http" "reflect" + "strings" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -180,6 +182,9 @@ func (r *ServiceInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { return false }, UpdateFunc: func(e event.UpdateEvent) bool { + if e.ObjectNew.GetLabels()[common.WatchSecretLabel] != "true" { + return false + } return isSecretDataChanged(e) }, DeleteFunc: func(e event.DeleteEvent) bool { @@ -204,7 +209,7 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient log := utils.GetLogger(ctx) log.Info("Creating instance in SM") updateHashedSpecValue(serviceInstance) - _, instanceParameters, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.ParametersFrom, serviceInstance.Spec.Parameters) + _, instanceParameters, secrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.ParametersFrom, serviceInstance.Spec.Parameters) if err != nil { // if parameters are invalid there is nothing we can do, the user should fix it according to the error message in the condition log.Error(err, "failed to parse instance parameters") @@ -251,16 +256,38 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient log.Info(fmt.Sprintf("Instance provisioned successfully, instanceID: %s, subaccountID: %s", serviceInstance.Status.InstanceID, serviceInstance.Status.SubaccountID)) utils.SetSuccessConditions(smClientTypes.CREATE, serviceInstance) + if len(secrets) > 0 { + if serviceInstance.Labels == nil { + serviceInstance.Labels = make(map[string]string) + } + for key := range secrets { + serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" + verifySecretHaveWatchLabel(ctx, secrets[key], r, log) + } + return ctrl.Result{}, r.Client.Update(ctx, serviceInstance) + } return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } +func verifySecretHaveWatchLabel(ctx context.Context, secret *corev1.Secret, r *ServiceInstanceReconciler, log logr.Logger) { + if secret != nil && (secret.Labels == nil || secret.Labels[common.WatchSecretLabel] != "true") { + if secret.Labels == nil { + secret.Labels = make(map[string]string) + } + secret.Labels[common.WatchSecretLabel] = "true" + if err := r.Client.Update(ctx, secret); err != nil { + log.Error(err, "failed to update secret with watch label") + } + } +} + func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) { log := utils.GetLogger(ctx) log.Info(fmt.Sprintf("updating instance %s in SM", serviceInstance.Status.InstanceID)) updateHashedSpecValue(serviceInstance) - _, instanceParameters, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.ParametersFrom, serviceInstance.Spec.Parameters) + _, instanceParameters, secrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.ParametersFrom, serviceInstance.Spec.Parameters) if err != nil { log.Error(err, "failed to parse instance parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance) @@ -291,6 +318,23 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient } log.Info("Instance updated successfully") utils.SetSuccessConditions(smClientTypes.UPDATE, serviceInstance) + if len(secrets) > 0 { + if serviceInstance.Labels == nil { + serviceInstance.Labels = make(map[string]string) + } else { // remove old secret labels + for labelKey := range serviceInstance.Labels { + if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { + delete(serviceInstance.Labels, labelKey) + } + } + } + // add new secret labels + for key := range secrets { + serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" + verifySecretHaveWatchLabel(ctx, secrets[key], r, log) + } + return ctrl.Result{}, r.Client.Update(ctx, serviceInstance) + } return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } @@ -766,7 +810,20 @@ type SecretPredicate struct { func (r *ServiceInstanceReconciler) findRequestsForSecret(ctx context.Context, secret client.Object) []reconcile.Request { instancesToUpdate := make([]reconcile.Request, 0) - // TODO + var instances v1.ServiceInstanceList + labelSelector := client.MatchingLabels{common.InstanceSecretLabel + "-" + string(secret.GetUID()): "true"} + if err := r.Client.List(ctx, &instances, labelSelector); err != nil { + r.Log.Error(err, "failed to list service instances") + return nil + } + for _, instance := range instances.Items { + instancesToUpdate = append(instancesToUpdate, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: instance.Name, + Namespace: instance.Namespace, + }, + }) + } return instancesToUpdate } diff --git a/internal/utils/parameters.go b/internal/utils/parameters.go index 7d4b18dc..cae245ff 100644 --- a/internal/utils/parameters.go +++ b/internal/utils/parameters.go @@ -3,8 +3,8 @@ package utils import ( "context" "encoding/json" - "fmt" + "github.com/SAP/sap-btp-service-operator/api/common" servicesv1 "github.com/SAP/sap-btp-service-operator/api/v1" corev1 "k8s.io/api/core/v1" @@ -20,14 +20,16 @@ import ( // secret values. // The second return value is parameters marshalled to byt array // The third return value is any error that caused the function to fail. -func BuildSMRequestParameters(namespace string, parametersFrom []servicesv1.ParametersFromSource, parameters *runtime.RawExtension) (map[string]interface{}, []byte, error) { +func BuildSMRequestParameters(namespace string, parametersFrom []servicesv1.ParametersFromSource, parameters *runtime.RawExtension) (map[string]interface{}, []byte, map[string]*corev1.Secret, error) { params := make(map[string]interface{}) + secretsSet := map[string]*corev1.Secret{} if len(parametersFrom) > 0 { for _, p := range parametersFrom { - fps, err := fetchParametersFromSource(namespace, &p) + fps, secret, err := fetchParametersFromSource(namespace, &p) if err != nil { - return nil, nil, err + return nil, nil, nil, err } + secretsSet[string(secret.UID)] = secret for k, v := range fps { // we don't want to add shared param because sm api does not support updating // shared param with other params, for sharing we have different function. @@ -35,7 +37,7 @@ func BuildSMRequestParameters(namespace string, parametersFrom []servicesv1.Para continue } if _, ok := params[k]; ok { - return nil, nil, fmt.Errorf("conflict: duplicate entry for parameter %q", k) + return nil, nil, nil, fmt.Errorf("conflict: duplicate entry for parameter %q", k) } params[k] = v } @@ -44,11 +46,11 @@ func BuildSMRequestParameters(namespace string, parametersFrom []servicesv1.Para if parameters != nil { pp, err := UnmarshalRawParameters(parameters.Raw) if err != nil { - return nil, nil, err + return nil, nil, nil, err } for k, v := range pp { if _, ok := params[k]; ok { - return nil, nil, fmt.Errorf("conflict: duplicate entry for parameter %q", k) + return nil, nil, nil, fmt.Errorf("conflict: duplicate entry for parameter %q", k) } params[k] = v } @@ -60,9 +62,9 @@ func BuildSMRequestParameters(namespace string, parametersFrom []servicesv1.Para parametersRaw, err := MarshalRawParameters(params) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return params, parametersRaw, nil + return params, parametersRaw, secretsSet, nil } // UnmarshalRawParameters produces a map structure from a given raw YAML/JSON input @@ -94,31 +96,36 @@ func unmarshalJSON(in []byte) (map[string]interface{}, error) { } // fetchSecretKeyValue requests and returns the contents of the given secret key -func fetchSecretKeyValue(namespace string, secretKeyRef *servicesv1.SecretKeyReference) ([]byte, error) { +func fetchSecretKeyValue(namespace string, secretKeyRef *servicesv1.SecretKeyReference) ([]byte, *corev1.Secret, error) { secret := &corev1.Secret{} err := GetSecretWithFallback(context.Background(), types.NamespacedName{Namespace: namespace, Name: secretKeyRef.Name}, secret) if err != nil { - return nil, err + return nil, nil, err + } + if secret.Labels == nil { + secret.Labels = make(map[string]string) } - return secret.Data[secretKeyRef.Key], nil + secret.Labels[common.WatchSecretLabel] = "true" + + return secret.Data[secretKeyRef.Key], secret, nil } // fetchParametersFromSource fetches data from a specified external source and // represents it in the parameters map format -func fetchParametersFromSource(namespace string, parametersFrom *servicesv1.ParametersFromSource) (map[string]interface{}, error) { +func fetchParametersFromSource(namespace string, parametersFrom *servicesv1.ParametersFromSource) (map[string]interface{}, *corev1.Secret, error) { var params map[string]interface{} if parametersFrom.SecretKeyRef != nil { - data, err := fetchSecretKeyValue(namespace, parametersFrom.SecretKeyRef) + data, secret, err := fetchSecretKeyValue(namespace, parametersFrom.SecretKeyRef) if err != nil { - return nil, err + return nil, nil, err } p, err := unmarshalJSON(data) if err != nil { - return nil, err + return nil, nil, err } params = p - + return params, secret, nil } - return params, nil + return params, nil, nil } From 400ffcbd685fc8c0db08e6526a923d66c5a0ea97 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 21 Nov 2024 11:53:11 +0200 Subject: [PATCH 05/74] remove rate limit error from state --- controllers/servicebinding_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/servicebinding_controller.go b/controllers/servicebinding_controller.go index fef2f309..f716b4ee 100644 --- a/controllers/servicebinding_controller.go +++ b/controllers/servicebinding_controller.go @@ -259,7 +259,7 @@ func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient s log := utils.GetLogger(ctx) log.Info("Creating smBinding in SM") serviceBinding.Status.InstanceID = serviceInstance.Status.InstanceID - _, bindingParameters, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.ParametersFrom, serviceBinding.Spec.Parameters) + _, bindingParameters, _, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.ParametersFrom, serviceBinding.Spec.Parameters) if err != nil { log.Error(err, "failed to parse smBinding parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceBinding) From 58e7a7d61aeca867482d32f98964e2e176a24dbc Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 21 Nov 2024 12:20:32 +0200 Subject: [PATCH 06/74] remove rate limit error from state --- controllers/serviceinstance_controller.go | 66 +++++++++++++---------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 118a616a..7f223ce8 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -215,7 +215,20 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient log.Error(err, "failed to parse instance parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceInstance) } - + if len(secrets) > 0 { + if serviceInstance.Labels == nil { + serviceInstance.Labels = make(map[string]string) + } + for key := range secrets { + serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" + verifySecretHaveWatchLabel(ctx, secrets[key], r, log) + } + err = r.Client.Update(ctx, serviceInstance) + if err != nil { + log.Error(err, "failed to Update instance with secret labels") + return ctrl.Result{}, err + } + } provision, provisionErr := smClient.Provision(&smClientTypes.ServiceInstance{ Name: serviceInstance.Spec.ExternalName, ServicePlanID: serviceInstance.Spec.ServicePlanID, @@ -256,16 +269,7 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient log.Info(fmt.Sprintf("Instance provisioned successfully, instanceID: %s, subaccountID: %s", serviceInstance.Status.InstanceID, serviceInstance.Status.SubaccountID)) utils.SetSuccessConditions(smClientTypes.CREATE, serviceInstance) - if len(secrets) > 0 { - if serviceInstance.Labels == nil { - serviceInstance.Labels = make(map[string]string) - } - for key := range secrets { - serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" - verifySecretHaveWatchLabel(ctx, secrets[key], r, log) - } - return ctrl.Result{}, r.Client.Update(ctx, serviceInstance) - } + return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } @@ -292,7 +296,27 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient log.Error(err, "failed to parse instance parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance) } - + if len(secrets) > 0 { + if serviceInstance.Labels == nil { + serviceInstance.Labels = make(map[string]string) + } else { // remove old secret labels + for labelKey := range serviceInstance.Labels { + if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { + delete(serviceInstance.Labels, labelKey) + } + } + } + // add new secret labels + for key := range secrets { + serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" + verifySecretHaveWatchLabel(ctx, secrets[key], r, log) + } + err = r.Client.Update(ctx, serviceInstance) + if err != nil { + log.Error(err, "failed to Update instance with secret labels") + return ctrl.Result{}, err + } + } _, operationURL, err := smClient.UpdateInstance(serviceInstance.Status.InstanceID, &smClientTypes.ServiceInstance{ Name: serviceInstance.Spec.ExternalName, ServicePlanID: serviceInstance.Spec.ServicePlanID, @@ -318,23 +342,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient } log.Info("Instance updated successfully") utils.SetSuccessConditions(smClientTypes.UPDATE, serviceInstance) - if len(secrets) > 0 { - if serviceInstance.Labels == nil { - serviceInstance.Labels = make(map[string]string) - } else { // remove old secret labels - for labelKey := range serviceInstance.Labels { - if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { - delete(serviceInstance.Labels, labelKey) - } - } - } - // add new secret labels - for key := range secrets { - serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" - verifySecretHaveWatchLabel(ctx, secrets[key], r, log) - } - return ctrl.Result{}, r.Client.Update(ctx, serviceInstance) - } + return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } From f3791d2620a46bf81bf9e59401d7c10aaec3096e Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 21 Nov 2024 12:41:48 +0200 Subject: [PATCH 07/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- internal/utils/parameters_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/utils/parameters_test.go b/internal/utils/parameters_test.go index d5e34ae0..738b61f5 100644 --- a/internal/utils/parameters_test.go +++ b/internal/utils/parameters_test.go @@ -13,11 +13,12 @@ var _ = Describe("Parameters", func() { var parametersFrom []v1.ParametersFromSource parameters := (*runtime.RawExtension)(nil) - params, rawParam, err := BuildSMRequestParameters("", parametersFrom, parameters) + params, rawParam, secrets, err := BuildSMRequestParameters("", parametersFrom, parameters) Expect(err).To(BeNil()) Expect(params).To(BeNil()) Expect(rawParam).To(BeNil()) + Expect(len(secrets)).To(BeZero()) }) It("handles parameters from source", func() { var parametersFrom []v1.ParametersFromSource @@ -25,11 +26,12 @@ var _ = Describe("Parameters", func() { Raw: []byte(`{"key":"value"}`), } - params, rawParam, err := BuildSMRequestParameters("", parametersFrom, parameters) + params, rawParam, secrets, err := BuildSMRequestParameters("", parametersFrom, parameters) Expect(err).To(BeNil()) Expect(params).To(Equal(map[string]interface{}{"key": "value"})) Expect(rawParam).To(Equal([]byte(`{"key":"value"}`))) + Expect(len(secrets)).To(BeZero()) }) }) }) From 686158f8b1be305e5c5fbd844a17fc530fecbb89 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 21 Nov 2024 13:44:12 +0200 Subject: [PATCH 08/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 7f223ce8..d81a54e2 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -215,7 +215,7 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient log.Error(err, "failed to parse instance parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceInstance) } - if len(secrets) > 0 { + if len(secrets) > 0 && serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges { if serviceInstance.Labels == nil { serviceInstance.Labels = make(map[string]string) } @@ -296,7 +296,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient log.Error(err, "failed to parse instance parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance) } - if len(secrets) > 0 { + if len(secrets) > 0 && serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges { if serviceInstance.Labels == nil { serviceInstance.Labels = make(map[string]string) } else { // remove old secret labels From fe25616c39b3b070f429f8f89804b7c152186865 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 21 Nov 2024 13:58:21 +0200 Subject: [PATCH 09/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 3 ++- internal/utils/parameters.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index d81a54e2..3b64fb31 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -22,11 +22,12 @@ import ( "encoding/hex" "encoding/json" "fmt" - "k8s.io/apimachinery/pkg/types" "net/http" "reflect" "strings" + "k8s.io/apimachinery/pkg/types" + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/event" diff --git a/internal/utils/parameters.go b/internal/utils/parameters.go index cae245ff..396a089e 100644 --- a/internal/utils/parameters.go +++ b/internal/utils/parameters.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/SAP/sap-btp-service-operator/api/common" servicesv1 "github.com/SAP/sap-btp-service-operator/api/v1" From a89b9bc9ff69e11222fa2a4e4873dcce28b906c6 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 21 Nov 2024 15:02:30 +0200 Subject: [PATCH 10/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 16 ++-------- internal/utils/controller_util.go | 14 +++++++++ internal/utils/controller_util_test.go | 36 ++++++++++++++++++++--- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 3b64fb31..b38735a1 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -222,7 +222,7 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient } for key := range secrets { serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" - verifySecretHaveWatchLabel(ctx, secrets[key], r, log) + utils.VerifySecretHaveWatchLabel(ctx, secrets[key], r.Client) } err = r.Client.Update(ctx, serviceInstance) if err != nil { @@ -274,18 +274,6 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } -func verifySecretHaveWatchLabel(ctx context.Context, secret *corev1.Secret, r *ServiceInstanceReconciler, log logr.Logger) { - if secret != nil && (secret.Labels == nil || secret.Labels[common.WatchSecretLabel] != "true") { - if secret.Labels == nil { - secret.Labels = make(map[string]string) - } - secret.Labels[common.WatchSecretLabel] = "true" - if err := r.Client.Update(ctx, secret); err != nil { - log.Error(err, "failed to update secret with watch label") - } - } -} - func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) { log := utils.GetLogger(ctx) log.Info(fmt.Sprintf("updating instance %s in SM", serviceInstance.Status.InstanceID)) @@ -310,7 +298,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient // add new secret labels for key := range secrets { serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" - verifySecretHaveWatchLabel(ctx, secrets[key], r, log) + utils.VerifySecretHaveWatchLabel(ctx, secrets[key], r.Client) } err = r.Client.Update(ctx, serviceInstance) if err != nil { diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 0e62968b..43e1c267 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + v12 "k8s.io/api/core/v1" "net/http" "strings" "time" @@ -244,3 +245,16 @@ func serialize(value interface{}) ([]byte, format, error) { } return data, JSON, nil } + +func VerifySecretHaveWatchLabel(ctx context.Context, secret *v12.Secret, k8sClient client.Client) { + log := GetLogger(ctx) + if secret != nil && (secret.Labels == nil || secret.Labels[common.WatchSecretLabel] != "true") { + if secret.Labels == nil { + secret.Labels = make(map[string]string) + } + secret.Labels[common.WatchSecretLabel] = "true" + if err := k8sClient.Update(ctx, secret); err != nil { + log.Error(err, "failed to update secret with watch label") + } + } +} diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index ccb0ddaa..4339b472 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -2,16 +2,17 @@ package utils import ( "encoding/json" - "net/http" - - "k8s.io/apimachinery/pkg/types" - + "github.com/SAP/sap-btp-service-operator/api/common" v1 "github.com/SAP/sap-btp-service-operator/api/v1" "github.com/SAP/sap-btp-service-operator/client/sm" "github.com/go-logr/logr" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" authv1 "k8s.io/api/authentication/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "net/http" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -48,6 +49,7 @@ var _ = Describe("Controller Util", func() { }) }) + Context("SliceContains", func() { It("slice contains", func() { slice := []string{"element1", "element2", "element3"} @@ -198,4 +200,30 @@ var _ = Describe("Controller Util", func() { Expect(err.Error()).To(ContainSubstring("invalid format: expected 'namespace/name")) }) }) + + Context("VerifySecretHaveWatchLabel", func() { + It("should add the watch label to the secret if it is missing", func() { + // Create a fake client + + // Create a secret without the watch label + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "default", + }, + } + err := k8sClient.Create(ctx, secret) + Expect(err).ToNot(HaveOccurred()) + // Call the function + VerifySecretHaveWatchLabel(ctx, secret, k8sClient) + + // Get the updated secret + updatedSecret := &corev1.Secret{} + err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) + Expect(err).ToNot(HaveOccurred()) + + // Verify the label was added + Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) + }) + }) }) From 927f3c1c7d890fad12b2ba077e32787523397994 Mon Sep 17 00:00:00 2001 From: I501080 Date: Mon, 25 Nov 2024 09:58:15 +0200 Subject: [PATCH 11/74] subscribe to secret change --- api/v1/serviceinstance_types.go | 4 ++ controllers/servicebinding_controller.go | 2 +- controllers/serviceinstance_controller.go | 86 +++++++++-------------- internal/utils/controller_util.go | 3 +- internal/utils/controller_util_test.go | 3 +- internal/utils/parameters.go | 17 +++-- internal/utils/parameters_test.go | 6 +- 7 files changed, 55 insertions(+), 66 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 37cac48b..9e78d2f9 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -212,3 +212,7 @@ func (si *ServiceInstance) Hub() {} func (si *ServiceInstance) ShouldBeShared() bool { return si.Spec.Shared != nil && *si.Spec.Shared } + +func (si *ServiceInstance) IsSubscribedToSecretKeyRefChange() bool { + return si.Spec.SubscribeToSecretChanges != nil && *si.Spec.SubscribeToSecretChanges +} diff --git a/controllers/servicebinding_controller.go b/controllers/servicebinding_controller.go index f716b4ee..2efe4770 100644 --- a/controllers/servicebinding_controller.go +++ b/controllers/servicebinding_controller.go @@ -259,7 +259,7 @@ func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient s log := utils.GetLogger(ctx) log.Info("Creating smBinding in SM") serviceBinding.Status.InstanceID = serviceInstance.Status.InstanceID - _, bindingParameters, _, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.ParametersFrom, serviceBinding.Spec.Parameters) + bindingParameters, _, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.Parameters, serviceBinding.Spec.ParametersFrom) if err != nil { log.Error(err, "failed to parse smBinding parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceBinding) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index b38735a1..a2f65391 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -22,11 +22,9 @@ import ( "encoding/hex" "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/types" "net/http" "reflect" - "strings" - - "k8s.io/apimachinery/pkg/types" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -173,17 +171,13 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } func (r *ServiceInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { - c := ctrl.NewControllerManagedBy(mgr). - For(&v1.ServiceInstance{}). - WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}) - secretPredicate := SecretPredicate{ Funcs: predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { return false }, UpdateFunc: func(e event.UpdateEvent) bool { - if e.ObjectNew.GetLabels()[common.WatchSecretLabel] != "true" { + if _, ok := e.ObjectNew.GetLabels()[common.WatchSecretLabel]; !ok { return false } return isSecretDataChanged(e) @@ -197,39 +191,27 @@ func (r *ServiceInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { }, } - c.Watches( - &corev1.Secret{}, - handler.EnqueueRequestsFromMapFunc(r.findRequestsForSecret), - builder.WithPredicates(secretPredicate), - ) - - return c.Complete(r) + return ctrl.NewControllerManagedBy(mgr). + For(&v1.ServiceInstance{}). + WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.findRequestsForSecret), + builder.WithPredicates(secretPredicate), + ).Complete(r) } func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) { log := utils.GetLogger(ctx) log.Info("Creating instance in SM") updateHashedSpecValue(serviceInstance) - _, instanceParameters, secrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.ParametersFrom, serviceInstance.Spec.Parameters) + instanceParameters, err := r.buildSMRequestParameters(ctx, serviceInstance) if err != nil { // if parameters are invalid there is nothing we can do, the user should fix it according to the error message in the condition log.Error(err, "failed to parse instance parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceInstance) } - if len(secrets) > 0 && serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges { - if serviceInstance.Labels == nil { - serviceInstance.Labels = make(map[string]string) - } - for key := range secrets { - serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" - utils.VerifySecretHaveWatchLabel(ctx, secrets[key], r.Client) - } - err = r.Client.Update(ctx, serviceInstance) - if err != nil { - log.Error(err, "failed to Update instance with secret labels") - return ctrl.Result{}, err - } - } + provision, provisionErr := smClient.Provision(&smClientTypes.ServiceInstance{ Name: serviceInstance.Spec.ExternalName, ServicePlanID: serviceInstance.Spec.ServicePlanID, @@ -280,32 +262,12 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient updateHashedSpecValue(serviceInstance) - _, instanceParameters, secrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.ParametersFrom, serviceInstance.Spec.Parameters) + instanceParameters, err := r.buildSMRequestParameters(ctx, serviceInstance) if err != nil { log.Error(err, "failed to parse instance parameters") return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance) } - if len(secrets) > 0 && serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges { - if serviceInstance.Labels == nil { - serviceInstance.Labels = make(map[string]string) - } else { // remove old secret labels - for labelKey := range serviceInstance.Labels { - if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { - delete(serviceInstance.Labels, labelKey) - } - } - } - // add new secret labels - for key := range secrets { - serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" - utils.VerifySecretHaveWatchLabel(ctx, secrets[key], r.Client) - } - err = r.Client.Update(ctx, serviceInstance) - if err != nil { - log.Error(err, "failed to Update instance with secret labels") - return ctrl.Result{}, err - } - } + _, operationURL, err := smClient.UpdateInstance(serviceInstance.Status.InstanceID, &smClientTypes.ServiceInstance{ Name: serviceInstance.Spec.ExternalName, ServicePlanID: serviceInstance.Spec.ServicePlanID, @@ -619,6 +581,26 @@ func (r *ServiceInstanceReconciler) handleInstanceSharingError(ctx context.Conte return ctrl.Result{Requeue: isTransient}, utils.UpdateStatus(ctx, r.Client, object) } +func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context, serviceInstance *v1.ServiceInstance) ([]byte, error) { + log := utils.GetLogger(ctx) + instanceParameters, secrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom) + if serviceInstance.IsSubscribedToSecretKeyRefChange() && len(secrets) > 0 { + if serviceInstance.Labels == nil { + serviceInstance.Labels = make(map[string]string) + } + for key := range secrets { + serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" + utils.VerifySecretHaveWatchLabel(ctx, secrets[key], r.Client) + } + err := r.Client.Update(ctx, serviceInstance) + if err != nil { + log.Error(err, "failed to Update instance with secret labels") + return nil, err + } + } + return instanceParameters, err +} + func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool { log := utils.GetLogger(ctx) if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) { diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 43e1c267..062c39c7 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -5,11 +5,12 @@ import ( "encoding/json" "errors" "fmt" - v12 "k8s.io/api/core/v1" "net/http" "strings" "time" + v12 "k8s.io/api/core/v1" + "github.com/SAP/sap-btp-service-operator/api/common" "github.com/SAP/sap-btp-service-operator/client/sm" smClientTypes "github.com/SAP/sap-btp-service-operator/client/sm/types" diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index 4339b472..c63f64cc 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -2,6 +2,8 @@ package utils import ( "encoding/json" + "net/http" + "github.com/SAP/sap-btp-service-operator/api/common" v1 "github.com/SAP/sap-btp-service-operator/api/v1" "github.com/SAP/sap-btp-service-operator/client/sm" @@ -12,7 +14,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "net/http" "sigs.k8s.io/controller-runtime/pkg/client" ) diff --git a/internal/utils/parameters.go b/internal/utils/parameters.go index 396a089e..dab5d232 100644 --- a/internal/utils/parameters.go +++ b/internal/utils/parameters.go @@ -21,14 +21,14 @@ import ( // secret values. // The second return value is parameters marshalled to byt array // The third return value is any error that caused the function to fail. -func BuildSMRequestParameters(namespace string, parametersFrom []servicesv1.ParametersFromSource, parameters *runtime.RawExtension) (map[string]interface{}, []byte, map[string]*corev1.Secret, error) { +func BuildSMRequestParameters(namespace string, parameters *runtime.RawExtension, parametersFrom []servicesv1.ParametersFromSource) ([]byte, map[string]*corev1.Secret, error) { params := make(map[string]interface{}) secretsSet := map[string]*corev1.Secret{} if len(parametersFrom) > 0 { for _, p := range parametersFrom { fps, secret, err := fetchParametersFromSource(namespace, &p) if err != nil { - return nil, nil, nil, err + return nil, nil, err } secretsSet[string(secret.UID)] = secret for k, v := range fps { @@ -38,20 +38,23 @@ func BuildSMRequestParameters(namespace string, parametersFrom []servicesv1.Para continue } if _, ok := params[k]; ok { - return nil, nil, nil, fmt.Errorf("conflict: duplicate entry for parameter %q", k) + return nil, nil, fmt.Errorf("conflict: duplicate entry for parameter %q", k) } params[k] = v } } + if subscribeToSecretRefChanges { + + } } if parameters != nil { pp, err := UnmarshalRawParameters(parameters.Raw) if err != nil { - return nil, nil, nil, err + return nil, nil, err } for k, v := range pp { if _, ok := params[k]; ok { - return nil, nil, nil, fmt.Errorf("conflict: duplicate entry for parameter %q", k) + return nil, nil, fmt.Errorf("conflict: duplicate entry for parameter %q", k) } params[k] = v } @@ -63,9 +66,9 @@ func BuildSMRequestParameters(namespace string, parametersFrom []servicesv1.Para parametersRaw, err := MarshalRawParameters(params) if err != nil { - return nil, nil, nil, err + return nil, nil, err } - return params, parametersRaw, secretsSet, nil + return parametersRaw, secretsSet, nil } // UnmarshalRawParameters produces a map structure from a given raw YAML/JSON input diff --git a/internal/utils/parameters_test.go b/internal/utils/parameters_test.go index 738b61f5..53b08d38 100644 --- a/internal/utils/parameters_test.go +++ b/internal/utils/parameters_test.go @@ -13,10 +13,9 @@ var _ = Describe("Parameters", func() { var parametersFrom []v1.ParametersFromSource parameters := (*runtime.RawExtension)(nil) - params, rawParam, secrets, err := BuildSMRequestParameters("", parametersFrom, parameters) + rawParam, secrets, err := BuildSMRequestParameters("", parameters, parametersFrom) Expect(err).To(BeNil()) - Expect(params).To(BeNil()) Expect(rawParam).To(BeNil()) Expect(len(secrets)).To(BeZero()) }) @@ -26,10 +25,9 @@ var _ = Describe("Parameters", func() { Raw: []byte(`{"key":"value"}`), } - params, rawParam, secrets, err := BuildSMRequestParameters("", parametersFrom, parameters) + rawParam, secrets, err := BuildSMRequestParameters("", parameters, parametersFrom) Expect(err).To(BeNil()) - Expect(params).To(Equal(map[string]interface{}{"key": "value"})) Expect(rawParam).To(Equal([]byte(`{"key":"value"}`))) Expect(len(secrets)).To(BeZero()) }) From 5e055c38ec280fbb3ef04363d1750c17cb120608 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 25 Nov 2024 14:15:15 +0200 Subject: [PATCH 12/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 29 +++++++++++++-- internal/utils/controller_util.go | 45 +++++++++++++++++++++-- internal/utils/controller_util_test.go | 30 +++++++++++++-- internal/utils/parameters.go | 3 -- 4 files changed, 95 insertions(+), 12 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index a2f65391..72a76cc0 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/types" "net/http" "reflect" + "strings" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -338,7 +339,11 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI log.Info("Deleting instance async") return r.handleAsyncDelete(ctx, serviceInstance, operationURL) } - + for labelKey := range serviceInstance.Labels { + if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { + utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Namespace, labelKey) + } + } log.Info("Instance was deleted successfully, removing finalizer") // remove our finalizer from the list and update it. return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName) @@ -585,12 +590,30 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context log := utils.GetLogger(ctx) instanceParameters, secrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom) if serviceInstance.IsSubscribedToSecretKeyRefChange() && len(secrets) > 0 { + existingSecrets := make(map[string]string) if serviceInstance.Labels == nil { serviceInstance.Labels = make(map[string]string) + } else { // remove old secret labels + for labelKey := range serviceInstance.Labels { + if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { + existingSecrets[labelKey] = "false" + } + } } for key := range secrets { - serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" - utils.VerifySecretHaveWatchLabel(ctx, secrets[key], r.Client) + if _, ok := existingSecrets[common.InstanceSecretLabel+"-"+key]; ok { + // this secret was already on the instance and should stay + existingSecrets[common.InstanceSecretLabel+"-"+key] = "true" + } else { + // this is a new secret on the instance + serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" + utils.IncreaseSecretHaveWatchLabel(ctx, secrets[key], r.Client) + } + } + for key := range existingSecrets { + if existingSecrets[key] == "false" { + utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Name, key) + } } err := r.Client.Update(ctx, serviceInstance) if err != nil { diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 062c39c7..006cc83a 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "time" @@ -247,13 +248,51 @@ func serialize(value interface{}) ([]byte, format, error) { return data, JSON, nil } -func VerifySecretHaveWatchLabel(ctx context.Context, secret *v12.Secret, k8sClient client.Client) { +func IncreaseSecretHaveWatchLabel(ctx context.Context, secret *v12.Secret, k8sClient client.Client) { log := GetLogger(ctx) - if secret != nil && (secret.Labels == nil || secret.Labels[common.WatchSecretLabel] != "true") { + if secret != nil { if secret.Labels == nil { secret.Labels = make(map[string]string) } - secret.Labels[common.WatchSecretLabel] = "true" + if _, exists := secret.Labels[common.WatchSecretLabel]; exists { + counter, err := strconv.Atoi(secret.Labels[common.WatchSecretLabel]) + if err != nil { + log.Error(err, "failed to convert label value to integer") + return + } + secret.Labels[common.WatchSecretLabel] = strconv.Itoa(counter + 1) + } else { + secret.Labels[common.WatchSecretLabel] = "1" + } + if err := k8sClient.Update(ctx, secret); err != nil { + log.Error(err, "failed to update secret with watch label") + } + } +} + +func DecreaseSecretWatchLabel(ctx context.Context, k8sClient client.Client, namespace string, name string) { + log := GetLogger(ctx) + secret := &v12.Secret{} + err := k8sClient.Get(ctx, apimachinerytypes.NamespacedName{Name: name, Namespace: namespace}, secret) + if err != nil { + log.Error(err, "failed to get secret with name %s and namespace %s", name, namespace) + return + } + if secret.Labels == nil { + return + } + if _, exists := secret.Labels[common.WatchSecretLabel]; exists { + counter, err := strconv.Atoi(secret.Labels[common.WatchSecretLabel]) + if err != nil { + log.Error(err, "failed to convert label value to integer") + return + } + if counter == 1 { + log.Info(fmt.Sprintf("deleting watch label from secret %s", secret.UID)) + delete(secret.Labels, common.WatchSecretLabel) + } else { + secret.Labels[common.WatchSecretLabel] = strconv.Itoa(counter - 1) + } if err := k8sClient.Update(ctx, secret); err != nil { log.Error(err, "failed to update secret with watch label") } diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index c63f64cc..212e81bd 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -202,7 +202,7 @@ var _ = Describe("Controller Util", func() { }) }) - Context("VerifySecretHaveWatchLabel", func() { + Context("IncreaseSecretHaveWatchLabel", func() { It("should add the watch label to the secret if it is missing", func() { // Create a fake client @@ -216,7 +216,7 @@ var _ = Describe("Controller Util", func() { err := k8sClient.Create(ctx, secret) Expect(err).ToNot(HaveOccurred()) // Call the function - VerifySecretHaveWatchLabel(ctx, secret, k8sClient) + IncreaseSecretHaveWatchLabel(ctx, secret, k8sClient) // Get the updated secret updatedSecret := &corev1.Secret{} @@ -224,7 +224,31 @@ var _ = Describe("Controller Util", func() { Expect(err).ToNot(HaveOccurred()) // Verify the label was added - Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) + Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("1")) + + IncreaseSecretHaveWatchLabel(ctx, secret, k8sClient) + + err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) + Expect(err).ToNot(HaveOccurred()) + + // Verify the label was added + Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("2")) + + DecreaseSecretWatchLabel(ctx, k8sClient, secret.Namespace, secret.Name) + + err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) + Expect(err).ToNot(HaveOccurred()) + + // Verify the label was added + Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("1")) + + DecreaseSecretWatchLabel(ctx, k8sClient, secret.Namespace, secret.Name) + + err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) + Expect(err).ToNot(HaveOccurred()) + + // Verify the label was added + Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("")) }) }) }) diff --git a/internal/utils/parameters.go b/internal/utils/parameters.go index dab5d232..250aac94 100644 --- a/internal/utils/parameters.go +++ b/internal/utils/parameters.go @@ -43,9 +43,6 @@ func BuildSMRequestParameters(namespace string, parameters *runtime.RawExtension params[k] = v } } - if subscribeToSecretRefChanges { - - } } if parameters != nil { pp, err := UnmarshalRawParameters(parameters.Raw) From 26d64e50a53eb77546bcee62ce178d3a8d4711b0 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 25 Nov 2024 14:29:12 +0200 Subject: [PATCH 13/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 72a76cc0..542153eb 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -22,11 +22,12 @@ import ( "encoding/hex" "encoding/json" "fmt" - "k8s.io/apimachinery/pkg/types" "net/http" "reflect" "strings" + "k8s.io/apimachinery/pkg/types" + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/event" From d5f450d92726ed6410f3ab9d60105971d8d43cb3 Mon Sep 17 00:00:00 2001 From: i065450 Date: Tue, 26 Nov 2024 09:02:56 +0200 Subject: [PATCH 14/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- internal/utils/parameters.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/internal/utils/parameters.go b/internal/utils/parameters.go index 250aac94..88de29c1 100644 --- a/internal/utils/parameters.go +++ b/internal/utils/parameters.go @@ -5,8 +5,6 @@ import ( "encoding/json" "fmt" - "github.com/SAP/sap-btp-service-operator/api/common" - servicesv1 "github.com/SAP/sap-btp-service-operator/api/v1" corev1 "k8s.io/api/core/v1" @@ -104,10 +102,6 @@ func fetchSecretKeyValue(namespace string, secretKeyRef *servicesv1.SecretKeyRef if err != nil { return nil, nil, err } - if secret.Labels == nil { - secret.Labels = make(map[string]string) - } - secret.Labels[common.WatchSecretLabel] = "true" return secret.Data[secretKeyRef.Key], secret, nil } From 4d63b683a47e72e8beda0bfbba22cda29c2ca959 Mon Sep 17 00:00:00 2001 From: i065450 Date: Tue, 26 Nov 2024 15:32:07 +0200 Subject: [PATCH 15/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/v1/serviceinstance_types.go | 4 +- ...ervices.cloud.sap.com_servicebindings.yaml | 2 +- ...rvices.cloud.sap.com_serviceinstances.yaml | 6 +- config/rbac/role.yaml | 31 +------ controllers/serviceinstance_controller.go | 2 +- sapbtp-operator-charts/templates/crd.yml | 88 ++++--------------- 6 files changed, 28 insertions(+), 105 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 9e78d2f9..018fd1fb 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -72,8 +72,10 @@ type ServiceInstanceSpec struct { // +optional ParametersFrom []ParametersFromSource `json:"parametersFrom,omitempty"` + // indicate instance will update on secrets from parametersFrom change // +optional - SubscribeToSecretChanges *bool `json:"subscribeToChanges,omitempty"` + SubscribeToSecretChanges *bool `json:"subscribeToSecretChanges,omitempty"` + // List of custom tags describing the ServiceInstance, will be copied to `ServiceBinding` secret in the key called `tags`. // +optional CustomTags []string `json:"customTags,omitempty"` diff --git a/config/crd/bases/services.cloud.sap.com_servicebindings.yaml b/config/crd/bases/services.cloud.sap.com_servicebindings.yaml index 9f6384d3..97aba465 100644 --- a/config/crd/bases/services.cloud.sap.com_servicebindings.yaml +++ b/config/crd/bases/services.cloud.sap.com_servicebindings.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.16.1 name: servicebindings.services.cloud.sap.com spec: group: services.cloud.sap.com diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index c6ea2391..c1c56cc4 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.16.1 name: serviceinstances.services.cloud.sap.com spec: group: services.cloud.sap.com @@ -140,7 +140,9 @@ spec: shared: description: Indicates the desired shared state type: boolean - subscribeToChanges: + subscribeToSecretChanges: + description: indicate instance will update on secrets from parametersFrom + change type: boolean userInfo: description: |- diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index de9857dc..74f1babd 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -17,17 +17,6 @@ rules: - "" resources: - events - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - secrets verbs: - create @@ -41,25 +30,6 @@ rules: - services.cloud.sap.com resources: - servicebindings - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - services.cloud.sap.com - resources: - - servicebindings/status - verbs: - - get - - patch - - update -- apiGroups: - - services.cloud.sap.com - resources: - serviceinstances verbs: - create @@ -72,6 +42,7 @@ rules: - apiGroups: - services.cloud.sap.com resources: + - servicebindings/status - serviceinstances/status verbs: - get diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 542153eb..d97dc783 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -590,7 +590,7 @@ func (r *ServiceInstanceReconciler) handleInstanceSharingError(ctx context.Conte func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context, serviceInstance *v1.ServiceInstance) ([]byte, error) { log := utils.GetLogger(ctx) instanceParameters, secrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom) - if serviceInstance.IsSubscribedToSecretKeyRefChange() && len(secrets) > 0 { + if serviceInstance.IsSubscribedToSecretKeyRefChange() { existingSecrets := make(map[string]string) if serviceInstance.Labels == nil { serviceInstance.Labels = make(map[string]string) diff --git a/sapbtp-operator-charts/templates/crd.yml b/sapbtp-operator-charts/templates/crd.yml index 551cbd69..899611fe 100644 --- a/sapbtp-operator-charts/templates/crd.yml +++ b/sapbtp-operator-charts/templates/crd.yml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.16.1 name: servicebindings.services.cloud.sap.com spec: group: services.cloud.sap.com @@ -81,7 +81,6 @@ spec: description: |- Parameters for the binding. - The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -198,16 +197,8 @@ spec: conditions: description: Service binding conditions items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -248,12 +239,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -365,7 +351,6 @@ spec: description: |- Parameters for the binding. - The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -467,16 +452,8 @@ spec: conditions: description: Service binding conditions items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -517,12 +494,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -568,7 +540,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.15.0 + controller-gen.kubebuilder.io/version: v0.16.1 name: serviceinstances.services.cloud.sap.com spec: group: services.cloud.sap.com @@ -654,7 +626,6 @@ spec: description: |- Provisioning parameters for the instance. - The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -705,6 +676,10 @@ spec: shared: description: Indicates the desired shared state type: boolean + subscribeToSecretChanges: + description: indicate instance will update on secrets from parametersFrom + change + type: boolean userInfo: description: |- UserInfo contains information about the user that last modified this @@ -746,16 +721,8 @@ spec: conditions: description: Service instance conditions items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -796,12 +763,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -914,7 +876,6 @@ spec: description: |- Provisioning parameters for the instance. - The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -1006,16 +967,8 @@ spec: conditions: description: Service instance conditions items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -1056,12 +1009,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string From 6216fe45b905663f2e5f297f170b6b3cdd709be1 Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 27 Nov 2024 16:58:28 +0200 Subject: [PATCH 16/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 37 ++++++++++++++++++++--- internal/utils/controller_util.go | 15 +++++---- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index d97dc783..862e6fa5 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -589,7 +589,8 @@ func (r *ServiceInstanceReconciler) handleInstanceSharingError(ctx context.Conte func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context, serviceInstance *v1.ServiceInstance) ([]byte, error) { log := utils.GetLogger(ctx) - instanceParameters, secrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom) + instanceParameters, newSecretsMap, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom) + shouldUpdate := false if serviceInstance.IsSubscribedToSecretKeyRefChange() { existingSecrets := make(map[string]string) if serviceInstance.Labels == nil { @@ -601,21 +602,47 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } } } - for key := range secrets { + for key := range newSecretsMap { if _, ok := existingSecrets[common.InstanceSecretLabel+"-"+key]; ok { // this secret was already on the instance and should stay existingSecrets[common.InstanceSecretLabel+"-"+key] = "true" } else { // this is a new secret on the instance - serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = "true" - utils.IncreaseSecretHaveWatchLabel(ctx, secrets[key], r.Client) + shouldUpdate = true + secret := newSecretsMap[key] + serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = secret.Name + utils.IncreaseSecretHaveWatchLabel(ctx, secret, r.Client) } } for key := range existingSecrets { if existingSecrets[key] == "false" { - utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Name, key) + // this secret is not on the instance anymore and should be deleted + shouldUpdate = true + err = utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key]) + if err != nil { + log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) + } else { + delete(serviceInstance.Labels, key) + } + } + } + } else { + if serviceInstance.Labels != nil { + // remove all secret labels + for key := range serviceInstance.Labels { + if strings.HasPrefix(key, common.InstanceSecretLabel) { + shouldUpdate = true + err = utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key]) + if err != nil { + log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) + } else { + delete(serviceInstance.Labels, key) + } + } } } + } + if shouldUpdate { err := r.Client.Update(ctx, serviceInstance) if err != nil { log.Error(err, "failed to Update instance with secret labels") diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 006cc83a..7fa32f93 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -270,22 +270,20 @@ func IncreaseSecretHaveWatchLabel(ctx context.Context, secret *v12.Secret, k8sCl } } -func DecreaseSecretWatchLabel(ctx context.Context, k8sClient client.Client, namespace string, name string) { +func DecreaseSecretWatchLabel(ctx context.Context, k8sClient client.Client, namespace string, name string) error { log := GetLogger(ctx) secret := &v12.Secret{} err := k8sClient.Get(ctx, apimachinerytypes.NamespacedName{Name: name, Namespace: namespace}, secret) if err != nil { - log.Error(err, "failed to get secret with name %s and namespace %s", name, namespace) - return + return err } if secret.Labels == nil { - return + return nil } if _, exists := secret.Labels[common.WatchSecretLabel]; exists { counter, err := strconv.Atoi(secret.Labels[common.WatchSecretLabel]) if err != nil { - log.Error(err, "failed to convert label value to integer") - return + return err } if counter == 1 { log.Info(fmt.Sprintf("deleting watch label from secret %s", secret.UID)) @@ -293,8 +291,9 @@ func DecreaseSecretWatchLabel(ctx context.Context, k8sClient client.Client, name } else { secret.Labels[common.WatchSecretLabel] = strconv.Itoa(counter - 1) } - if err := k8sClient.Update(ctx, secret); err != nil { - log.Error(err, "failed to update secret with watch label") + if err = k8sClient.Update(ctx, secret); err != nil { + return err } } + return nil } From 23059fc03ec1d1ded77736302d89233b1fb4f358 Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 27 Nov 2024 17:04:00 +0200 Subject: [PATCH 17/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 862e6fa5..ad482174 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -342,7 +342,10 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI } for labelKey := range serviceInstance.Labels { if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { - utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Namespace, labelKey) + err := utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[labelKey]) + if err != nil { + return ctrl.Result{}, err + } } } log.Info("Instance was deleted successfully, removing finalizer") From 88d185496507540a1e3917db28c8ec599513079c Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 28 Nov 2024 10:41:12 +0200 Subject: [PATCH 18/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 30 ++++++++----- internal/utils/controller_util.go | 54 ++++++++++------------- internal/utils/controller_util_test.go | 39 ++++++++++------ 3 files changed, 67 insertions(+), 56 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index ad482174..94d913ab 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -179,10 +179,7 @@ func (r *ServiceInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { return false }, UpdateFunc: func(e event.UpdateEvent) bool { - if _, ok := e.ObjectNew.GetLabels()[common.WatchSecretLabel]; !ok { - return false - } - return isSecretDataChanged(e) + return utils.IsSecretWatched(e.ObjectNew) && isSecretDataChanged(e) }, DeleteFunc: func(e event.DeleteEvent) bool { return false @@ -342,8 +339,9 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI } for labelKey := range serviceInstance.Labels { if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { - err := utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[labelKey]) + err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[labelKey], serviceInstance.Name) if err != nil { + log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", labelKey)) return ctrl.Result{}, err } } @@ -593,6 +591,10 @@ func (r *ServiceInstanceReconciler) handleInstanceSharingError(ctx context.Conte func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context, serviceInstance *v1.ServiceInstance) ([]byte, error) { log := utils.GetLogger(ctx) instanceParameters, newSecretsMap, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom) + if err != nil { + log.Error(err, "failed to build instance parameters") + return nil, err + } shouldUpdate := false if serviceInstance.IsSubscribedToSecretKeyRefChange() { existingSecrets := make(map[string]string) @@ -614,19 +616,23 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context shouldUpdate = true secret := newSecretsMap[key] serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = secret.Name - utils.IncreaseSecretHaveWatchLabel(ctx, secret, r.Client) + err = utils.AddSecretHaveWatch(ctx, secret, r.Client, serviceInstance.Name) + if err != nil { + log.Error(err, fmt.Sprintf("failed to increase secret watch label with key %s", key)) + return nil, err + } } } for key := range existingSecrets { if existingSecrets[key] == "false" { // this secret is not on the instance anymore and should be deleted shouldUpdate = true - err = utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key]) + err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) if err != nil { log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) - } else { - delete(serviceInstance.Labels, key) + return nil, err } + delete(serviceInstance.Labels, key) } } } else { @@ -635,12 +641,12 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context for key := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretLabel) { shouldUpdate = true - err = utils.DecreaseSecretWatchLabel(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key]) + err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) if err != nil { log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) - } else { - delete(serviceInstance.Labels, key) + return nil, err } + delete(serviceInstance.Labels, key) } } } diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 7fa32f93..3c9d196e 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/http" - "strconv" "strings" "time" @@ -248,52 +247,45 @@ func serialize(value interface{}) ([]byte, format, error) { return data, JSON, nil } -func IncreaseSecretHaveWatchLabel(ctx context.Context, secret *v12.Secret, k8sClient client.Client) { - log := GetLogger(ctx) +func AddSecretHaveWatch(ctx context.Context, secret *v12.Secret, k8sClient client.Client, instanceName string) error { if secret != nil { - if secret.Labels == nil { - secret.Labels = make(map[string]string) + if secret.Annotations == nil { + secret.Annotations = make(map[string]string) } - if _, exists := secret.Labels[common.WatchSecretLabel]; exists { - counter, err := strconv.Atoi(secret.Labels[common.WatchSecretLabel]) - if err != nil { - log.Error(err, "failed to convert label value to integer") - return + if _, exists := secret.Annotations[common.WatchSecretLabel+instanceName]; !exists { + secret.Annotations[common.WatchSecretLabel+instanceName] = "true" + if err := k8sClient.Update(ctx, secret); err != nil { + return err } - secret.Labels[common.WatchSecretLabel] = strconv.Itoa(counter + 1) - } else { - secret.Labels[common.WatchSecretLabel] = "1" - } - if err := k8sClient.Update(ctx, secret); err != nil { - log.Error(err, "failed to update secret with watch label") } } + return nil } -func DecreaseSecretWatchLabel(ctx context.Context, k8sClient client.Client, namespace string, name string) error { - log := GetLogger(ctx) +func RemoveSecretWatch(ctx context.Context, k8sClient client.Client, namespace string, name string, instanceName string) error { secret := &v12.Secret{} err := k8sClient.Get(ctx, apimachinerytypes.NamespacedName{Name: name, Namespace: namespace}, secret) if err != nil { return err } - if secret.Labels == nil { + if secret.Annotations == nil { return nil } - if _, exists := secret.Labels[common.WatchSecretLabel]; exists { - counter, err := strconv.Atoi(secret.Labels[common.WatchSecretLabel]) - if err != nil { - return err - } - if counter == 1 { - log.Info(fmt.Sprintf("deleting watch label from secret %s", secret.UID)) - delete(secret.Labels, common.WatchSecretLabel) - } else { - secret.Labels[common.WatchSecretLabel] = strconv.Itoa(counter - 1) - } - if err = k8sClient.Update(ctx, secret); err != nil { + if _, exists := secret.Annotations[common.WatchSecretLabel+instanceName]; exists { + delete(secret.Annotations, common.WatchSecretLabel+instanceName) + if err := k8sClient.Update(ctx, secret); err != nil { return err } } + return nil } + +func IsSecretWatched(secret client.Object) bool { + for key := range secret.GetAnnotations() { + if strings.HasPrefix(key, common.WatchSecretLabel) { + return true + } + } + return false +} diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index 212e81bd..ec578be5 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -202,7 +202,7 @@ var _ = Describe("Controller Util", func() { }) }) - Context("IncreaseSecretHaveWatchLabel", func() { + Context("AddSecretHaveWatch", func() { It("should add the watch label to the secret if it is missing", func() { // Create a fake client @@ -215,40 +215,53 @@ var _ = Describe("Controller Util", func() { } err := k8sClient.Create(ctx, secret) Expect(err).ToNot(HaveOccurred()) + err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, secret) + Expect(err).ToNot(HaveOccurred()) + Expect(IsSecretWatched(secret)).To(BeFalse()) + // Call the function - IncreaseSecretHaveWatchLabel(ctx, secret, k8sClient) + name := "instancedName" + err = AddSecretHaveWatch(ctx, secret, k8sClient, name) + Expect(err).ToNot(HaveOccurred()) // Get the updated secret updatedSecret := &corev1.Secret{} err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - // Verify the label was added - Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("1")) + Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) + // Verify the annotation was added + Expect(updatedSecret.Annotations[common.WatchSecretLabel+name]).To(Equal("true")) - IncreaseSecretHaveWatchLabel(ctx, secret, k8sClient) + err = AddSecretHaveWatch(ctx, secret, k8sClient, "new-name") + Expect(err).ToNot(HaveOccurred()) err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - // Verify the label was added - Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("2")) + Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) + // Verify the annotation was added + Expect(updatedSecret.Annotations[common.WatchSecretLabel+"new-name"]).To(Equal("true")) - DecreaseSecretWatchLabel(ctx, k8sClient, secret.Namespace, secret.Name) + err = RemoveSecretWatch(ctx, k8sClient, secret.Namespace, secret.Name, name) + Expect(err).ToNot(HaveOccurred()) err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - // Verify the label was added - Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("1")) + Expect(updatedSecret.Annotations[common.WatchSecretLabel+"new-name"]).To(Equal("true")) + _, exist := updatedSecret.Annotations[common.WatchSecretLabel+name] + Expect(exist).To(BeFalse()) + + Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) - DecreaseSecretWatchLabel(ctx, k8sClient, secret.Namespace, secret.Name) + err = RemoveSecretWatch(ctx, k8sClient, secret.Namespace, secret.Name, "new-name") + Expect(err).ToNot(HaveOccurred()) err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - // Verify the label was added - Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("")) + Expect(IsSecretWatched(updatedSecret)).To(BeFalse()) }) }) }) From 27b271bb36eb83b4d681f6746415ea7c185bcc11 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 28 Nov 2024 11:56:46 +0200 Subject: [PATCH 19/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 94d913ab..afbe7ae2 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -638,17 +638,22 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } else { if serviceInstance.Labels != nil { // remove all secret labels + keysToDelete := []string{} for key := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretLabel) { shouldUpdate = true + keysToDelete = append(keysToDelete, key) err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) if err != nil { log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) return nil, err } - delete(serviceInstance.Labels, key) } } + // Perform deletions after the iteration + for _, key := range keysToDelete { + delete(serviceInstance.Labels, key) + } } } if shouldUpdate { From d977fecca150db48b0ccf66ff860f688e5809161 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 28 Nov 2024 14:48:36 +0200 Subject: [PATCH 20/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/common/consts.go | 1 + controllers/serviceinstance_controller.go | 12 ++++++------ internal/utils/controller_util.go | 8 ++++---- internal/utils/controller_util_test.go | 8 ++++---- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/api/common/consts.go b/api/common/consts.go index f0380960..736644a0 100644 --- a/api/common/consts.go +++ b/api/common/consts.go @@ -5,6 +5,7 @@ const ( ClusterSecretLabel = "services.cloud.sap.com/cluster-secret" InstanceSecretLabel = "services.cloud.sap.com/secretRef" WatchSecretLabel = "services.cloud.sap.com/watchSecret" + Separator = "_" NamespaceLabel = "_namespace" K8sNameLabel = "_k8sname" diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index afbe7ae2..b4c62016 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -208,7 +208,7 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient if err != nil { // if parameters are invalid there is nothing we can do, the user should fix it according to the error message in the condition log.Error(err, "failed to parse instance parameters") - return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceInstance) + return utils.MarkAsTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceInstance) } provision, provisionErr := smClient.Provision(&smClientTypes.ServiceInstance{ @@ -608,14 +608,14 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } } for key := range newSecretsMap { - if _, ok := existingSecrets[common.InstanceSecretLabel+"-"+key]; ok { + if _, ok := existingSecrets[common.InstanceSecretLabel+common.Separator+key]; ok { // this secret was already on the instance and should stay - existingSecrets[common.InstanceSecretLabel+"-"+key] = "true" + existingSecrets[common.InstanceSecretLabel+common.Separator+key] = "true" } else { // this is a new secret on the instance shouldUpdate = true secret := newSecretsMap[key] - serviceInstance.Labels[common.InstanceSecretLabel+"-"+key] = secret.Name + serviceInstance.Labels[common.InstanceSecretLabel+common.Separator+key] = secret.Name err = utils.AddSecretHaveWatch(ctx, secret, r.Client, serviceInstance.Name) if err != nil { log.Error(err, fmt.Sprintf("failed to increase secret watch label with key %s", key)) @@ -638,7 +638,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } else { if serviceInstance.Labels != nil { // remove all secret labels - keysToDelete := []string{} + var keysToDelete []string for key := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretLabel) { shouldUpdate = true @@ -855,7 +855,7 @@ type SecretPredicate struct { func (r *ServiceInstanceReconciler) findRequestsForSecret(ctx context.Context, secret client.Object) []reconcile.Request { instancesToUpdate := make([]reconcile.Request, 0) var instances v1.ServiceInstanceList - labelSelector := client.MatchingLabels{common.InstanceSecretLabel + "-" + string(secret.GetUID()): "true"} + labelSelector := client.MatchingLabels{common.InstanceSecretLabel + common.Separator + string(secret.GetUID()): "true"} if err := r.Client.List(ctx, &instances, labelSelector); err != nil { r.Log.Error(err, "failed to list service instances") return nil diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 3c9d196e..c952c660 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -252,8 +252,8 @@ func AddSecretHaveWatch(ctx context.Context, secret *v12.Secret, k8sClient clien if secret.Annotations == nil { secret.Annotations = make(map[string]string) } - if _, exists := secret.Annotations[common.WatchSecretLabel+instanceName]; !exists { - secret.Annotations[common.WatchSecretLabel+instanceName] = "true" + if _, exists := secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName]; !exists { + secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName] = "true" if err := k8sClient.Update(ctx, secret); err != nil { return err } @@ -271,8 +271,8 @@ func RemoveSecretWatch(ctx context.Context, k8sClient client.Client, namespace s if secret.Annotations == nil { return nil } - if _, exists := secret.Annotations[common.WatchSecretLabel+instanceName]; exists { - delete(secret.Annotations, common.WatchSecretLabel+instanceName) + if _, exists := secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName]; exists { + delete(secret.Annotations, common.WatchSecretLabel+common.Separator+instanceName) if err := k8sClient.Update(ctx, secret); err != nil { return err } diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index ec578be5..d0b5d47e 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -231,7 +231,7 @@ var _ = Describe("Controller Util", func() { Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) // Verify the annotation was added - Expect(updatedSecret.Annotations[common.WatchSecretLabel+name]).To(Equal("true")) + Expect(updatedSecret.Annotations[common.WatchSecretLabel+common.Separator+name]).To(Equal("true")) err = AddSecretHaveWatch(ctx, secret, k8sClient, "new-name") Expect(err).ToNot(HaveOccurred()) @@ -241,7 +241,7 @@ var _ = Describe("Controller Util", func() { Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) // Verify the annotation was added - Expect(updatedSecret.Annotations[common.WatchSecretLabel+"new-name"]).To(Equal("true")) + Expect(updatedSecret.Annotations[common.WatchSecretLabel+common.Separator+"new-name"]).To(Equal("true")) err = RemoveSecretWatch(ctx, k8sClient, secret.Namespace, secret.Name, name) Expect(err).ToNot(HaveOccurred()) @@ -249,8 +249,8 @@ var _ = Describe("Controller Util", func() { err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - Expect(updatedSecret.Annotations[common.WatchSecretLabel+"new-name"]).To(Equal("true")) - _, exist := updatedSecret.Annotations[common.WatchSecretLabel+name] + Expect(updatedSecret.Annotations[common.WatchSecretLabel+common.Separator+"new-name"]).To(Equal("true")) + _, exist := updatedSecret.Annotations[common.WatchSecretLabel+common.Separator+name] Expect(exist).To(BeFalse()) Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) From 3aeb027a16fbc27a28635039961666dcbb26a468 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 28 Nov 2024 14:53:52 +0200 Subject: [PATCH 21/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- ...ervices.cloud.sap.com_servicebindings.yaml | 40 +++---------------- ...rvices.cloud.sap.com_serviceinstances.yaml | 40 +++---------------- 2 files changed, 12 insertions(+), 68 deletions(-) diff --git a/config/crd/bases/services.cloud.sap.com_servicebindings.yaml b/config/crd/bases/services.cloud.sap.com_servicebindings.yaml index 97aba465..fc82a3d6 100644 --- a/config/crd/bases/services.cloud.sap.com_servicebindings.yaml +++ b/config/crd/bases/services.cloud.sap.com_servicebindings.yaml @@ -82,7 +82,6 @@ spec: description: |- Parameters for the binding. - The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -199,16 +198,8 @@ spec: conditions: description: Service binding conditions items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -249,12 +240,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -366,7 +352,6 @@ spec: description: |- Parameters for the binding. - The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -468,16 +453,8 @@ spec: conditions: description: Service binding conditions items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -518,12 +495,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index c1c56cc4..5d386c85 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -89,7 +89,6 @@ spec: description: |- Provisioning parameters for the instance. - The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -185,16 +184,8 @@ spec: conditions: description: Service instance conditions items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -235,12 +226,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -353,7 +339,6 @@ spec: description: |- Provisioning parameters for the instance. - The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -445,16 +430,8 @@ spec: conditions: description: Service instance conditions items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" + description: Condition contains details for one aspect of the current + state of this API Resource. properties: lastTransitionTime: description: |- @@ -495,12 +472,7 @@ spec: - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + description: type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string From 24bc634e5ca2b994319f194b2971d4be8893880e Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 2 Dec 2024 13:06:09 +0200 Subject: [PATCH 22/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/v1/serviceinstance_types.go | 3 + ...rvices.cloud.sap.com_serviceinstances.yaml | 3 + config/rbac/role.yaml | 8 ++ controllers/secret_controller.go | 105 ++++++++++++++++++ controllers/serviceinstance_controller.go | 73 +----------- internal/utils/controller_util.go | 7 ++ main.go | 8 ++ 7 files changed, 139 insertions(+), 68 deletions(-) create mode 100644 controllers/secret_controller.go diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 018fd1fb..7d202b9e 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -122,6 +122,9 @@ type ServiceInstanceStatus struct { // The subaccount id of the service instance SubaccountID string `json:"subaccountID,omitempty"` + + // if true need to update instance + SecretChange bool `json:"secretChange,omitempty"` } // +kubebuilder:object:root=true diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index 5d386c85..a2266eb8 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -259,6 +259,9 @@ spec: ready: description: Indicates whether instance is ready for usage type: string + secretChange: + description: if true need to update instance + type: boolean subaccountID: description: The subaccount id of the service instance type: string diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 74f1babd..ee85b1e0 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -26,6 +26,14 @@ rules: - patch - update - watch +- apiGroups: + - services.cloud.sap.com + resources: + - secret + verbs: + - delete + - patch + - update - apiGroups: - services.cloud.sap.com resources: diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go new file mode 100644 index 00000000..806a8fc3 --- /dev/null +++ b/controllers/secret_controller.go @@ -0,0 +1,105 @@ +package controllers + +import ( + "context" + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + "github.com/SAP/sap-btp-service-operator/api/common" + v1 "github.com/SAP/sap-btp-service-operator/api/v1" + "github.com/SAP/sap-btp-service-operator/internal/utils" + "github.com/go-logr/logr" + "github.com/google/uuid" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type SecretReconciler struct { + client.Client + Scheme *runtime.Scheme + Log logr.Logger +} + +// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=secret,verbs=update;patch;delete +// +kubebuilder:rbac:groups=core,resources=events,verbs=update;patch;delete +// +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=update + +func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + log := r.Log.WithValues("secret", req.NamespacedName).WithValues("correlation_id", uuid.New().String()) + ctx = context.WithValue(ctx, utils.LogKey{}, log) + log.Info(fmt.Sprintf("reconciling secret %s", req.NamespacedName)) + // Fetch the Secret + var secret corev1.Secret + if err := r.Get(ctx, req.NamespacedName, &secret); err != nil { + if !apierrors.IsNotFound(err) { + log.Error(err, "unable to fetch ServiceInstance") + } + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + var instances v1.ServiceInstanceList + labelSelector := client.MatchingLabels{common.InstanceSecretLabel + common.Separator + string(secret.GetUID()): secret.Name} + if err := r.Client.List(ctx, &instances, labelSelector); err != nil { + log.Error(err, "failed to list service instances") + return ctrl.Result{}, err + } + for _, instance := range instances.Items { + log.Info(fmt.Sprintf("waking up instance %s", instance.Name)) + instance.Status.SecretChange = true + err := utils.UpdateStatus(ctx, r.Client, &instance) + if err != nil { + return reconcile.Result{}, err + } + } + return reconcile.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *SecretReconciler) SetupWithManager(mgr ctrl.Manager) error { + labelSelector := labels.SelectorFromSet(map[string]string{common.WatchSecretLabel: "true"}) + labelPredicate := predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + return labelSelector.Matches(labels.Set(e.ObjectNew.GetLabels())) && isSecretDataChanged(e) + }, + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return labelSelector.Matches(labels.Set(e.Object.GetLabels())) + }, + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + } + + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Secret{}). + WithEventFilter(labelPredicate). + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). + Complete(r) +} + +func isSecretDataChanged(e event.UpdateEvent) bool { + // Type assert to *v1.Secret + oldSecret, okOld := e.ObjectOld.(*corev1.Secret) + newSecret, okNew := e.ObjectNew.(*corev1.Secret) + if !okOld || !okNew { + // If the objects are not Secrets, skip the event + return false + } + + // Compare the Data field (byte slices) + return !reflect.DeepEqual(oldSecret.Data, newSecret.Data) +} diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index b4c62016..1a3ff776 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -23,19 +23,10 @@ import ( "encoding/json" "fmt" "net/http" - "reflect" "strings" - "k8s.io/apimachinery/pkg/types" - - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/predicate" "github.com/SAP/sap-btp-service-operator/api/common" "github.com/SAP/sap-btp-service-operator/internal/config" @@ -173,31 +164,9 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } func (r *ServiceInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { - secretPredicate := SecretPredicate{ - Funcs: predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { - return false - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return utils.IsSecretWatched(e.ObjectNew) && isSecretDataChanged(e) - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return false - }, - GenericFunc: func(e event.GenericEvent) bool { - return false - }, - }, - } - return ctrl.NewControllerManagedBy(mgr). For(&v1.ServiceInstance{}). - WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}). - Watches( - &corev1.Secret{}, - handler.EnqueueRequestsFromMapFunc(r.findRequestsForSecret), - builder.WithPredicates(secretPredicate), - ).Complete(r) + WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).Complete(r) } func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) { @@ -283,7 +252,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient serviceInstance.Status.OperationURL = operationURL serviceInstance.Status.OperationType = smClientTypes.UPDATE utils.SetInProgressConditions(ctx, smClientTypes.UPDATE, "", serviceInstance) - + serviceInstance.Status.SecretChange = false if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil { return ctrl.Result{}, err } @@ -292,7 +261,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient } log.Info("Instance updated successfully") utils.SetSuccessConditions(smClientTypes.UPDATE, serviceInstance) - + serviceInstance.Status.SecretChange = false return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } @@ -694,7 +663,7 @@ func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool } return false } - if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges { + if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges && serviceInstance.Status.SecretChange { log.Info("instance is not in final state, SubscribeToSecretChanges is true") return false } @@ -851,35 +820,3 @@ func getErrorMsgFromLastOperation(status *smClientTypes.Operation) string { type SecretPredicate struct { predicate.Funcs } - -func (r *ServiceInstanceReconciler) findRequestsForSecret(ctx context.Context, secret client.Object) []reconcile.Request { - instancesToUpdate := make([]reconcile.Request, 0) - var instances v1.ServiceInstanceList - labelSelector := client.MatchingLabels{common.InstanceSecretLabel + common.Separator + string(secret.GetUID()): "true"} - if err := r.Client.List(ctx, &instances, labelSelector); err != nil { - r.Log.Error(err, "failed to list service instances") - return nil - } - for _, instance := range instances.Items { - instancesToUpdate = append(instancesToUpdate, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: instance.Name, - Namespace: instance.Namespace, - }, - }) - } - return instancesToUpdate -} - -func isSecretDataChanged(e event.UpdateEvent) bool { - // Type assert to *v1.Secret - oldSecret, okOld := e.ObjectOld.(*corev1.Secret) - newSecret, okNew := e.ObjectNew.(*corev1.Secret) - if !okOld || !okNew { - // If the objects are not Secrets, skip the event - return false - } - - // Compare the Data field (byte slices) - return !reflect.DeepEqual(oldSecret.Data, newSecret.Data) -} diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index c952c660..a2e2dd4f 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -252,6 +252,10 @@ func AddSecretHaveWatch(ctx context.Context, secret *v12.Secret, k8sClient clien if secret.Annotations == nil { secret.Annotations = make(map[string]string) } + if secret.Labels == nil { + secret.Labels = make(map[string]string) + } + secret.Labels[common.WatchSecretLabel] = "true" if _, exists := secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName]; !exists { secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName] = "true" if err := k8sClient.Update(ctx, secret); err != nil { @@ -273,6 +277,9 @@ func RemoveSecretWatch(ctx context.Context, k8sClient client.Client, namespace s } if _, exists := secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName]; exists { delete(secret.Annotations, common.WatchSecretLabel+common.Separator+instanceName) + if len(secret.Annotations) == 0 { + delete(secret.Labels, common.WatchSecretLabel) + } if err := k8sClient.Update(ctx, secret); err != nil { return err } diff --git a/main.go b/main.go index 50bd0c47..c7eff406 100644 --- a/main.go +++ b/main.go @@ -174,6 +174,14 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "ServiceBinding") os.Exit(1) } + if err = (&controllers.SecretReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Secret"), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Secret") + os.Exit(1) + } if os.Getenv("ENABLE_WEBHOOKS") != "false" { mgr.GetWebhookServer().Register("/mutate-services-cloud-sap-com-v1-serviceinstance", &webhook.Admission{Handler: &webhooks.ServiceInstanceDefaulter{Decoder: admission.NewDecoder(mgr.GetScheme())}}) mgr.GetWebhookServer().Register("/mutate-services-cloud-sap-com-v1-servicebinding", &webhook.Admission{Handler: &webhooks.ServiceBindingDefaulter{Decoder: admission.NewDecoder(mgr.GetScheme())}}) From 27e5f8713d21df029c88352aa248a39e792006a6 Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 4 Dec 2024 11:41:58 +0200 Subject: [PATCH 23/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/v1/serviceinstance_types.go | 2 +- ...rvices.cloud.sap.com_serviceinstances.yaml | 6 ++-- controllers/secret_controller.go | 11 +++--- controllers/serviceinstance_controller.go | 9 +++-- .../serviceinstance_controller_test.go | 35 +++++++++++++++++++ controllers/suite_test.go | 7 ++++ 6 files changed, 56 insertions(+), 14 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 7d202b9e..8e79c8f8 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -124,7 +124,7 @@ type ServiceInstanceStatus struct { SubaccountID string `json:"subaccountID,omitempty"` // if true need to update instance - SecretChange bool `json:"secretChange,omitempty"` + ForceReconcile bool `json:"forceReconcile,omitempty"` } // +kubebuilder:object:root=true diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index a2266eb8..24c671b0 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -238,6 +238,9 @@ spec: - type type: object type: array + forceReconcile: + description: if true need to update instance + type: boolean hashedSpec: description: HashedSpec is the hashed spec without the shared property type: string @@ -259,9 +262,6 @@ spec: ready: description: Indicates whether instance is ready for usage type: string - secretChange: - description: if true need to update instance - type: boolean subaccountID: description: The subaccount id of the service instance type: string diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index 806a8fc3..b736cd40 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -29,9 +29,11 @@ type SecretReconciler struct { Log logr.Logger } -// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=secret,verbs=update;patch;delete -// +kubebuilder:rbac:groups=core,resources=events,verbs=update;patch;delete -// +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=update +// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=servicebindings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=servicebindings/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;create;update func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { log := r.Log.WithValues("secret", req.NamespacedName).WithValues("correlation_id", uuid.New().String()) @@ -48,7 +50,6 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) // on deleted requests. return ctrl.Result{}, client.IgnoreNotFound(err) } - var instances v1.ServiceInstanceList labelSelector := client.MatchingLabels{common.InstanceSecretLabel + common.Separator + string(secret.GetUID()): secret.Name} if err := r.Client.List(ctx, &instances, labelSelector); err != nil { @@ -57,7 +58,7 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) } for _, instance := range instances.Items { log.Info(fmt.Sprintf("waking up instance %s", instance.Name)) - instance.Status.SecretChange = true + instance.Status.ForceReconcile = true err := utils.UpdateStatus(ctx, r.Client, &instance) if err != nil { return reconcile.Result{}, err diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 1a3ff776..049f34f1 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -220,7 +220,6 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient log.Info(fmt.Sprintf("Instance provisioned successfully, instanceID: %s, subaccountID: %s", serviceInstance.Status.InstanceID, serviceInstance.Status.SubaccountID)) utils.SetSuccessConditions(smClientTypes.CREATE, serviceInstance) - return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } @@ -252,7 +251,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient serviceInstance.Status.OperationURL = operationURL serviceInstance.Status.OperationType = smClientTypes.UPDATE utils.SetInProgressConditions(ctx, smClientTypes.UPDATE, "", serviceInstance) - serviceInstance.Status.SecretChange = false + serviceInstance.Status.ForceReconcile = false if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil { return ctrl.Result{}, err } @@ -261,7 +260,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient } log.Info("Instance updated successfully") utils.SetSuccessConditions(smClientTypes.UPDATE, serviceInstance) - serviceInstance.Status.SecretChange = false + serviceInstance.Status.ForceReconcile = false return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } @@ -663,7 +662,7 @@ func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool } return false } - if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges && serviceInstance.Status.SecretChange { + if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges && serviceInstance.Status.ForceReconcile { log.Info("instance is not in final state, SubscribeToSecretChanges is true") return false } @@ -681,7 +680,7 @@ func updateRequired(serviceInstance *v1.ServiceInstance) bool { if cond != nil && cond.Reason == common.UpdateInProgress { //in case of transient error occurred return true } - if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges { + if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges && serviceInstance.Status.ForceReconcile { return true } diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 01fb7399..82e1a82d 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -226,9 +226,44 @@ var _ = Describe("ServiceInstance controller", func() { params := smInstance.Parameters Expect(params).To(ContainSubstring("\"key\":\"value\"")) Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) + + secret := &corev1.Secret{} + err := k8sClient.Get(ctx, types.NamespacedName{Namespace: testNamespace, Name: "param-secret"}, secret) + Expect(err).ToNot(HaveOccurred()) + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") + secret.Data = credentialsMap + Expect(k8sClient.Update(ctx, secret)).To(Succeed()) + + Expect(fakeClient.ProvisionCallCount()).To(Equal(1)) }) }) + When("provision request to SM succeeds", func() { + FIt("should provision instance of the provided offering and plan name successfully", func() { + instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) + serviceInstance = createInstance(ctx, instanceSpec, nil, true) + smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) + params := smInstance.Parameters + Expect(params).To(ContainSubstring("\"key\":\"value\"")) + Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) + + secret := &corev1.Secret{} + err := k8sClient.Get(ctx, types.NamespacedName{Namespace: testNamespace, Name: "param-secret"}, secret) + Expect(err).ToNot(HaveOccurred()) + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") + secret.Data = credentialsMap + Expect(k8sClient.Update(ctx, secret)).To(Succeed()) + Eventually(func() bool { + return fakeClient.UpdateInstanceCallCount() == 1 + }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(1) + params = smInstance.Parameters + Expect(params).To(ContainSubstring("\"key\":\"value\"")) + Expect(params).To(ContainSubstring("\"secret-key\":\"new-secret-value\"")) + }) + }) When("provision request to SM fails", func() { errMessage := "failed to provision instance" diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 211eaaad..56b1a2fe 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -178,6 +178,13 @@ var _ = BeforeSuite(func(done Done) { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) + err = (&SecretReconciler{ + Client: k8sManager.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("Secret"), + Scheme: k8sManager.GetScheme(), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + // +kubebuilder:scaffold:webhook ctx, cancel = context.WithCancel(context.TODO()) From b43ad4a82ef66ce455d0c51a156645270ff824bc Mon Sep 17 00:00:00 2001 From: I501080 Date: Wed, 4 Dec 2024 14:30:36 +0200 Subject: [PATCH 24/74] observed generation refactor --- api/common/common.go | 12 ++- api/v1/servicebinding_types.go | 11 --- api/v1/serviceinstance_types.go | 11 --- .../samples/services_v1_serviceinstance.yaml | 11 +++ controllers/servicebinding_controller.go | 40 +++++----- controllers/servicebinding_controller_test.go | 75 +++++++------------ controllers/serviceinstance_controller.go | 46 +++++------- .../serviceinstance_controller_test.go | 58 ++++++-------- controllers/suite_test.go | 50 +++++-------- internal/utils/condition_utils.go | 52 +++++++++---- internal/utils/condition_utils_test.go | 6 +- 11 files changed, 163 insertions(+), 209 deletions(-) diff --git a/api/common/common.go b/api/common/common.go index cec801e3..9ec4941c 100644 --- a/api/common/common.go +++ b/api/common/common.go @@ -2,6 +2,7 @@ package common import ( "fmt" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -74,11 +75,18 @@ type SAPBTPResource interface { GetParameters() *runtime.RawExtension GetStatus() interface{} SetStatus(status interface{}) - GetObservedGeneration() int64 - SetObservedGeneration(int64) DeepClone() SAPBTPResource SetReady(metav1.ConditionStatus) GetReady() metav1.ConditionStatus GetAnnotations() map[string]string SetAnnotations(map[string]string) } + +func GetObservedGeneration(obj SAPBTPResource) int64 { + cond := meta.FindStatusCondition(obj.GetConditions(), ConditionSucceeded) + observedGen := int64(0) + if cond != nil { + observedGen = cond.ObservedGeneration + } + return observedGen +} diff --git a/api/v1/servicebinding_types.go b/api/v1/servicebinding_types.go index db432556..01296125 100644 --- a/api/v1/servicebinding_types.go +++ b/api/v1/servicebinding_types.go @@ -123,9 +123,6 @@ type ServiceBindingStatus struct { // Service binding conditions Conditions []metav1.Condition `json:"conditions"` - // Last generation that was acted on - ObservedGeneration int64 `json:"observedGeneration,omitempty"` - // Indicates whether binding is ready for usage Ready metav1.ConditionStatus `json:"ready,omitempty"` @@ -179,14 +176,6 @@ func (sb *ServiceBinding) SetStatus(status interface{}) { sb.Status = status.(ServiceBindingStatus) } -func (sb *ServiceBinding) GetObservedGeneration() int64 { - return sb.Status.ObservedGeneration -} - -func (sb *ServiceBinding) SetObservedGeneration(newObserved int64) { - sb.Status.ObservedGeneration = newObserved -} - func (sb *ServiceBinding) DeepClone() common.SAPBTPResource { return sb.DeepCopy() } diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 8e79c8f8..4a0154ab 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -111,9 +111,6 @@ type ServiceInstanceStatus struct { // Service instance conditions Conditions []metav1.Condition `json:"conditions"` - // Last generation that was acted on - ObservedGeneration int64 `json:"observedGeneration,omitempty"` - // Indicates whether instance is ready for usage Ready metav1.ConditionStatus `json:"ready,omitempty"` @@ -172,14 +169,6 @@ func (si *ServiceInstance) SetStatus(status interface{}) { si.Status = status.(ServiceInstanceStatus) } -func (si *ServiceInstance) GetObservedGeneration() int64 { - return si.Status.ObservedGeneration -} - -func (si *ServiceInstance) SetObservedGeneration(newObserved int64) { - si.Status.ObservedGeneration = newObserved -} - func (si *ServiceInstance) DeepClone() common.SAPBTPResource { return si.DeepCopy() } diff --git a/config/samples/services_v1_serviceinstance.yaml b/config/samples/services_v1_serviceinstance.yaml index bd19f598..6ec8e64b 100644 --- a/config/samples/services_v1_serviceinstance.yaml +++ b/config/samples/services_v1_serviceinstance.yaml @@ -2,6 +2,17 @@ apiVersion: services.cloud.sap.com/v1 kind: ServiceInstance metadata: name: sample-instance-1 + labels: + "services.cloud.sap.com/secretKeyRef-my-secret": "true" + "services.cloud.sap.com/secretKeyRef-my-secret1": "true" spec: serviceOfferingName: service-manager servicePlanName: subaccount-audit + parametersFrom: + - secretKeyRef: + name: my-secret + key: secret-paramete + - secretKeyRef: + name: my-secret1 + key: secret-paramete + diff --git a/controllers/servicebinding_controller.go b/controllers/servicebinding_controller.go index 2efe4770..10829684 100644 --- a/controllers/servicebinding_controller.go +++ b/controllers/servicebinding_controller.go @@ -91,8 +91,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque return ctrl.Result{}, client.IgnoreNotFound(err) } serviceBinding = serviceBinding.DeepCopy() - log.Info(fmt.Sprintf("Current generation is %v and observed is %v", serviceBinding.Generation, serviceBinding.GetObservedGeneration())) - serviceBinding.SetObservedGeneration(serviceBinding.Generation) + log.Info(fmt.Sprintf("Current generation is %v and observed is %v", serviceBinding.Generation, common.GetObservedGeneration(serviceBinding))) if len(serviceBinding.GetConditions()) == 0 { if err := utils.InitConditions(ctx, r.Client, serviceBinding); err != nil { @@ -182,7 +181,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque if utils.IsInProgress(serviceInstance) { log.Info(fmt.Sprintf("Service instance with k8s name %s is not ready for binding yet", serviceInstance.Name)) - utils.SetInProgressConditions(ctx, smClientTypes.CREATE, fmt.Sprintf("creation in progress, waiting for service instance '%s' to be ready", serviceBinding.Spec.ServiceInstanceName), serviceBinding) + utils.SetInProgressConditions(ctx, smClientTypes.CREATE, fmt.Sprintf("creation in progress, waiting for service instance '%s' to be ready", serviceBinding.Spec.ServiceInstanceName), serviceBinding, false) return ctrl.Result{Requeue: true, RequeueAfter: r.Config.PollInterval}, utils.UpdateStatus(ctx, r.Client, serviceBinding) } @@ -242,7 +241,7 @@ func (r *ServiceBindingReconciler) updateSecret(ctx context.Context, serviceBind return err } log.Info("Updating binding", "bindingID", smBinding.ID) - utils.SetSuccessConditions(smClientTypes.UPDATE, serviceBinding) + utils.SetSuccessConditions(smClientTypes.UPDATE, serviceBinding, false) } } return nil @@ -291,7 +290,7 @@ func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient s log.Info("Create smBinding request is async") serviceBinding.Status.OperationURL = operationURL serviceBinding.Status.OperationType = smClientTypes.CREATE - utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceBinding) + utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceBinding, false) if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil { log.Error(err, "unable to update ServiceBinding status") return ctrl.Result{}, err @@ -313,7 +312,7 @@ func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient s serviceBinding.Status.BindingID = smBinding.ID serviceBinding.Status.SubaccountID = subaccountID serviceBinding.Status.Ready = metav1.ConditionTrue - utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding) + utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false) log.Info("Updating binding", "bindingID", smBinding.ID) return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding) @@ -336,7 +335,7 @@ func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v if smBinding != nil { log.Info("binding exists in SM continue with deletion") serviceBinding.Status.BindingID = smBinding.ID - utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceBinding) + utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceBinding, false) return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding) } @@ -367,7 +366,7 @@ func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v log.Info("Deleting binding async") serviceBinding.Status.OperationURL = operationURL serviceBinding.Status.OperationType = smClientTypes.DELETE - utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceBinding) + utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceBinding, false) if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil { return ctrl.Result{}, err } @@ -392,7 +391,7 @@ func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1. status, statusErr := smClient.Status(serviceBinding.Status.OperationURL, nil) if statusErr != nil { log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceBinding.Status.OperationURL) - utils.SetInProgressConditions(ctx, serviceBinding.Status.OperationType, string(smClientTypes.INPROGRESS), serviceBinding) + utils.SetInProgressConditions(ctx, serviceBinding.Status.OperationType, string(smClientTypes.INPROGRESS), serviceBinding, false) freshStatus := v1.ServiceBindingStatus{ Conditions: serviceBinding.GetConditions(), } @@ -414,7 +413,7 @@ func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1. fallthrough case smClientTypes.PENDING: if len(status.Description) != 0 { - utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceBinding) + utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceBinding, true) if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil { log.Error(err, "unable to update ServiceBinding polling description") return ctrl.Result{}, err @@ -423,7 +422,7 @@ func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1. return ctrl.Result{Requeue: true, RequeueAfter: r.Config.PollInterval}, nil case smClientTypes.FAILED: // non transient error - should not retry - utils.SetFailureConditions(status.Type, status.Description, serviceBinding) + utils.SetFailureConditions(status.Type, status.Description, serviceBinding, true) if serviceBinding.Status.OperationType == smClientTypes.DELETE { serviceBinding.Status.OperationURL = "" serviceBinding.Status.OperationType = "" @@ -438,7 +437,7 @@ func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1. return ctrl.Result{}, fmt.Errorf(errMsg) } case smClientTypes.SUCCEEDED: - utils.SetSuccessConditions(status.Type, serviceBinding) + utils.SetSuccessConditions(status.Type, serviceBinding, true) switch serviceBinding.Status.OperationType { case smClientTypes.CREATE: smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil) @@ -453,7 +452,7 @@ func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1. if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil { return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding) } - utils.SetSuccessConditions(status.Type, serviceBinding) + utils.SetSuccessConditions(status.Type, serviceBinding, false) case smClientTypes.DELETE: return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding) } @@ -495,10 +494,6 @@ func (r *ServiceBindingReconciler) getBindingForRecovery(ctx context.Context, sm func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.ServiceBinding) (ctrl.Result, error) { log := utils.GetLogger(ctx) shouldUpdateStatus := false - if binding.Generation != binding.Status.ObservedGeneration { - binding.SetObservedGeneration(binding.Generation) - shouldUpdateStatus = true - } if !utils.IsFailed(binding) { secret, err := r.getSecret(ctx, binding.Namespace, binding.Spec.SecretName) if err != nil { @@ -507,7 +502,7 @@ func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.Ser log.Info(fmt.Sprintf("secret not found recovering binding %s", binding.Name)) binding.Status.BindingID = "" binding.Status.Ready = metav1.ConditionFalse - utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "recreating deleted secret", binding) + utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "recreating deleted secret", binding, false) shouldUpdateStatus = true r.Recorder.Event(binding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted") } else { @@ -565,7 +560,6 @@ func (r *ServiceBindingReconciler) setOwner(ctx context.Context, serviceInstance } func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) { - k8sBinding.Status.ObservedGeneration = k8sBinding.Generation k8sBinding.Status.BindingID = smBinding.ID k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID k8sBinding.Status.OperationURL = "" @@ -587,11 +581,11 @@ func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sB case smClientTypes.INPROGRESS: k8sBinding.Status.OperationURL = sm.BuildOperationURL(smBinding.LastOperation.ID, smBinding.ID, smClientTypes.ServiceBindingsURL) k8sBinding.Status.OperationType = smBinding.LastOperation.Type - utils.SetInProgressConditions(ctx, smBinding.LastOperation.Type, smBinding.LastOperation.Description, k8sBinding) + utils.SetInProgressConditions(ctx, smBinding.LastOperation.Type, smBinding.LastOperation.Description, k8sBinding, false) case smClientTypes.SUCCEEDED: - utils.SetSuccessConditions(operationType, k8sBinding) + utils.SetSuccessConditions(operationType, k8sBinding, false) case smClientTypes.FAILED: - utils.SetFailureConditions(operationType, description, k8sBinding) + utils.SetFailureConditions(operationType, description, k8sBinding, false) } } @@ -988,7 +982,7 @@ func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, bindin binding.Status.BindingID = "" binding.Status.Ready = metav1.ConditionFalse - utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding) + utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false) utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding) return utils.UpdateStatus(ctx, r.Client, binding) } diff --git a/controllers/servicebinding_controller_test.go b/controllers/servicebinding_controller_test.go index 5350e59a..d9280f6b 100644 --- a/controllers/servicebinding_controller_test.go +++ b/controllers/servicebinding_controller_test.go @@ -23,7 +23,6 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -37,8 +36,6 @@ import ( var _ = Describe("ServiceBinding controller", func() { var ( - ctx context.Context - createdInstance *v1.ServiceInstance createdBinding *v1.ServiceBinding @@ -48,6 +45,7 @@ var _ = Describe("ServiceBinding controller", func() { bindingName string instanceName string instanceExternalName string + paramsSecret *corev1.Secret ) createBindingWithoutAssertionsAndWait := func(ctx context.Context, name, namespace, instanceName, instanceNamespace, externalName string, secretTemplate string, wait bool) (*v1.ServiceBinding, error) { @@ -111,7 +109,7 @@ var _ = Describe("ServiceBinding controller", func() { Expect(createdBinding.Status.InstanceID).ToNot(BeEmpty()) Expect(createdBinding.Status.BindingID).To(Equal(fakeBindingID)) Expect(createdBinding.Spec.SecretName).To(Not(BeEmpty())) - Expect(int(createdBinding.Status.ObservedGeneration)).To(Equal(1)) + Expect(common.GetObservedGeneration(createdBinding)).To(Equal(int64(1))) Expect(string(createdBinding.Spec.Parameters.Raw)).To(ContainSubstring("\"key\":\"value\"")) smBinding, _, _ := fakeClient.BindArgsForCall(0) params := smBinding.Parameters @@ -175,6 +173,9 @@ var _ = Describe("ServiceBinding controller", func() { bindingName = "test-binding-" + testUUID instanceExternalName = instanceName + "-external" + paramsSecret = createParamsSecret(ctx, "binding-params-secret", testNamespace) + Expect(len(paramsSecret.Data)).ToNot(BeZero()) + fakeClient = &smfakes.FakeClient{} fakeClient.ProvisionReturns(&sm.ProvisionResponse{InstanceID: "12345678", Tags: []byte("[\"test\"]")}, nil) fakeClient.BindReturns(&smClientTypes.ServiceBinding{ID: fakeBindingID, Credentials: json.RawMessage(`{"secret_key": "secret_value", "escaped": "{\"escaped_key\":\"escaped_val\"}"}`)}, "", nil) @@ -183,12 +184,6 @@ var _ = Describe("ServiceBinding controller", func() { fakeClient.GetInstanceByIDReturns(smInstance, nil) defaultLookupKey = types.NamespacedName{Namespace: bindingTestNamespace, Name: bindingName} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: bindingTestNamespace, Name: "param-secret"}, &corev1.Secret{}) - if apierrors.IsNotFound(err) { - createParamsSecret(bindingTestNamespace) - } else { - Expect(err).ToNot(HaveOccurred()) - } createdInstance = createInstance(ctx, instanceName, bindingTestNamespace, instanceExternalName) }) @@ -196,13 +191,15 @@ var _ = Describe("ServiceBinding controller", func() { AfterEach(func() { if createdBinding != nil { fakeClient.UnbindReturns("", nil) - deleteAndWait(ctx, types.NamespacedName{Name: createdBinding.Name, Namespace: createdBinding.Namespace}, &v1.ServiceBinding{}) + deleteAndWait(ctx, createdBinding) } - if createdInstance != nil { - deleteAndWait(ctx, types.NamespacedName{Name: instanceName, Namespace: bindingTestNamespace}, &v1.ServiceInstance{}) + fakeClient.DeprovisionReturns("", nil) + deleteAndWait(ctx, createdInstance) } + deleteAndWait(ctx, paramsSecret) + createdBinding = nil createdInstance = nil }) @@ -236,7 +233,7 @@ var _ = Describe("ServiceBinding controller", func() { }) AfterEach(func() { - deleteAndWait(ctx, types.NamespacedName{Name: secretName, Namespace: bindingTestNamespace}, &corev1.Secret{}) + deleteAndWait(ctx, secret) }) When("name is already taken", func() { @@ -395,7 +392,7 @@ var _ = Describe("ServiceBinding controller", func() { fakeClient.BindReturns(nil, "", errors.New(errorMessage)) }) - It("should fail with the error returned from SM", func() { + FIt("should fail with the error returned from SM", func() { createBindingWithError(ctx, bindingName, bindingTestNamespace, instanceName, "binding-external-name", errorMessage, "") }) }) @@ -567,10 +564,10 @@ var _ = Describe("ServiceBinding controller", func() { When("referenced service instance is failed", func() { It("should retry and succeed once the instance is ready", func() { - utils.SetFailureConditions(smClientTypes.CREATE, "Failed to create instance (test)", createdInstance) + utils.SetFailureConditions(smClientTypes.CREATE, "Failed to create instance (test)", createdInstance, false) updateInstanceStatus(ctx, createdInstance) binding := createBindingWithBlockedError(ctx, bindingName, bindingTestNamespace, instanceName, "binding-external-name", "is not usable") - utils.SetSuccessConditions(smClientTypes.CREATE, createdInstance) + utils.SetSuccessConditions(smClientTypes.CREATE, createdInstance, false) updateInstanceStatus(ctx, createdInstance) waitForResourceToBeReady(ctx, binding) }) @@ -579,7 +576,7 @@ var _ = Describe("ServiceBinding controller", func() { When("referenced service instance is not ready", func() { It("should retry and succeed once the instance is ready", func() { fakeClient.StatusReturns(&smClientTypes.Operation{ResourceID: fakeInstanceID, State: smClientTypes.INPROGRESS}, nil) - utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", createdInstance) + utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", createdInstance, false) createdInstance.Status.OperationURL = "/1234" createdInstance.Status.OperationType = smClientTypes.CREATE updateInstanceStatus(ctx, createdInstance) @@ -588,7 +585,7 @@ var _ = Describe("ServiceBinding controller", func() { Expect(err).ToNot(HaveOccurred()) Expect(utils.IsInProgress(createdBinding)).To(BeTrue()) - utils.SetSuccessConditions(smClientTypes.CREATE, createdInstance) + utils.SetSuccessConditions(smClientTypes.CREATE, createdInstance, false) createdInstance.Status.OperationType = "" createdInstance.Status.OperationURL = "" updateInstanceStatus(ctx, createdInstance) @@ -923,11 +920,6 @@ stringData: }) Context("Delete", func() { - deleteAndValidate := func(binding *v1.ServiceBinding) { - deleteAndWait(ctx, getResourceNamespacedName(createdBinding), &v1.ServiceBinding{}) - err := k8sClient.Get(ctx, types.NamespacedName{Name: binding.Spec.SecretName, Namespace: binding.Namespace}, &corev1.Secret{}) - Expect(apierrors.IsNotFound(err)).To(BeTrue()) - } BeforeEach(func() { createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") @@ -940,7 +932,7 @@ stringData: fakeClient.UnbindReturns("", nil) }) It("should delete the k8s binding and secret", func() { - deleteAndValidate(createdBinding) + deleteAndWait(ctx, createdBinding) }) }) @@ -965,7 +957,7 @@ stringData: }) It("recovers the binding and delete the k8s binding and secret", func() { - deleteAndValidate(createdBinding) + deleteAndWait(ctx, createdBinding) }) }) @@ -976,7 +968,7 @@ stringData: }) AfterEach(func() { fakeClient.UnbindReturns("", nil) - deleteAndValidate(createdBinding) + deleteAndWait(ctx, createdBinding) }) It("should not remove finalizer and keep the secret", func() { @@ -1001,7 +993,7 @@ stringData: }) It("should eventually succeed", func() { - deleteAndValidate(createdBinding) + deleteAndWait(ctx, createdBinding) }) }) @@ -1022,7 +1014,7 @@ stringData: cond := meta.FindStatusCondition(createdBinding.GetConditions(), common.ConditionSucceeded) return cond != nil && cond.Reason == common.Blocked }, timeout, interval).Should(BeTrue()) - deleteAndValidate(createdBinding) + deleteAndWait(ctx, createdBinding) }) }) }) @@ -1039,7 +1031,7 @@ stringData: It("should delete the k8s binding and secret", func() { Expect(k8sClient.Delete(ctx, createdBinding)).To(Succeed()) - deleteAndValidate(createdBinding) + deleteAndWait(ctx, createdBinding) }) }) @@ -1064,7 +1056,7 @@ stringData: return failedCond != nil && strings.Contains(failedCond.Message, errorMessage) }, timeout, interval).Should(BeTrue()) fakeClient.UnbindReturns("", nil) - deleteAndValidate(createdBinding) + deleteAndWait(ctx, createdBinding) }) }) @@ -1086,7 +1078,7 @@ stringData: return cond != nil && strings.Contains(cond.Message, string(smClientTypes.INPROGRESS)) }, timeout, interval).Should(BeTrue()) fakeClient.UnbindReturns("", nil) - deleteAndValidate(createdBinding) + deleteAndWait(ctx, createdBinding) }) }) }) @@ -1344,21 +1336,6 @@ stringData: Context("Cross Namespace", func() { var crossBinding *v1.ServiceBinding - var paramsSecret *corev1.Secret - BeforeEach(func() { - paramsSecret = &corev1.Secret{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: testNamespace, Name: "param-secret"}, paramsSecret) - if apierrors.IsNotFound(err) { - createParamsSecret(testNamespace) - } else { - Expect(err).ToNot(HaveOccurred()) - } - }) - - AfterEach(func() { - deleteAndWait(ctx, types.NamespacedName{Namespace: testNamespace, Name: "param-secret"}, &corev1.Secret{}) - }) - When("binding is created in a different namespace than the instance", func() { AfterEach(func() { if crossBinding != nil { @@ -1403,7 +1380,7 @@ stringData: }) AfterEach(func() { if crossBinding != nil { - Expect(k8sClient.Delete(ctx, crossBinding)) + deleteAndWait(ctx, crossBinding) } }) @@ -1465,7 +1442,7 @@ func generateBasicBindingTemplate(name, namespace, instanceName, instanceNamespa binding.Spec.ParametersFrom = []v1.ParametersFromSource{ { SecretKeyRef: &v1.SecretKeyReference{ - Name: "param-secret", + Name: "binding-params-secret", Key: "secret-parameter", }, }, diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 049f34f1..750e55f8 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -103,8 +103,6 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) { - // delete updates the generation - serviceInstance.SetObservedGeneration(serviceInstance.Generation) return r.deleteInstance(ctx, serviceInstance) } @@ -121,8 +119,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } } - log.Info(fmt.Sprintf("instance is not in final state, handling... (generation: %d, observedGen: %d", serviceInstance.Generation, serviceInstance.Status.ObservedGeneration)) - serviceInstance.SetObservedGeneration(serviceInstance.Generation) + log.Info(fmt.Sprintf("instance is not in final state, handling... (generation: %d, observedGen: %d", serviceInstance.Generation, common.GetObservedGeneration(serviceInstance))) smClient, err := r.GetSMClient(ctx, serviceInstance) if err != nil { @@ -212,14 +209,14 @@ func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient log.Info("Provision request is in progress (async)") serviceInstance.Status.OperationURL = provision.Location serviceInstance.Status.OperationType = smClientTypes.CREATE - utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceInstance) + utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceInstance, false) return ctrl.Result{Requeue: true, RequeueAfter: r.Config.PollInterval}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } log.Info(fmt.Sprintf("Instance provisioned successfully, instanceID: %s, subaccountID: %s", serviceInstance.Status.InstanceID, serviceInstance.Status.SubaccountID)) - utils.SetSuccessConditions(smClientTypes.CREATE, serviceInstance) + utils.SetSuccessConditions(smClientTypes.CREATE, serviceInstance, false) return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } @@ -250,7 +247,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient log.Info(fmt.Sprintf("Update request accepted, operation URL: %s", operationURL)) serviceInstance.Status.OperationURL = operationURL serviceInstance.Status.OperationType = smClientTypes.UPDATE - utils.SetInProgressConditions(ctx, smClientTypes.UPDATE, "", serviceInstance) + utils.SetInProgressConditions(ctx, smClientTypes.UPDATE, "", serviceInstance, false) serviceInstance.Status.ForceReconcile = false if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil { return ctrl.Result{}, err @@ -259,7 +256,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient return ctrl.Result{Requeue: true, RequeueAfter: r.Config.PollInterval}, nil } log.Info("Instance updated successfully") - utils.SetSuccessConditions(smClientTypes.UPDATE, serviceInstance) + utils.SetSuccessConditions(smClientTypes.UPDATE, serviceInstance, false) serviceInstance.Status.ForceReconcile = false return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } @@ -282,7 +279,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI if smInstance != nil { log.Info("instance exists in SM continue with deletion") serviceInstance.Status.InstanceID = smInstance.ID - utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceInstance) + utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceInstance, false) return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } log.Info("instance does not exists in SM, removing finalizer") @@ -367,9 +364,9 @@ func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *v status, statusErr := smClient.Status(serviceInstance.Status.OperationURL, nil) if statusErr != nil { log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceInstance.Status.OperationURL) - utils.SetInProgressConditions(ctx, serviceInstance.Status.OperationType, string(smClientTypes.INPROGRESS), serviceInstance) + utils.SetInProgressConditions(ctx, serviceInstance.Status.OperationType, string(smClientTypes.INPROGRESS), serviceInstance, false) // if failed to read operation status we cleanup the status to trigger re-sync from SM - freshStatus := v1.ServiceInstanceStatus{Conditions: serviceInstance.GetConditions(), ObservedGeneration: serviceInstance.Generation} + freshStatus := v1.ServiceInstanceStatus{Conditions: serviceInstance.GetConditions()} if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) { freshStatus.InstanceID = serviceInstance.Status.InstanceID } @@ -390,7 +387,7 @@ func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *v case smClientTypes.PENDING: if len(status.Description) > 0 { log.Info(fmt.Sprintf("last operation description is '%s'", status.Description)) - utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceInstance) + utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceInstance, true) if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil { log.Error(err, "unable to update ServiceInstance polling description") return ctrl.Result{}, err @@ -399,7 +396,7 @@ func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *v return ctrl.Result{Requeue: true, RequeueAfter: r.Config.PollInterval}, nil case smClientTypes.FAILED: errMsg := getErrorMsgFromLastOperation(status) - utils.SetFailureConditions(status.Type, errMsg, serviceInstance) + utils.SetFailureConditions(status.Type, errMsg, serviceInstance, true) // in order to delete eventually the object we need return with error if serviceInstance.Status.OperationType == smClientTypes.DELETE { serviceInstance.Status.OperationURL = "" @@ -426,7 +423,7 @@ func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *v return ctrl.Result{}, err } } - utils.SetSuccessConditions(status.Type, serviceInstance) + utils.SetSuccessConditions(status.Type, serviceInstance, true) } serviceInstance.Status.OperationURL = "" @@ -438,7 +435,7 @@ func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *v func (r *ServiceInstanceReconciler) handleAsyncDelete(ctx context.Context, serviceInstance *v1.ServiceInstance, opURL string) (ctrl.Result, error) { serviceInstance.Status.OperationURL = opURL serviceInstance.Status.OperationType = smClientTypes.DELETE - utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceInstance) + utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceInstance, false) if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil { return ctrl.Result{}, err @@ -477,14 +474,6 @@ func (r *ServiceInstanceReconciler) recover(ctx context.Context, smClient sm.Cli log.Info(fmt.Sprintf("found existing instance in SM with id %s, updating status", smInstance.ID)) updateHashedSpecValue(k8sInstance) - // set observed generation to 0 because we dont know which generation the current state in SM represents, - // unless the generation is 1 and SM is in the same state as operator - if k8sInstance.Generation == 1 { - k8sInstance.SetObservedGeneration(1) - } else { - k8sInstance.SetObservedGeneration(0) - } - if smInstance.Ready { k8sInstance.Status.Ready = metav1.ConditionTrue } @@ -519,11 +508,11 @@ func (r *ServiceInstanceReconciler) recover(ctx context.Context, smClient sm.Cli case smClientTypes.INPROGRESS: k8sInstance.Status.OperationURL = sm.BuildOperationURL(smInstance.LastOperation.ID, smInstance.ID, smClientTypes.ServiceInstancesURL) k8sInstance.Status.OperationType = smInstance.LastOperation.Type - utils.SetInProgressConditions(ctx, smInstance.LastOperation.Type, smInstance.LastOperation.Description, k8sInstance) + utils.SetInProgressConditions(ctx, smInstance.LastOperation.Type, smInstance.LastOperation.Description, k8sInstance, false) case smClientTypes.SUCCEEDED: - utils.SetSuccessConditions(operationType, k8sInstance) + utils.SetSuccessConditions(operationType, k8sInstance, false) case smClientTypes.FAILED: - utils.SetFailureConditions(operationType, description, k8sInstance) + utils.SetFailureConditions(operationType, description, k8sInstance, false) } return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, k8sInstance) @@ -644,8 +633,9 @@ func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool log.Info(fmt.Sprintf("instance is not in final state, async operation is in progress (%s)", serviceInstance.Status.OperationURL)) return false } - if serviceInstance.Generation != serviceInstance.GetObservedGeneration() { - log.Info(fmt.Sprintf("instance is not in final state, generation: %d, observedGen: %d", serviceInstance.Generation, serviceInstance.GetObservedGeneration())) + observedGen := common.GetObservedGeneration(serviceInstance) + if serviceInstance.Generation != observedGen { + log.Info(fmt.Sprintf("instance is not in final state, generation: %d, observedGen: %d", serviceInstance.Generation, observedGen)) return false } diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 82e1a82d..251ac0ac 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -42,11 +42,10 @@ const ( var _ = Describe("ServiceInstance controller", func() { var ( - ctx context.Context - serviceInstance *v1.ServiceInstance fakeInstanceName string defaultLookupKey types.NamespacedName + paramsSecret *corev1.Secret ) instanceSpec := v1.ServiceInstanceSpec{ @@ -59,7 +58,7 @@ var _ = Describe("ServiceInstance controller", func() { ParametersFrom: []v1.ParametersFromSource{ { SecretKeyRef: &v1.SecretKeyReference{ - Name: "param-secret", + Name: "instance-params-secret", Key: "secret-parameter", }, }, @@ -77,7 +76,7 @@ var _ = Describe("ServiceInstance controller", func() { ParametersFrom: []v1.ParametersFromSource{ { SecretKeyRef: &v1.SecretKeyReference{ - Name: "param-secret", + Name: "instance-params-secret", Key: "secret-parameter", }, }, @@ -138,18 +137,14 @@ var _ = Describe("ServiceInstance controller", func() { fakeClient.DeprovisionReturns("", nil) fakeClient.GetInstanceByIDReturns(&smclientTypes.ServiceInstance{ID: fakeInstanceID, Ready: true, LastOperation: &smClientTypes.Operation{State: smClientTypes.SUCCEEDED, Type: smClientTypes.CREATE}}, nil) - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: testNamespace, Name: "param-secret"}, &corev1.Secret{}) - if apierrors.IsNotFound(err) { - createParamsSecret(testNamespace) - } else { - Expect(err).ToNot(HaveOccurred()) - } + paramsSecret = createParamsSecret(ctx, "instance-params-secret", testNamespace) }) AfterEach(func() { if serviceInstance != nil { - deleteInstance(ctx, serviceInstance, true) + deleteAndWait(ctx, serviceInstance) } + deleteAndWait(ctx, paramsSecret) }) Describe("Create", func() { @@ -226,20 +221,10 @@ var _ = Describe("ServiceInstance controller", func() { params := smInstance.Parameters Expect(params).To(ContainSubstring("\"key\":\"value\"")) Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) - - secret := &corev1.Secret{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: testNamespace, Name: "param-secret"}, secret) - Expect(err).ToNot(HaveOccurred()) - credentialsMap := make(map[string][]byte) - credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") - secret.Data = credentialsMap - Expect(k8sClient.Update(ctx, secret)).To(Succeed()) - - Expect(fakeClient.ProvisionCallCount()).To(Equal(1)) }) }) - When("provision request to SM succeeds", func() { - FIt("should provision instance of the provided offering and plan name successfully", func() { + When("secret updated", func() { + It("should provision instance of the provided offering and plan name successfully", func() { instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) serviceInstance = createInstance(ctx, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) @@ -247,18 +232,15 @@ var _ = Describe("ServiceInstance controller", func() { Expect(params).To(ContainSubstring("\"key\":\"value\"")) Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) - secret := &corev1.Secret{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: testNamespace, Name: "param-secret"}, secret) - Expect(err).ToNot(HaveOccurred()) credentialsMap := make(map[string][]byte) credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") - secret.Data = credentialsMap - Expect(k8sClient.Update(ctx, secret)).To(Succeed()) + paramsSecret.Data = credentialsMap + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Eventually(func() bool { return fakeClient.UpdateInstanceCallCount() == 1 }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") - _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(1) + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) params = smInstance.Parameters Expect(params).To(ContainSubstring("\"key\":\"value\"")) Expect(params).To(ContainSubstring("\"secret-key\":\"new-secret-value\"")) @@ -877,7 +859,7 @@ var _ = Describe("ServiceInstance controller", func() { Eventually(func() bool { _ = k8sClient.Get(ctx, key, serviceInstance) return serviceInstance.Status.InstanceID == fakeInstanceID - }, timeout, interval).Should(BeTrue(), eventuallyMsgForResource("service instance id not recovered", key, serviceInstance)) + }, timeout, interval).Should(BeTrue(), eventuallyMsgForResource("service instance id not recovered", serviceInstance)) Expect(fakeClient.ProvisionCallCount()).To(Equal(0)) Expect(fakeClient.ListInstancesCallCount()).To(BeNumerically(">", 0)) fakeClient.StatusReturns(&smclientTypes.Operation{ResourceID: fakeInstanceID, State: smClientTypes.SUCCEEDED, Type: smClientTypes.CREATE}, nil) @@ -1171,6 +1153,9 @@ var _ = Describe("ServiceInstance controller", func() { When("async operation in progress", func() { It("should return false", func() { var instance = &v1.ServiceInstance{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 2, + }, Status: v1.ServiceInstanceStatus{ Conditions: []metav1.Condition{ { @@ -1179,9 +1164,8 @@ var _ = Describe("ServiceInstance controller", func() { ObservedGeneration: 1, }, }, - HashedSpec: "929e78f4449f8036ce39da3cc3e7eaea", - OperationURL: "/operations/somepollingurl", - ObservedGeneration: 2, + HashedSpec: "929e78f4449f8036ce39da3cc3e7eaea", + OperationURL: "/operations/somepollingurl", }, Spec: v1.ServiceInstanceSpec{ ExternalName: "name", @@ -1230,7 +1214,7 @@ var _ = Describe("ServiceInstance controller", func() { { Type: common.ConditionSucceeded, Status: metav1.ConditionTrue, - ObservedGeneration: 2, + ObservedGeneration: 1, }, }, HashedSpec: "bla", @@ -1278,6 +1262,9 @@ var _ = Describe("ServiceInstance controller", func() { When("in final state", func() { It("should return true", func() { var instance = &v1.ServiceInstance{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 2, + }, Status: v1.ServiceInstanceStatus{ Conditions: []metav1.Condition{ { @@ -1295,8 +1282,7 @@ var _ = Describe("ServiceInstance controller", func() { Status: metav1.ConditionTrue, }, }, - HashedSpec: "929e78f4449f8036ce39da3cc3e7eaea", - ObservedGeneration: 2, + HashedSpec: "929e78f4449f8036ce39da3cc3e7eaea", }, Spec: v1.ServiceInstanceSpec{ ExternalName: "name", diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 56b1a2fe..2fafc636 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -237,7 +237,7 @@ var _ = AfterSuite(func() { }) func isResourceReady(resource common.SAPBTPResource) bool { - return resource.GetObservedGeneration() == resource.GetGeneration() && + return common.GetObservedGeneration(resource) == resource.GetGeneration() && meta.IsStatusConditionPresentAndEqual(resource.GetConditions(), common.ConditionReady, metav1.ConditionTrue) } @@ -252,7 +252,7 @@ func waitForResourceCondition(ctx context.Context, resource common.SAPBTPResourc return false } - if resource.GetObservedGeneration() != resource.GetGeneration() { + if common.GetObservedGeneration(resource) != resource.GetGeneration() { return false } @@ -275,10 +275,7 @@ func waitForResourceCondition(ctx context.Context, resource common.SAPBTPResourc return true }, timeout*3, interval).Should(BeTrue(), - eventuallyMsgForResource( - fmt.Sprintf("expected condition: {type: %s, status: %s, reason: %s, message: %s} was not met", conditionType, status, reason, message), - key, - resource), + eventuallyMsgForResource(fmt.Sprintf("expected condition: {type: %s, status: %s, reason: %s, message: %s} was not met. %v", conditionType, status, reason, message, resource.GetConditions()), resource), ) } @@ -286,51 +283,44 @@ func getResourceNamespacedName(resource client.Object) types.NamespacedName { return types.NamespacedName{Namespace: resource.GetNamespace(), Name: resource.GetName()} } -func deleteAndWait(ctx context.Context, key types.NamespacedName, resource client.Object) { - wait := true +func deleteAndWait(ctx context.Context, resource client.Object) { Eventually(func() bool { - if err := k8sClient.Get(ctx, key, resource); err != nil { - if apierrors.IsNotFound(err) { - wait = false - return true - } - return false - } - - if err := k8sClient.Delete(ctx, resource); err != nil { + if err := k8sClient.Get(ctx, getResourceNamespacedName(resource), resource); err != nil { if apierrors.IsNotFound(err) { - wait = false return true } return false } - return true - }, timeout, interval).Should(BeTrue(), eventuallyMsgForResource("failed to mark for deletion", key, resource)) - - if wait { - waitForResourceToBeDeleted(ctx, key, resource) - } + err := k8sClient.Delete(ctx, resource) + return apierrors.IsNotFound(err) + }, timeout, interval).Should(BeTrue(), eventuallyMsgForResource("failed to delete resource", resource)) } func waitForResourceToBeDeleted(ctx context.Context, key types.NamespacedName, resource client.Object) { Eventually(func() bool { return apierrors.IsNotFound(k8sClient.Get(ctx, key, resource)) - }, timeout, interval).Should(BeTrue(), eventuallyMsgForResource("resource is not deleted", key, resource)) + }, timeout, interval).Should(BeTrue(), eventuallyMsgForResource("resource is not deleted", resource)) } -func createParamsSecret(namespace string) { +func createParamsSecret(ctx context.Context, secretName, namespace string) *corev1.Secret { credentialsMap := make(map[string][]byte) credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"secret-value\"}") secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "param-secret", + Name: secretName, Namespace: namespace, }, Data: credentialsMap, } - Expect(k8sClient.Create(context.Background(), secret)).ToNot(HaveOccurred()) + Expect(k8sClient.Create(ctx, secret)).To(Succeed()) + + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, secret) + return err == nil + }, timeout, interval).Should(BeTrue()) + return secret } func printSection(str string) { @@ -366,7 +356,7 @@ func getTransientBrokerError(errorMessage string) error { } } -func eventuallyMsgForResource(message string, key types.NamespacedName, resource client.Object) string { +func eventuallyMsgForResource(message string, resource client.Object) string { gvk, _ := apiutil.GVKForObject(resource, scheme.Scheme) - return fmt.Sprintf("eventaully failure for %s %s. message: %s", gvk.Kind, key.String(), message) + return fmt.Sprintf("eventaully failure for %s %s. message: %s", gvk.Kind, getResourceNamespacedName(resource), message) } diff --git a/internal/utils/condition_utils.go b/internal/utils/condition_utils.go index 6d82cc40..21174963 100644 --- a/internal/utils/condition_utils.go +++ b/internal/utils/condition_utils.go @@ -14,7 +14,7 @@ import ( func InitConditions(ctx context.Context, k8sClient client.Client, obj common.SAPBTPResource) error { obj.SetReady(metav1.ConditionFalse) - SetInProgressConditions(ctx, smClientTypes.CREATE, "Pending", obj) + SetInProgressConditions(ctx, smClientTypes.CREATE, "Pending", obj, false) return UpdateStatus(ctx, k8sClient, obj) } @@ -55,7 +55,7 @@ func GetConditionReason(opType smClientTypes.OperationCategory, state smClientTy return common.Unknown } -func SetInProgressConditions(ctx context.Context, operationType smClientTypes.OperationCategory, message string, object common.SAPBTPResource) { +func SetInProgressConditions(ctx context.Context, operationType smClientTypes.OperationCategory, message string, object common.SAPBTPResource, isAsyncOperation bool) { log := GetLogger(ctx) if len(message) == 0 { if operationType == smClientTypes.CREATE { @@ -71,12 +71,16 @@ func SetInProgressConditions(ctx context.Context, operationType smClientTypes.Op if len(conditions) > 0 { meta.RemoveStatusCondition(&conditions, common.ConditionFailed) } + observedGen := object.GetGeneration() + if isAsyncOperation { + observedGen = getLastObservedGen(object) + } lastOpCondition := metav1.Condition{ Type: common.ConditionSucceeded, Status: metav1.ConditionFalse, Reason: GetConditionReason(operationType, smClientTypes.INPROGRESS), Message: message, - ObservedGeneration: object.GetObservedGeneration(), + ObservedGeneration: observedGen, } meta.SetStatusCondition(&conditions, lastOpCondition) meta.SetStatusCondition(&conditions, getReadyCondition(object)) @@ -85,7 +89,7 @@ func SetInProgressConditions(ctx context.Context, operationType smClientTypes.Op log.Info(fmt.Sprintf("setting inProgress conditions: reason: %s, message:%s, generation: %d", lastOpCondition.Reason, message, object.GetGeneration())) } -func SetSuccessConditions(operationType smClientTypes.OperationCategory, object common.SAPBTPResource) { +func SetSuccessConditions(operationType smClientTypes.OperationCategory, object common.SAPBTPResource, isAsyncOperation bool) { var message string if operationType == smClientTypes.CREATE { message = fmt.Sprintf("%s provisioned successfully", object.GetControllerName()) @@ -99,19 +103,23 @@ func SetSuccessConditions(operationType smClientTypes.OperationCategory, object if len(conditions) > 0 { meta.RemoveStatusCondition(&conditions, common.ConditionFailed) } + observedGen := object.GetGeneration() + if isAsyncOperation { + observedGen = getLastObservedGen(object) + } lastOpCondition := metav1.Condition{ Type: common.ConditionSucceeded, Status: metav1.ConditionTrue, Reason: GetConditionReason(operationType, smClientTypes.SUCCEEDED), Message: message, - ObservedGeneration: object.GetObservedGeneration(), + ObservedGeneration: observedGen, } readyCondition := metav1.Condition{ Type: common.ConditionReady, Status: metav1.ConditionTrue, Reason: common.Provisioned, Message: message, - ObservedGeneration: object.GetObservedGeneration(), + ObservedGeneration: observedGen, } meta.SetStatusCondition(&conditions, lastOpCondition) meta.SetStatusCondition(&conditions, readyCondition) @@ -130,13 +138,13 @@ func SetCredRotationInProgressConditions(reason, message string, object common.S Status: metav1.ConditionTrue, Reason: reason, Message: message, - ObservedGeneration: object.GetObservedGeneration(), + ObservedGeneration: object.GetGeneration(), } meta.SetStatusCondition(&conditions, credRotCondition) object.SetConditions(conditions) } -func SetFailureConditions(operationType smClientTypes.OperationCategory, errorMessage string, object common.SAPBTPResource) { +func SetFailureConditions(operationType smClientTypes.OperationCategory, errorMessage string, object common.SAPBTPResource, isAsyncOperation bool) { var message string if operationType == smClientTypes.CREATE { message = fmt.Sprintf("%s create failed: %s", object.GetControllerName(), errorMessage) @@ -153,23 +161,27 @@ func SetFailureConditions(operationType smClientTypes.OperationCategory, errorMe reason = object.GetConditions()[0].Reason } + observedGen := object.GetGeneration() + if isAsyncOperation { + observedGen = getLastObservedGen(object) + } conditions := object.GetConditions() lastOpCondition := metav1.Condition{ Type: common.ConditionSucceeded, Status: metav1.ConditionFalse, Reason: reason, Message: message, - ObservedGeneration: object.GetObservedGeneration(), + ObservedGeneration: observedGen, } - meta.SetStatusCondition(&conditions, lastOpCondition) - failedCondition := metav1.Condition{ Type: common.ConditionFailed, Status: metav1.ConditionTrue, Reason: reason, Message: message, - ObservedGeneration: object.GetObservedGeneration(), + ObservedGeneration: observedGen, } + + meta.SetStatusCondition(&conditions, lastOpCondition) meta.SetStatusCondition(&conditions, failedCondition) meta.SetStatusCondition(&conditions, getReadyCondition(object)) @@ -180,15 +192,14 @@ func MarkAsNonTransientError(ctx context.Context, k8sClient client.Client, opera log := GetLogger(ctx) errMsg := err.Error() log.Info(fmt.Sprintf("operation %s of %s encountered a non transient error %s, setting failure conditions", operationType, object.GetControllerName(), errMsg)) - SetFailureConditions(operationType, errMsg, object) - object.SetObservedGeneration(object.GetGeneration()) + SetFailureConditions(operationType, errMsg, object, false) return ctrl.Result{}, UpdateStatus(ctx, k8sClient, object) } func MarkAsTransientError(ctx context.Context, k8sClient client.Client, operationType smClientTypes.OperationCategory, err error, object common.SAPBTPResource) (ctrl.Result, error) { log := GetLogger(ctx) log.Info(fmt.Sprintf("operation %s of %s encountered a transient error %s, retrying operation :)", operationType, object.GetControllerName(), err.Error())) - SetInProgressConditions(ctx, operationType, err.Error(), object) + SetInProgressConditions(ctx, operationType, err.Error(), object, false) if updateErr := UpdateStatus(ctx, k8sClient, object); updateErr != nil { return ctrl.Result{}, updateErr } @@ -198,7 +209,7 @@ func MarkAsTransientError(ctx context.Context, k8sClient client.Client, operatio // blocked condition marks to the user that action from his side is required, this is considered as in progress operation func SetBlockedCondition(ctx context.Context, message string, object common.SAPBTPResource) { - SetInProgressConditions(ctx, common.Unknown, message, object) + SetInProgressConditions(ctx, common.Unknown, message, object, false) lastOpCondition := meta.FindStatusCondition(object.GetConditions(), common.ConditionSucceeded) lastOpCondition.Reason = common.Blocked } @@ -229,3 +240,12 @@ func getReadyCondition(object common.SAPBTPResource) metav1.Condition { return metav1.Condition{Type: common.ConditionReady, Status: status, Reason: reason} } + +func getLastObservedGen(object common.SAPBTPResource) int64 { + conditions := object.GetConditions() + cond := meta.FindStatusCondition(conditions, common.ConditionSucceeded) + if cond != nil { + return cond.ObservedGeneration + } + return 0 +} diff --git a/internal/utils/condition_utils_test.go b/internal/utils/condition_utils_test.go index a044eaf7..a29e552d 100644 --- a/internal/utils/condition_utils_test.go +++ b/internal/utils/condition_utils_test.go @@ -120,7 +120,7 @@ var _ = Describe("Condition Utils", func() { It("should set in-progress conditions", func() { resource = getBinding() - SetInProgressConditions(ctx, smClientTypes.CREATE, "Pending", resource) + SetInProgressConditions(ctx, smClientTypes.CREATE, "Pending", resource, false) // Add assertions to check the state of the resource after calling SetInProgressConditions Expect(resource.GetConditions()).ToNot(BeEmpty()) @@ -133,7 +133,7 @@ var _ = Describe("Condition Utils", func() { operationType := smClientTypes.CREATE resource = getBinding() - SetSuccessConditions(operationType, resource) + SetSuccessConditions(operationType, resource, false) // Add assertions to check the state of the resource after calling SetSuccessConditions Expect(resource.GetConditions()).ToNot(BeEmpty()) @@ -160,7 +160,7 @@ var _ = Describe("Condition Utils", func() { It("should set failure conditions", func() { operationType := smClientTypes.CREATE errorMessage := "Operation failed" - SetFailureConditions(operationType, errorMessage, resource) + SetFailureConditions(operationType, errorMessage, resource, false) Expect(resource.GetConditions()).ToNot(BeEmpty()) Expect(meta.IsStatusConditionPresentAndEqual(resource.GetConditions(), common.ConditionReady, metav1.ConditionFalse)).To(BeTrue()) }) From a9d81277600d90ee03e61fed86fcec41272a3fd6 Mon Sep 17 00:00:00 2001 From: I501080 Date: Wed, 4 Dec 2024 15:09:52 +0200 Subject: [PATCH 25/74] observed generation refactor --- controllers/servicebinding_controller_test.go | 29 +++++++++++-------- .../serviceinstance_controller_test.go | 1 + 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/controllers/servicebinding_controller_test.go b/controllers/servicebinding_controller_test.go index d9280f6b..df84a67e 100644 --- a/controllers/servicebinding_controller_test.go +++ b/controllers/servicebinding_controller_test.go @@ -173,9 +173,6 @@ var _ = Describe("ServiceBinding controller", func() { bindingName = "test-binding-" + testUUID instanceExternalName = instanceName + "-external" - paramsSecret = createParamsSecret(ctx, "binding-params-secret", testNamespace) - Expect(len(paramsSecret.Data)).ToNot(BeZero()) - fakeClient = &smfakes.FakeClient{} fakeClient.ProvisionReturns(&sm.ProvisionResponse{InstanceID: "12345678", Tags: []byte("[\"test\"]")}, nil) fakeClient.BindReturns(&smClientTypes.ServiceBinding{ID: fakeBindingID, Credentials: json.RawMessage(`{"secret_key": "secret_value", "escaped": "{\"escaped_key\":\"escaped_val\"}"}`)}, "", nil) @@ -184,8 +181,9 @@ var _ = Describe("ServiceBinding controller", func() { fakeClient.GetInstanceByIDReturns(smInstance, nil) defaultLookupKey = types.NamespacedName{Namespace: bindingTestNamespace, Name: bindingName} - createdInstance = createInstance(ctx, instanceName, bindingTestNamespace, instanceExternalName) + paramsSecret = createParamsSecret(ctx, "binding-params-secret", bindingTestNamespace) + }) AfterEach(func() { @@ -392,7 +390,7 @@ var _ = Describe("ServiceBinding controller", func() { fakeClient.BindReturns(nil, "", errors.New(errorMessage)) }) - FIt("should fail with the error returned from SM", func() { + It("should fail with the error returned from SM", func() { createBindingWithError(ctx, bindingName, bindingTestNamespace, instanceName, "binding-external-name", errorMessage, "") }) }) @@ -1336,6 +1334,13 @@ stringData: Context("Cross Namespace", func() { var crossBinding *v1.ServiceBinding + var serviceInstanceInAnotherNamespace *v1.ServiceInstance + BeforeEach(func() { + serviceInstanceInAnotherNamespace = createInstance(ctx, instanceName, testNamespace, instanceExternalName) + }) + AfterEach(func() { + deleteAndWait(ctx, serviceInstanceInAnotherNamespace) + }) When("binding is created in a different namespace than the instance", func() { AfterEach(func() { if crossBinding != nil { @@ -1343,7 +1348,7 @@ stringData: } }) It("should succeed", func() { - crossBinding = createBinding(ctx, bindingName, testNamespace, instanceName, bindingTestNamespace, "cross-binding-external-name", "") + crossBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, testNamespace, "cross-binding-external-name", "") By("Verify binding secret created") getSecret(ctx, createdBinding.Spec.SecretName, createdBinding.Namespace, true) @@ -1353,7 +1358,7 @@ stringData: Context("cred rotation", func() { BeforeEach(func() { fakeClient.RenameBindingReturns(nil, nil) - crossBinding = createBinding(ctx, bindingName, testNamespace, instanceName, bindingTestNamespace, "cross-binding-external-name", "") + crossBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, testNamespace, "cross-binding-external-name", "") fakeClient.ListBindingsStub = func(params *sm.Parameters) (*smClientTypes.ServiceBindings, error) { if params == nil || params.FieldQuery == nil || len(params.FieldQuery) == 0 { return nil, nil @@ -1385,7 +1390,7 @@ stringData: }) It("should rotate the credentials and create old binding", func() { - key := types.NamespacedName{Name: bindingName, Namespace: testNamespace} + key := types.NamespacedName{Name: bindingName, Namespace: bindingTestNamespace} Expect(k8sClient.Get(ctx, key, crossBinding)).To(Succeed()) crossBinding.Spec.CredRotationPolicy = &v1.CredentialsRotationPolicy{ Enabled: true, @@ -1395,7 +1400,7 @@ stringData: var secret *corev1.Secret Eventually(func() bool { - secret = getSecret(ctx, crossBinding.Spec.SecretName, testNamespace, true) + secret = getSecret(ctx, crossBinding.Spec.SecretName, bindingTestNamespace, true) secret.Data = map[string][]byte{} return k8sClient.Update(ctx, secret) == nil }, timeout, interval).Should(BeTrue()) @@ -1408,19 +1413,19 @@ stringData: return err == nil && myBinding.Status.LastCredentialsRotationTime != nil && len(myBinding.Status.Conditions) == 2 }, timeout, interval).Should(BeTrue()) - secret = getSecret(ctx, myBinding.Spec.SecretName, testNamespace, true) + secret = getSecret(ctx, myBinding.Spec.SecretName, bindingTestNamespace, true) val := secret.Data["secret_key"] Expect(string(val)).To(Equal("secret_value")) bindingList := &v1.ServiceBindingList{} Eventually(func() bool { - Expect(k8sClient.List(ctx, bindingList, client.MatchingLabels{common.StaleBindingIDLabel: myBinding.Status.BindingID}, client.InNamespace(testNamespace))).To(Succeed()) + Expect(k8sClient.List(ctx, bindingList, client.MatchingLabels{common.StaleBindingIDLabel: myBinding.Status.BindingID}, client.InNamespace(bindingTestNamespace))).To(Succeed()) return len(bindingList.Items) > 0 }, timeout, interval).Should(BeTrue()) oldBinding := bindingList.Items[0] Expect(oldBinding.Spec.CredRotationPolicy.Enabled).To(BeFalse()) - secret = getSecret(ctx, oldBinding.Spec.SecretName, testNamespace, true) + secret = getSecret(ctx, oldBinding.Spec.SecretName, bindingTestNamespace, true) val = secret.Data["secret_key2"] Expect(string(val)).To(Equal("secret_value2")) }) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 251ac0ac..73192ec4 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -232,6 +232,7 @@ var _ = Describe("ServiceInstance controller", func() { Expect(params).To(ContainSubstring("\"key\":\"value\"")) Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) + Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) credentialsMap := make(map[string][]byte) credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") paramsSecret.Data = credentialsMap From 346991f16cdee7a1914267ad5279b0c8e7818fbc Mon Sep 17 00:00:00 2001 From: I501080 Date: Thu, 5 Dec 2024 09:32:41 +0200 Subject: [PATCH 26/74] binding tests refactor --- controllers/servicebinding_controller_test.go | 129 ++++++++---------- 1 file changed, 58 insertions(+), 71 deletions(-) diff --git a/controllers/servicebinding_controller_test.go b/controllers/servicebinding_controller_test.go index df84a67e..4d9baf20 100644 --- a/controllers/servicebinding_controller_test.go +++ b/controllers/servicebinding_controller_test.go @@ -48,7 +48,7 @@ var _ = Describe("ServiceBinding controller", func() { paramsSecret *corev1.Secret ) - createBindingWithoutAssertionsAndWait := func(ctx context.Context, name, namespace, instanceName, instanceNamespace, externalName string, secretTemplate string, wait bool) (*v1.ServiceBinding, error) { + createBindingWithoutAssertions := func(ctx context.Context, name, namespace, instanceName, instanceNamespace, externalName string, secretTemplate string, wait bool) (*v1.ServiceBinding, error) { binding := generateBasicBindingTemplate(name, namespace, instanceName, instanceNamespace, externalName, secretTemplate) if err := k8sClient.Create(ctx, binding); err != nil { return nil, err @@ -71,40 +71,8 @@ var _ = Describe("ServiceBinding controller", func() { return createdBinding, nil } - createBindingWithoutAssertions := func(ctx context.Context, name, namespace, instanceName, instanceNamespace, externalName string, secretTemplate string) (*v1.ServiceBinding, error) { - return createBindingWithoutAssertionsAndWait(ctx, name, namespace, instanceName, instanceNamespace, externalName, secretTemplate, false) - } - - createBindingWithTransitError := func(ctx context.Context, name, namespace, instanceName, externalName, failureMessage string, secretTemplate string) { - binding, err := createBindingWithoutAssertions(ctx, name, namespace, instanceName, "", externalName, secretTemplate) - if err != nil { - Expect(err.Error()).To(ContainSubstring(failureMessage)) - } else { - waitForResourceCondition(ctx, binding, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, failureMessage) - } - } - - createBindingWithError := func(ctx context.Context, name, namespace, instanceName, externalName, failureMessage string, secretTemplate string) { - binding, err := createBindingWithoutAssertions(ctx, name, namespace, instanceName, "", externalName, secretTemplate) - if err != nil { - Expect(err.Error()).To(ContainSubstring(failureMessage)) - } else { - waitForResourceCondition(ctx, binding, common.ConditionFailed, metav1.ConditionTrue, "", failureMessage) - } - } - - createBindingWithBlockedError := func(ctx context.Context, name, namespace, instanceName, externalName, failureMessage string) *v1.ServiceBinding { - binding, err := createBindingWithoutAssertions(ctx, name, namespace, instanceName, "", externalName, "") - if err != nil { - Expect(err.Error()).To(ContainSubstring(failureMessage)) - } else { - waitForResourceCondition(ctx, binding, common.ConditionSucceeded, metav1.ConditionFalse, "", failureMessage) - } - return binding - } - - createBinding := func(ctx context.Context, name, namespace, instanceName, instanceNamespace, externalName string, secretTemplate string) *v1.ServiceBinding { - createdBinding, err := createBindingWithoutAssertions(ctx, name, namespace, instanceName, instanceNamespace, externalName, secretTemplate) + createAndValidateBinding := func(ctx context.Context, name, namespace, instanceName, instanceNamespace, externalName string, secretTemplate string) *v1.ServiceBinding { + createdBinding, err := createBindingWithoutAssertions(ctx, name, namespace, instanceName, instanceNamespace, externalName, secretTemplate, false) Expect(err).ToNot(HaveOccurred()) Expect(createdBinding.Status.InstanceID).ToNot(BeEmpty()) Expect(createdBinding.Status.BindingID).To(Equal(fakeBindingID)) @@ -206,15 +174,17 @@ var _ = Describe("ServiceBinding controller", func() { Context("invalid parameters", func() { When("service instance name is not provided", func() { It("should fail", func() { - createBindingWithTransitError(ctx, bindingName, bindingTestNamespace, "", "", - "spec.serviceInstanceName in body should be at least 1 chars long", "") + _, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, "", "", "", "", false) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("spec.serviceInstanceName in body should be at least 1 chars long")) }) }) When("referenced service instance does not exist", func() { It("should fail", func() { - createBindingWithBlockedError(ctx, bindingName, bindingTestNamespace, "no-such-instance", "", - "couldn't find the service instance") + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, "no-such-instance", "", "", "", false) + Expect(err).ToNot(HaveOccurred()) + waitForResourceCondition(ctx, binding, common.ConditionSucceeded, metav1.ConditionFalse, "", "couldn't find the service instance") }) }) @@ -280,7 +250,7 @@ var _ = Describe("ServiceBinding controller", func() { Context("sync", func() { It("Should create binding and store the binding credentials in a secret", func() { - createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") + createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") Expect(createdBinding.Spec.ExternalName).To(Equal("binding-external-name")) Expect(createdBinding.Spec.UserInfo).NotTo(BeNil()) @@ -350,7 +320,7 @@ var _ = Describe("ServiceBinding controller", func() { When("secret deleted by user", func() { It("should recreate the secret", func() { - createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") + createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") secretLookupKey := types.NamespacedName{Name: createdBinding.Spec.SecretName, Namespace: createdBinding.Namespace} bindingSecret := getSecret(ctx, secretLookupKey.Name, secretLookupKey.Namespace, true) originalSecretUID := bindingSecret.UID @@ -391,7 +361,9 @@ var _ = Describe("ServiceBinding controller", func() { }) It("should fail with the error returned from SM", func() { - createBindingWithError(ctx, bindingName, bindingTestNamespace, instanceName, "binding-external-name", errorMessage, "") + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", false) + Expect(err).ToNot(HaveOccurred()) + waitForResourceCondition(ctx, binding, common.ConditionFailed, metav1.ConditionTrue, "", errorMessage) }) }) @@ -406,7 +378,7 @@ var _ = Describe("ServiceBinding controller", func() { }) It("should eventually succeed", func() { - b, err := createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", true) + b, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", true) Expect(err).ToNot(HaveOccurred()) Expect(isResourceReady(b)).To(BeTrue()) }) @@ -422,7 +394,9 @@ var _ = Describe("ServiceBinding controller", func() { }) It("should fail", func() { - createBindingWithTransitError(ctx, bindingName, bindingTestNamespace, instanceName, "binding-external-name", errorMessage, "") + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", false) + Expect(err).ToNot(HaveOccurred()) + waitForResourceCondition(ctx, binding, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, errorMessage) }) }) @@ -433,7 +407,7 @@ var _ = Describe("ServiceBinding controller", func() { }) It("should detect the error as transient and eventually succeed", func() { - createdBinding, _ := createBindingWithoutAssertionsAndWait(ctx, + createdBinding, _ := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, @@ -459,8 +433,10 @@ var _ = Describe("ServiceBinding controller", func() { fakeClient.BindReturns(nil, "", getNonTransientBrokerError(errorMessage)) }) - It("should detect the error as non-transient and fail", func() { - createBindingWithTransitError(ctx, bindingName, bindingTestNamespace, instanceName, "binding-external-name", errorMessage, "") + FIt("should detect the error as non-transient and fail", func() { + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", false) + Expect(err).ToNot(HaveOccurred()) + waitForResourceCondition(ctx, binding, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, errorMessage) }) }) @@ -472,7 +448,7 @@ var _ = Describe("ServiceBinding controller", func() { }) It("creation will fail with appropriate message", func() { - createdBinding, _ = createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "") + createdBinding, _ = createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "", false) waitForResourceCondition(ctx, createdBinding, common.ConditionFailed, metav1.ConditionTrue, "CreateFailed", "failed to create secret") }) }) @@ -486,7 +462,7 @@ var _ = Describe("ServiceBinding controller", func() { When("bind polling returns success", func() { It("Should create binding and store the binding credentials in a secret", func() { fakeClient.StatusReturns(&smClientTypes.Operation{ResourceID: fakeBindingID, State: smClientTypes.SUCCEEDED}, nil) - createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "") + createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "") }) }) @@ -498,7 +474,10 @@ var _ = Describe("ServiceBinding controller", func() { State: smClientTypes.FAILED, Description: errorMessage, }, nil) - createBindingWithError(ctx, bindingName, bindingTestNamespace, instanceName, "existing-name", errorMessage, "") + + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "existing-name", "", false) + Expect(err).ToNot(HaveOccurred()) + waitForResourceCondition(ctx, binding, common.ConditionFailed, metav1.ConditionTrue, "", errorMessage) }) }) @@ -509,7 +488,7 @@ var _ = Describe("ServiceBinding controller", func() { fakeClient.GetBindingByIDReturns(&smClientTypes.ServiceBinding{ID: fakeBindingID, LastOperation: &smClientTypes.Operation{State: smClientTypes.SUCCEEDED, Type: smClientTypes.CREATE}}, nil) }) It("should eventually succeed", func() { - binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "") + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "", false) Expect(err).ToNot(HaveOccurred()) waitForResourceCondition(ctx, binding, common.ConditionFailed, metav1.ConditionTrue, "", "no polling for you") fakeClient.ListBindingsReturns(&smClientTypes.ServiceBindings{ @@ -537,7 +516,7 @@ var _ = Describe("ServiceBinding controller", func() { common.UseInstanceMetadataNameInSecret: "true", } updateInstance(ctx, createdInstance) - createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "") + createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "") bindingSecret := getSecret(ctx, createdBinding.Spec.SecretName, createdBinding.Namespace, true) validateInstanceInfo(bindingSecret, instanceName) validateSecretMetadata(bindingSecret, nil) @@ -546,7 +525,7 @@ var _ = Describe("ServiceBinding controller", func() { When("external name is not provided", func() { It("succeeds and uses the k8s name as external name", func() { - createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "") + createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "", "") Expect(createdBinding.Spec.ExternalName).To(Equal(createdBinding.Name)) }) }) @@ -564,7 +543,11 @@ var _ = Describe("ServiceBinding controller", func() { It("should retry and succeed once the instance is ready", func() { utils.SetFailureConditions(smClientTypes.CREATE, "Failed to create instance (test)", createdInstance, false) updateInstanceStatus(ctx, createdInstance) - binding := createBindingWithBlockedError(ctx, bindingName, bindingTestNamespace, instanceName, "binding-external-name", "is not usable") + + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", false) + Expect(err).ToNot(HaveOccurred()) + waitForResourceCondition(ctx, binding, common.ConditionSucceeded, metav1.ConditionFalse, "", "is not usable") + utils.SetSuccessConditions(smClientTypes.CREATE, createdInstance, false) updateInstanceStatus(ctx, createdInstance) waitForResourceToBeReady(ctx, binding) @@ -579,7 +562,7 @@ var _ = Describe("ServiceBinding controller", func() { createdInstance.Status.OperationType = smClientTypes.CREATE updateInstanceStatus(ctx, createdInstance) - createdBinding, err := createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", false) + createdBinding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", false) Expect(err).ToNot(HaveOccurred()) Expect(utils.IsInProgress(createdBinding)).To(BeTrue()) @@ -606,7 +589,7 @@ stringData: newKey: {{ .credentials.secret_key }} tags: {{ .instance.tags }}`) - createdBinding, err := createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) + createdBinding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) Expect(err).ToNot(HaveOccurred()) Expect(isResourceReady(createdBinding)).To(BeTrue()) By("Verify binding secret created") @@ -628,7 +611,7 @@ stringData: newKey: {{ .credentials.secret_key }} tags: {{ .instance.tags }}`) - createdBinding, err := createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) + createdBinding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) Expect(err).ToNot(HaveOccurred()) Expect(isResourceReady(createdBinding)).To(BeTrue()) By("Verify binding secret created") @@ -645,7 +628,9 @@ stringData: kind: Secret metadata: name: my-secret-name`) - createBindingWithError(ctx, bindingName, bindingTestNamespace, instanceName, "", "the Secret template is invalid: Secret's metadata field", secretTemplate) + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, false) + Expect(err).ToNot(HaveOccurred()) + waitForResourceCondition(ctx, binding, common.ConditionFailed, metav1.ConditionTrue, "", "the Secret template is invalid: Secret's metadata field") }) It("should fail to create the secret if wrong template key in the spec.secretTemplate is provided", func() { ctx := context.Background() @@ -655,7 +640,7 @@ stringData: stringData: foo: {{ .non_existing_key }}`) - binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate) + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, false) Expect(err).To(BeNil()) bindingLookupKey := getResourceNamespacedName(binding) Eventually(func() bool { @@ -671,7 +656,9 @@ stringData: secretTemplate := dedent.Dedent(` apiVersion: v1 kind: Pod`) - createBindingWithError(ctx, bindingName, bindingTestNamespace, instanceName, "", "but needs to be of kind 'Secret'", secretTemplate) + binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, false) + Expect(err).ToNot(HaveOccurred()) + waitForResourceCondition(ctx, binding, common.ConditionFailed, metav1.ConditionTrue, "", "but needs to be of kind 'Secret'") }) It("should succeed to create the secret- empty data", func() { ctx := context.Background() @@ -685,7 +672,7 @@ metadata: instance_name: {{ .instance.instance_name }} stringData:`) - createdBinding, err := createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) + createdBinding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) Expect(err).ToNot(HaveOccurred()) Expect(isResourceReady(createdBinding)).To(BeTrue()) By("Verify binding secret created") @@ -718,7 +705,7 @@ metadata: annotations: instance_name: {{ .instance.instance_name }}`) - createdBinding, err := createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) + createdBinding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) Expect(err).ToNot(HaveOccurred()) Expect(isResourceReady(createdBinding)).To(BeTrue()) By("Verify binding secret created") @@ -757,7 +744,7 @@ stringData: newKey: {{ .credentials.auth.basic.password }} tags: {{ .instance.tags }}`) - createdBinding, err := createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) + createdBinding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "", secretTemplate, true) Expect(err).ToNot(HaveOccurred()) Expect(isResourceReady(createdBinding)).To(BeTrue()) By("Verify binding secret created") @@ -782,7 +769,7 @@ metadata: instance_name: {{ .instance.instance_name }} stringData: newKey: {{ .credentials.secret_key }}`) - createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", secretTemplate) + createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", secretTemplate) fakeClient.GetBindingByIDReturns(&smClientTypes.ServiceBinding{ID: fakeBindingID, Credentials: json.RawMessage("{\"secret_key\": \"secret_value\"}")}, nil) Expect(isResourceReady(createdBinding)).To(BeTrue()) }) @@ -920,7 +907,7 @@ stringData: Context("Delete", func() { BeforeEach(func() { - createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") + createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") Expect(isResourceReady(createdBinding)).To(BeTrue()) }) @@ -1000,7 +987,7 @@ stringData: fakeClient.UnbindReturns("", nil) }) It("should succeed", func() { - createdBinding, err := createBindingWithoutAssertions(ctx, bindingName+"-new", bindingTestNamespace, "non-exist-instance", "", "binding-external-name", "") + createdBinding, err := createBindingWithoutAssertions(ctx, bindingName+"-new", bindingTestNamespace, "non-exist-instance", "", "binding-external-name", "", false) Expect(err).ToNot(HaveOccurred()) createdBinding.Finalizers = []string{common.FinalizerName} Expect(k8sClient.Update(ctx, createdBinding)) @@ -1119,7 +1106,7 @@ stringData: When(fmt.Sprintf("last operation is %s %s", testCase.lastOpType, testCase.lastOpState), func() { It("should resync status", func() { var err error - createdBinding, err = createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "fake-binding-external-name", "", false) + createdBinding, err = createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "fake-binding-external-name", "", false) Expect(err).ToNot(HaveOccurred()) smCallArgs := fakeClient.ListBindingsArgsForCall(0) Expect(smCallArgs.LabelQuery).To(HaveLen(1)) @@ -1171,7 +1158,7 @@ stringData: It("should resync successfully", func() { var err error - createdBinding, err = createBindingWithoutAssertionsAndWait(ctx, bindingName, bindingTestNamespace, instanceName, "", "fake-binding-external-name", "", false) + createdBinding, err = createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "fake-binding-external-name", "", false) Expect(err).ToNot(HaveOccurred()) }) }) @@ -1180,7 +1167,7 @@ stringData: Context("Credential Rotation", func() { BeforeEach(func() { fakeClient.RenameBindingReturns(nil, nil) - createdBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") + createdBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "") fakeClient.ListBindingsStub = func(params *sm.Parameters) (*smClientTypes.ServiceBindings, error) { if params == nil || params.FieldQuery == nil || len(params.FieldQuery) == 0 { return nil, nil @@ -1348,7 +1335,7 @@ stringData: } }) It("should succeed", func() { - crossBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, testNamespace, "cross-binding-external-name", "") + crossBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, testNamespace, "cross-binding-external-name", "") By("Verify binding secret created") getSecret(ctx, createdBinding.Spec.SecretName, createdBinding.Namespace, true) @@ -1358,7 +1345,7 @@ stringData: Context("cred rotation", func() { BeforeEach(func() { fakeClient.RenameBindingReturns(nil, nil) - crossBinding = createBinding(ctx, bindingName, bindingTestNamespace, instanceName, testNamespace, "cross-binding-external-name", "") + crossBinding = createAndValidateBinding(ctx, bindingName, bindingTestNamespace, instanceName, testNamespace, "cross-binding-external-name", "") fakeClient.ListBindingsStub = func(params *sm.Parameters) (*smClientTypes.ServiceBindings, error) { if params == nil || params.FieldQuery == nil || len(params.FieldQuery) == 0 { return nil, nil From 72d0e59a5b9f4bb32fbfe98834f7297888b7a454 Mon Sep 17 00:00:00 2001 From: I501080 Date: Thu, 5 Dec 2024 10:20:14 +0200 Subject: [PATCH 27/74] . --- controllers/servicebinding_controller_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/servicebinding_controller_test.go b/controllers/servicebinding_controller_test.go index 4d9baf20..398d8a80 100644 --- a/controllers/servicebinding_controller_test.go +++ b/controllers/servicebinding_controller_test.go @@ -433,7 +433,7 @@ var _ = Describe("ServiceBinding controller", func() { fakeClient.BindReturns(nil, "", getNonTransientBrokerError(errorMessage)) }) - FIt("should detect the error as non-transient and fail", func() { + It("should detect the error as non-transient and fail", func() { binding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", false) Expect(err).ToNot(HaveOccurred()) waitForResourceCondition(ctx, binding, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, errorMessage) From b416137a063768429cd018b0bc3115fb8b15da69 Mon Sep 17 00:00:00 2001 From: I501080 Date: Thu, 5 Dec 2024 10:26:12 +0200 Subject: [PATCH 28/74] fix compile --- api/v1/servicebinding_types_test.go | 12 ------------ api/v1/serviceinstance_types_test.go | 6 ------ 2 files changed, 18 deletions(-) diff --git a/api/v1/servicebinding_types_test.go b/api/v1/servicebinding_types_test.go index c0a91b2f..c0e713f9 100644 --- a/api/v1/servicebinding_types_test.go +++ b/api/v1/servicebinding_types_test.go @@ -66,18 +66,6 @@ var _ = Describe("Service Binding Type Test", func() { Expect(binding.GetControllerName()).To(Equal(common.ServiceBindingController)) }) - It("should update observed generation", func() { - Expect(binding.Status.ObservedGeneration).To(Equal(int64(0))) - binding.SetObservedGeneration(2) - Expect(binding.GetObservedGeneration()).To(Equal(int64(2))) - }) - - It("should update observed generation", func() { - Expect(binding.Status.ObservedGeneration).To(Equal(int64(0))) - binding.SetObservedGeneration(2) - Expect(binding.Status.ObservedGeneration).To(Equal(int64(2))) - }) - It("should update ready", func() { Expect(binding.Status.Ready).To(Equal(metav1.ConditionStatus(""))) binding.SetReady(metav1.ConditionTrue) diff --git a/api/v1/serviceinstance_types_test.go b/api/v1/serviceinstance_types_test.go index 35fa441c..a63ddf1c 100644 --- a/api/v1/serviceinstance_types_test.go +++ b/api/v1/serviceinstance_types_test.go @@ -96,12 +96,6 @@ var _ = Describe("Service Instance Type Test", func() { Expect(instance.GetControllerName()).To(Equal(common.ServiceInstanceController)) }) - It("should update observed generation", func() { - Expect(instance.Status.ObservedGeneration).To(Equal(int64(0))) - instance.SetObservedGeneration(2) - Expect(instance.GetObservedGeneration()).To(Equal(int64(2))) - }) - It("should update ready", func() { Expect(instance.Status.Ready).To(Equal(metav1.ConditionStatus(""))) instance.SetReady(metav1.ConditionTrue) From 343bd9b3c9e55c3947801bb8ebbdb6b2e8c7eb14 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 5 Dec 2024 13:02:45 +0200 Subject: [PATCH 29/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- ...ervices.cloud.sap.com_servicebindings.yaml | 4 - ...rvices.cloud.sap.com_serviceinstances.yaml | 4 - config/rbac/role.yaml | 8 - controllers/secret_controller.go | 6 +- controllers/serviceinstance_controller.go | 2 +- .../serviceinstance_controller_test.go | 194 +++++++++++++----- internal/utils/controller_util.go | 7 + 7 files changed, 153 insertions(+), 72 deletions(-) diff --git a/config/crd/bases/services.cloud.sap.com_servicebindings.yaml b/config/crd/bases/services.cloud.sap.com_servicebindings.yaml index fc82a3d6..7a0fb9eb 100644 --- a/config/crd/bases/services.cloud.sap.com_servicebindings.yaml +++ b/config/crd/bases/services.cloud.sap.com_servicebindings.yaml @@ -259,10 +259,6 @@ spec: description: Indicates when binding secret was rotated format: date-time type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer operationType: description: The operation type (CREATE/UPDATE/DELETE) for ongoing operation diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index 24c671b0..eef2bb72 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -248,10 +248,6 @@ spec: description: The generated ID of the instance, will be automatically filled once the instance is created type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer operationType: description: The operation type (CREATE/UPDATE/DELETE) for ongoing operation diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ee85b1e0..74f1babd 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -26,14 +26,6 @@ rules: - patch - update - watch -- apiGroups: - - services.cloud.sap.com - resources: - - secret - verbs: - - delete - - patch - - update - apiGroups: - services.cloud.sap.com resources: diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index b736cd40..4bac2860 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -50,6 +50,10 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) // on deleted requests. return ctrl.Result{}, client.IgnoreNotFound(err) } + if utils.IsMarkedForDeletion(secret.ObjectMeta) { + err := fmt.Errorf("secret %s is marked for deletion but have instance using it", secret.Name) + return reconcile.Result{}, err + } var instances v1.ServiceInstanceList labelSelector := client.MatchingLabels{common.InstanceSecretLabel + common.Separator + string(secret.GetUID()): secret.Name} if err := r.Client.List(ctx, &instances, labelSelector); err != nil { @@ -102,5 +106,5 @@ func isSecretDataChanged(e event.UpdateEvent) bool { } // Compare the Data field (byte slices) - return !reflect.DeepEqual(oldSecret.Data, newSecret.Data) + return !reflect.DeepEqual(oldSecret.Data, newSecret.Data) || !reflect.DeepEqual(oldSecret.StringData, newSecret.StringData) } diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 750e55f8..35be375e 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -229,7 +229,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient instanceParameters, err := r.buildSMRequestParameters(ctx, serviceInstance) if err != nil { log.Error(err, "failed to parse instance parameters") - return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance) + return utils.MarkAsTransientError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance) } _, operationURL, err := smClient.UpdateInstance(serviceInstance.Status.InstanceID, &smClientTypes.ServiceInstance{ diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 73192ec4..0a0eee75 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -6,6 +6,8 @@ import ( "net/http" "strings" + "sigs.k8s.io/controller-runtime/pkg/client" + authv1 "k8s.io/api/authentication/v1" "github.com/SAP/sap-btp-service-operator/api/common" @@ -83,14 +85,14 @@ var _ = Describe("ServiceInstance controller", func() { }, } - createInstance := func(ctx context.Context, instanceSpec v1.ServiceInstanceSpec, annotations map[string]string, waitForReady bool) *v1.ServiceInstance { + createInstance := func(ctx context.Context, instanceName string, instanceSpec v1.ServiceInstanceSpec, annotations map[string]string, waitForReady bool) *v1.ServiceInstance { instance := &v1.ServiceInstance{ TypeMeta: metav1.TypeMeta{ APIVersion: "services.cloud.sap.com/v1", Kind: "ServiceInstance", }, ObjectMeta: metav1.ObjectMeta{ - Name: fakeInstanceName, + Name: instanceName, Namespace: testNamespace, Annotations: annotations, }, @@ -199,7 +201,7 @@ var _ = Describe("ServiceInstance controller", func() { }) It("provisioning should fail", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForInstanceConditionAndMessage(ctx, defaultLookupKey, common.ConditionSucceeded, "provided plan id does not match") }) }) @@ -209,7 +211,7 @@ var _ = Describe("ServiceInstance controller", func() { Context("Sync", func() { When("provision request to SM succeeds", func() { It("should provision instance of the provided offering and plan name successfully", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) Expect(serviceInstance.Status.InstanceID).To(Equal(fakeInstanceID)) Expect(serviceInstance.Status.SubaccountID).To(Equal(fakeSubaccountID)) Expect(serviceInstance.Spec.ExternalName).To(Equal(fakeInstanceExternalName)) @@ -223,30 +225,6 @@ var _ = Describe("ServiceInstance controller", func() { Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) }) }) - When("secret updated", func() { - It("should provision instance of the provided offering and plan name successfully", func() { - instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) - serviceInstance = createInstance(ctx, instanceSpec, nil, true) - smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) - params := smInstance.Parameters - Expect(params).To(ContainSubstring("\"key\":\"value\"")) - Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) - - Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) - credentialsMap := make(map[string][]byte) - credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") - paramsSecret.Data = credentialsMap - Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) - Eventually(func() bool { - return fakeClient.UpdateInstanceCallCount() == 1 - }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") - - _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) - params = smInstance.Parameters - Expect(params).To(ContainSubstring("\"key\":\"value\"")) - Expect(params).To(ContainSubstring("\"secret-key\":\"new-secret-value\"")) - }) - }) When("provision request to SM fails", func() { errMessage := "failed to provision instance" @@ -260,7 +238,7 @@ var _ = Describe("ServiceInstance controller", func() { }) It("should have failure condition", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Created, "") }) }) @@ -273,7 +251,7 @@ var _ = Describe("ServiceInstance controller", func() { Description: errMessage, }) fakeClient.ProvisionReturnsOnCall(1, &sm.ProvisionResponse{InstanceID: fakeInstanceID}, nil) - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Created, "") }) It("provision fails until timeout", func() { @@ -281,7 +259,7 @@ var _ = Describe("ServiceInstance controller", func() { StatusCode: http.StatusBadRequest, Description: errMessage, }) - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, errMessage) }) }) @@ -296,7 +274,7 @@ var _ = Describe("ServiceInstance controller", func() { }) It("should retry until success", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Created, "") }) }) @@ -308,7 +286,7 @@ var _ = Describe("ServiceInstance controller", func() { }) It("should be transient error and eventually succeed", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, tooManyRequestsError) fakeClient.ProvisionReturns(&sm.ProvisionResponse{InstanceID: fakeInstanceID}, nil) waitForResourceToBeReady(ctx, serviceInstance) @@ -321,7 +299,7 @@ var _ = Describe("ServiceInstance controller", func() { fakeClient.ProvisionReturnsOnCall(2, &sm.ProvisionResponse{InstanceID: fakeInstanceID}, nil) }) It("should fail first and then succeed", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Created, "") Expect(fakeClient.ProvisionCallCount()).To(BeNumerically(">", 1)) }) @@ -344,7 +322,7 @@ var _ = Describe("ServiceInstance controller", func() { fakeClient.GetInstanceByIDReturns(&smclientTypes.ServiceInstance{Labels: map[string][]string{"subaccount_id": {fakeSubaccountID}}}, nil) }) It("should update in progress condition and provision the instance successfully", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) fakeClient.StatusReturns(&smclientTypes.Operation{ ID: "1234", Type: smClientTypes.CREATE, @@ -357,7 +335,7 @@ var _ = Describe("ServiceInstance controller", func() { When("polling ends with failure", func() { It("should update to failure condition with the broker err description", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) fakeClient.StatusReturns(&smclientTypes.Operation{ ID: "1234", Type: smClientTypes.CREATE, @@ -370,7 +348,7 @@ var _ = Describe("ServiceInstance controller", func() { When("updating during create", func() { It("should update the instance after created successfully", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, "") newName := "new-name" + uuid.New().String() @@ -398,7 +376,7 @@ var _ = Describe("ServiceInstance controller", func() { When("deleting while create is in progress", func() { It("should be deleted successfully", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) By("waiting for instance to be CreateInProgress") waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, "") @@ -430,7 +408,7 @@ var _ = Describe("ServiceInstance controller", func() { ServicePlanName: "a-plan-name", ServiceOfferingName: "an-offering-name", } - serviceInstance = createInstance(ctx, withoutExternal, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, withoutExternal, nil, true) Expect(serviceInstance.Status.InstanceID).To(Equal(fakeInstanceID)) Expect(serviceInstance.Spec.ExternalName).To(Equal(fakeInstanceName)) Expect(serviceInstance.Name).To(Equal(fakeInstanceName)) @@ -440,7 +418,7 @@ var _ = Describe("ServiceInstance controller", func() { Describe("Update", func() { BeforeEach(func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) Expect(serviceInstance.Spec.ExternalName).To(Equal(fakeInstanceExternalName)) }) @@ -629,7 +607,7 @@ var _ = Describe("ServiceInstance controller", func() { When("subaccount id changed", func() { It("should fail", func() { deleteInstance(ctx, serviceInstance, true) - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) serviceInstance.Spec.BTPAccessCredentialsSecret = "12345" err := k8sClient.Update(ctx, serviceInstance) Expect(err).To(HaveOccurred()) @@ -652,7 +630,7 @@ var _ = Describe("ServiceInstance controller", func() { Describe("Delete", func() { BeforeEach(func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) fakeClient.DeprovisionReturns("", nil) }) AfterEach(func() { @@ -801,7 +779,7 @@ var _ = Describe("ServiceInstance controller", func() { Describe("full reconcile", func() { When("instance hashedSpec is not initialized", func() { BeforeEach(func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) }) It("should not send update request and update the hashed spec", func() { hashed := serviceInstance.Status.HashedSpec @@ -832,7 +810,7 @@ var _ = Describe("ServiceInstance controller", func() { }) It("should call correctly to SM and recover the instance", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) Expect(fakeClient.ProvisionCallCount()).To(Equal(0)) Expect(serviceInstance.Status.InstanceID).To(Equal(fakeInstanceID)) smCallArgs := fakeClient.ListInstancesArgsForCall(0) @@ -855,7 +833,7 @@ var _ = Describe("ServiceInstance controller", func() { }) It("should recover the existing instance and poll until instance is ready", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) key := getResourceNamespacedName(serviceInstance) Eventually(func() bool { _ = k8sClient.Get(ctx, key, serviceInstance) @@ -876,7 +854,7 @@ var _ = Describe("ServiceInstance controller", func() { }) It("should recover the existing instance and update condition failure", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateFailed, "") Expect(serviceInstance.Status.InstanceID).To(Equal(fakeInstanceID)) Expect(fakeClient.ProvisionCallCount()).To(Equal(0)) @@ -894,7 +872,7 @@ var _ = Describe("ServiceInstance controller", func() { recoveredInstance.Ready = true }) It("should recover the instance with status Ready=true", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceToBeReady(ctx, serviceInstance) Expect(fakeClient.ProvisionCallCount()).To(Equal(0)) Expect(serviceInstance.Status.InstanceID).To(Equal(fakeInstanceID)) @@ -905,7 +883,7 @@ var _ = Describe("ServiceInstance controller", func() { recoveredInstance.Ready = false }) It("should recover the instance with status Ready=false", func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionFailed, metav1.ConditionTrue, common.CreateFailed, "") Expect(fakeClient.ProvisionCallCount()).To(Equal(0)) Expect(serviceInstance.Status.InstanceID).To(Equal(fakeInstanceID)) @@ -922,14 +900,14 @@ var _ = Describe("ServiceInstance controller", func() { When("creating instance with shared=true", func() { It("should succeed to provision and sharing the instance", func() { fakeClient.ShareInstanceReturns(nil) - serviceInstance = createInstance(ctx, sharedInstanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, sharedInstanceSpec, nil, true) waitForInstanceToBeShared(ctx, serviceInstance) }) }) Context("sharing an existing instance", func() { BeforeEach(func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) }) When("updating existing instance to shared", func() { @@ -970,7 +948,7 @@ var _ = Describe("ServiceInstance controller", func() { StatusCode: http.StatusBadRequest, Description: "errMessage", }) - serviceInstance = createInstance(ctx, sharedInstanceSpec, nil, false) + serviceInstance = createInstance(ctx, fakeInstanceName, sharedInstanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, "") Expect(fakeClient.ShareInstanceCallCount()).To(BeZero()) }) @@ -978,7 +956,7 @@ var _ = Describe("ServiceInstance controller", func() { When("instance is valid and share failed", func() { BeforeEach(func() { - serviceInstance = createInstance(ctx, instanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) }) When("shared failed with rate limit error", func() { @@ -1040,7 +1018,7 @@ var _ = Describe("ServiceInstance controller", func() { Context("un-sharing an existing shared instance", func() { BeforeEach(func() { fakeClient.ShareInstanceReturns(nil) - serviceInstance = createInstance(ctx, sharedInstanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, sharedInstanceSpec, nil, true) waitForInstanceToBeShared(ctx, serviceInstance) }) @@ -1079,7 +1057,7 @@ var _ = Describe("ServiceInstance controller", func() { When("instance is valid and un-share failed", func() { BeforeEach(func() { fakeClient.ShareInstanceReturns(nil) - serviceInstance = createInstance(ctx, sharedInstanceSpec, nil, true) + serviceInstance = createInstance(ctx, fakeInstanceName, sharedInstanceSpec, nil, true) waitForInstanceToBeShared(ctx, serviceInstance) fakeClient.UnShareInstanceReturns(&sm.ServiceManagerError{ StatusCode: http.StatusBadRequest, @@ -1295,6 +1273,91 @@ var _ = Describe("ServiceInstance controller", func() { }) }) }) + + Context("secret watcher", func() { + When("secret updated", func() { + anotherInstanceName := "instance2" + var anotherInstance *v1.ServiceInstance + BeforeEach(func() { + instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) + }) + AfterEach(func() { + instanceSpec.SubscribeToSecretChanges = pointer.Bool(false) + if anotherInstance != nil { + deleteAndWait(ctx, anotherInstance) + } + }) + It("should update instance with the secret change", func() { + instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) + smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) + params := smInstance.Parameters + Expect(params).To(ContainSubstring("\"key\":\"value\"")) + Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) + + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") + paramsSecret.Data = credentialsMap + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + Eventually(func() bool { + return fakeClient.UpdateInstanceCallCount() == 1 + }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) + params = smInstance.Parameters + Expect(params).To(ContainSubstring("\"key\":\"value\"")) + Expect(params).To(ContainSubstring("\"secret-key\":\"new-secret-value\"")) + deleteAndWait(ctx, serviceInstance) + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) + }) + It("should update two instances with the secret change", func() { + instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) + smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) + + anotherInstance = createInstance(ctx, anotherInstanceName, instanceSpec, nil, true) + smInstance, _, _, _, _, _ = fakeClient.ProvisionArgsForCall(1) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) + + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance, anotherInstance}) + + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") + paramsSecret.Data = credentialsMap + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + Eventually(func() bool { + return fakeClient.UpdateInstanceCallCount() == 2 + }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) + + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(1) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) + + deleteAndWait(ctx, anotherInstance) + + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + }) + It("should prevent delete of secret when secret is watched", func() { + + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) + Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) + Expect(k8sClient.Delete(ctx, paramsSecret)).To(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: paramsSecret.Name, Namespace: paramsSecret.Namespace}, paramsSecret) + if err != nil { + return false + } + return len(paramsSecret.Finalizers) == 1 + }, timeout, interval).Should(BeTrue()) + + }) + }) + }) }) func waitForInstanceConditionAndMessage(ctx context.Context, key types.NamespacedName, conditionType, msg string) { @@ -1356,3 +1419,26 @@ func updateInstanceStatus(ctx context.Context, instance *v1.ServiceInstance) *v1 }, timeout, interval).Should(BeTrue()) return si } + +func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Client, paramsSecret *corev1.Secret, instances []*v1.ServiceInstance) { + Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) + + if len(instances) == 0 { + Expect(len(paramsSecret.Finalizers)).To(Equal(0)) + Expect(len(paramsSecret.Labels)).To(Equal(0)) + Expect(len(paramsSecret.Annotations)).To(Equal(0)) + return + } + Expect(paramsSecret.Finalizers[0]).To(Equal(common.FinalizerName)) + Expect(paramsSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) + Expect(len(paramsSecret.Annotations)).To(Equal(len(instances))) + for _, instance := range instances { + Expect(paramsSecret.Annotations[common.WatchSecretLabel+common.Separator+instance.Name]).To(Equal("true")) + Expect(instance.Labels[common.InstanceSecretLabel+common.Separator+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) + } +} +func checkParams(params string, substrings []string) { + for _, substring := range substrings { + Expect(params).To(ContainSubstring(substring)) + } +} diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index a2e2dd4f..fa3c2ac9 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -256,6 +256,10 @@ func AddSecretHaveWatch(ctx context.Context, secret *v12.Secret, k8sClient clien secret.Labels = make(map[string]string) } secret.Labels[common.WatchSecretLabel] = "true" + if !controllerutil.ContainsFinalizer(secret, common.FinalizerName) { + controllerutil.AddFinalizer(secret, common.FinalizerName) + } + if _, exists := secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName]; !exists { secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName] = "true" if err := k8sClient.Update(ctx, secret); err != nil { @@ -279,6 +283,9 @@ func RemoveSecretWatch(ctx context.Context, k8sClient client.Client, namespace s delete(secret.Annotations, common.WatchSecretLabel+common.Separator+instanceName) if len(secret.Annotations) == 0 { delete(secret.Labels, common.WatchSecretLabel) + if controllerutil.ContainsFinalizer(secret, common.FinalizerName) { + controllerutil.RemoveFinalizer(secret, common.FinalizerName) + } } if err := k8sClient.Update(ctx, secret); err != nil { return err From 7953510973ce808029776745c09e0a8cab93ae2a Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 5 Dec 2024 13:47:50 +0200 Subject: [PATCH 30/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/common/common.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/common/common.go b/api/common/common.go index 9ec4941c..a0ecf534 100644 --- a/api/common/common.go +++ b/api/common/common.go @@ -2,6 +2,7 @@ package common import ( "fmt" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" From ef63f3e9eb390862169bfa40f9960e02110ddc4e Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 5 Dec 2024 13:56:08 +0200 Subject: [PATCH 31/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- config/samples/services_v1_serviceinstance.yaml | 7 ++----- controllers/serviceinstance_controller_test.go | 8 ++------ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/config/samples/services_v1_serviceinstance.yaml b/config/samples/services_v1_serviceinstance.yaml index 6ec8e64b..cfe841a8 100644 --- a/config/samples/services_v1_serviceinstance.yaml +++ b/config/samples/services_v1_serviceinstance.yaml @@ -2,17 +2,14 @@ apiVersion: services.cloud.sap.com/v1 kind: ServiceInstance metadata: name: sample-instance-1 - labels: - "services.cloud.sap.com/secretKeyRef-my-secret": "true" - "services.cloud.sap.com/secretKeyRef-my-secret1": "true" spec: serviceOfferingName: service-manager servicePlanName: subaccount-audit parametersFrom: - secretKeyRef: name: my-secret - key: secret-paramete + key: secret-parameter - secretKeyRef: name: my-secret1 - key: secret-paramete + key: secret-parameter diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 0a0eee75..ddf497af 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1291,9 +1291,7 @@ var _ = Describe("ServiceInstance controller", func() { instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) - params := smInstance.Parameters - Expect(params).To(ContainSubstring("\"key\":\"value\"")) - Expect(params).To(ContainSubstring("\"secret-key\":\"secret-value\"")) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) @@ -1306,9 +1304,7 @@ var _ = Describe("ServiceInstance controller", func() { }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) - params = smInstance.Parameters - Expect(params).To(ContainSubstring("\"key\":\"value\"")) - Expect(params).To(ContainSubstring("\"secret-key\":\"new-secret-value\"")) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) deleteAndWait(ctx, serviceInstance) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) }) From eb8cdc54e77dacc7691bbaa82fe823eafbb0d5b4 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 5 Dec 2024 14:28:28 +0200 Subject: [PATCH 32/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- .../serviceinstance_controller_test.go | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index ddf497af..7af6ef5f 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1275,7 +1275,7 @@ var _ = Describe("ServiceInstance controller", func() { }) Context("secret watcher", func() { - When("secret updated", func() { + When("secret updated and instance watch secret", func() { anotherInstanceName := "instance2" var anotherInstance *v1.ServiceInstance BeforeEach(func() { @@ -1288,7 +1288,6 @@ var _ = Describe("ServiceInstance controller", func() { } }) It("should update instance with the secret change", func() { - instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) @@ -1309,7 +1308,6 @@ var _ = Describe("ServiceInstance controller", func() { checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) }) It("should update two instances with the secret change", func() { - instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) @@ -1353,6 +1351,22 @@ var _ = Describe("ServiceInstance controller", func() { }) }) + When("secret updated and instance don't watch secret", func() { + It("should update instance with the secret change", func() { + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) + smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) + + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) + + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") + paramsSecret.Data = credentialsMap + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(0)) + }) + }) + }) }) @@ -1433,6 +1447,7 @@ func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Clien Expect(instance.Labels[common.InstanceSecretLabel+common.Separator+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) } } + func checkParams(params string, substrings []string) { for _, substring := range substrings { Expect(params).To(ContainSubstring(substring)) From fad73eefaaab62bcc06a5b8365a16bdb3fbe2bdd Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 5 Dec 2024 18:18:19 +0200 Subject: [PATCH 33/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- .../serviceinstance_controller_test.go | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 7af6ef5f..5b7ef809 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1307,6 +1307,48 @@ var _ = Describe("ServiceInstance controller", func() { deleteAndWait(ctx, serviceInstance) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) }) + It("create instance before secret", func() { + newInstanceSpec := v1.ServiceInstanceSpec{ + ExternalName: fakeInstanceExternalName, + ServicePlanName: fakePlanName, + ServiceOfferingName: fakeOfferingName, + Parameters: &runtime.RawExtension{ + Raw: []byte(`{"key": "value"}`), + }, + ParametersFrom: []v1.ParametersFromSource{ + { + SecretKeyRef: &v1.SecretKeyReference{ + Name: "instance-params-secret-new", + Key: "secret-parameter", + }, + }, + }, + SubscribeToSecretChanges: pointer.Bool(true), + } + serviceInstance = createInstance(ctx, anotherInstanceName, newInstanceSpec, nil, false) + Expect(fakeClient.ProvisionCallCount()).To(Equal(0)) + + paramsSecret = createParamsSecret(ctx, "instance-params-secret-new", testNamespace) + waitForResourceToBeReady(ctx, serviceInstance) + smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) + + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") + paramsSecret.Data = credentialsMap + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + Eventually(func() bool { + return fakeClient.UpdateInstanceCallCount() == 1 + }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) + deleteAndWait(ctx, serviceInstance) + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) + }) + It("should update two instances with the secret change", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) From 0bb2585757b0bab3402e699018d6bf9954d71d63 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 5 Dec 2024 18:33:36 +0200 Subject: [PATCH 34/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 5b7ef809..d8a6e1bb 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1278,6 +1278,7 @@ var _ = Describe("ServiceInstance controller", func() { When("secret updated and instance watch secret", func() { anotherInstanceName := "instance2" var anotherInstance *v1.ServiceInstance + var anotherSecret *corev1.Secret BeforeEach(func() { instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) }) @@ -1286,6 +1287,9 @@ var _ = Describe("ServiceInstance controller", func() { if anotherInstance != nil { deleteAndWait(ctx, anotherInstance) } + if anotherSecret != nil { + deleteAndWait(ctx, anotherSecret) + } }) It("should update instance with the secret change", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) @@ -1328,17 +1332,17 @@ var _ = Describe("ServiceInstance controller", func() { serviceInstance = createInstance(ctx, anotherInstanceName, newInstanceSpec, nil, false) Expect(fakeClient.ProvisionCallCount()).To(Equal(0)) - paramsSecret = createParamsSecret(ctx, "instance-params-secret-new", testNamespace) + anotherSecret = createParamsSecret(ctx, "instance-params-secret-new", testNamespace) waitForResourceToBeReady(ctx, serviceInstance) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) - checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{serviceInstance}) credentialsMap := make(map[string][]byte) credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") - paramsSecret.Data = credentialsMap - Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + anotherSecret.Data = credentialsMap + Expect(k8sClient.Update(ctx, anotherSecret)).To(Succeed()) Eventually(func() bool { return fakeClient.UpdateInstanceCallCount() == 1 }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") @@ -1346,7 +1350,7 @@ var _ = Describe("ServiceInstance controller", func() { _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) deleteAndWait(ctx, serviceInstance) - checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) + checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{}) }) It("should update two instances with the secret change", func() { From ee6c04f29e40be0c8688e7610e5323f17f6e919e Mon Sep 17 00:00:00 2001 From: I501080 Date: Sun, 8 Dec 2024 09:28:05 +0200 Subject: [PATCH 35/74] . --- controllers/serviceinstance_controller.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 35be375e..81b1c5f0 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -83,6 +83,9 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } serviceInstance = serviceInstance.DeepCopy() + if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) { + return r.deleteInstance(ctx, serviceInstance) + } if len(serviceInstance.GetConditions()) == 0 { err := utils.InitConditions(ctx, r.Client, serviceInstance) if err != nil { @@ -102,10 +105,6 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, nil } - if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) { - return r.deleteInstance(ctx, serviceInstance) - } - if len(serviceInstance.Status.OperationURL) > 0 { // ongoing operation - poll status from SM return r.poll(ctx, serviceInstance) @@ -264,6 +263,7 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) { log := utils.GetLogger(ctx) + log.Info("deleting instance") if controllerutil.ContainsFinalizer(serviceInstance, common.FinalizerName) { smClient, err := r.GetSMClient(ctx, serviceInstance) if err != nil { @@ -625,10 +625,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool { log := utils.GetLogger(ctx) - if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) { - log.Info("instance is not in final state, it is marked for deletion") - return false - } + if len(serviceInstance.Status.OperationURL) > 0 { log.Info(fmt.Sprintf("instance is not in final state, async operation is in progress (%s)", serviceInstance.Status.OperationURL)) return false @@ -652,7 +649,7 @@ func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool } return false } - if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges && serviceInstance.Status.ForceReconcile { + if serviceInstance.Status.ForceReconcile { log.Info("instance is not in final state, SubscribeToSecretChanges is true") return false } @@ -670,7 +667,7 @@ func updateRequired(serviceInstance *v1.ServiceInstance) bool { if cond != nil && cond.Reason == common.UpdateInProgress { //in case of transient error occurred return true } - if serviceInstance.Spec.SubscribeToSecretChanges != nil && *serviceInstance.Spec.SubscribeToSecretChanges && serviceInstance.Status.ForceReconcile { + if serviceInstance.Status.ForceReconcile { return true } From 62240debea54f856a7e701becee9fbb82ed5f386 Mon Sep 17 00:00:00 2001 From: I501080 Date: Sun, 8 Dec 2024 10:40:34 +0200 Subject: [PATCH 36/74] add annotation to a secret created from template --- controllers/servicebinding_controller.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/controllers/servicebinding_controller.go b/controllers/servicebinding_controller.go index 10829684..fa372197 100644 --- a/controllers/servicebinding_controller.go +++ b/controllers/servicebinding_controller.go @@ -615,6 +615,11 @@ func (r *ServiceBindingReconciler) storeBindingSecret(ctx context.Context, k8sBi } secret.Labels[common.ManagedByBTPOperatorLabel] = "true" + if secret.Annotations == nil { + secret.Annotations = map[string]string{} + } + secret.Annotations["binding"] = k8sBinding.Name + return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret) } From b1e853cb613e38fd0a419d226cc84a49db80efce Mon Sep 17 00:00:00 2001 From: i065450 Date: Sun, 8 Dec 2024 16:10:50 +0200 Subject: [PATCH 37/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/v1/serviceinstance_types.go | 2 +- api/v1/serviceinstance_types_test.go | 5 ++++ controllers/serviceinstance_controller.go | 2 +- .../serviceinstance_controller_test.go | 24 ++++++++++++++++++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 4a0154ab..25b8d6bc 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -207,6 +207,6 @@ func (si *ServiceInstance) ShouldBeShared() bool { return si.Spec.Shared != nil && *si.Spec.Shared } -func (si *ServiceInstance) IsSubscribedToSecretKeyRefChange() bool { +func (si *ServiceInstance) IsSubscribedToSecretChange() bool { return si.Spec.SubscribeToSecretChanges != nil && *si.Spec.SubscribeToSecretChanges } diff --git a/api/v1/serviceinstance_types_test.go b/api/v1/serviceinstance_types_test.go index a63ddf1c..12735357 100644 --- a/api/v1/serviceinstance_types_test.go +++ b/api/v1/serviceinstance_types_test.go @@ -123,4 +123,9 @@ var _ = Describe("Service Instance Type Test", func() { instance.SetAnnotations(annotation) Expect(instance.GetAnnotations()).To(Equal(annotation)) }) + + It("should update SubscribeToSecretChanges", func() { + instance.Spec.SubscribeToSecretChanges = &[]bool{true}[0] + Expect(instance.IsSubscribedToSecretChange()).To(BeTrue()) + }) }) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 81b1c5f0..f24e7859 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -553,7 +553,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context return nil, err } shouldUpdate := false - if serviceInstance.IsSubscribedToSecretKeyRefChange() { + if serviceInstance.IsSubscribedToSecretChange() { existingSecrets := make(map[string]string) if serviceInstance.Labels == nil { serviceInstance.Labels = make(map[string]string) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index d8a6e1bb..47c36782 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1398,7 +1398,10 @@ var _ = Describe("ServiceInstance controller", func() { }) }) When("secret updated and instance don't watch secret", func() { - It("should update instance with the secret change", func() { + AfterEach(func() { + instanceSpec.SubscribeToSecretChanges = pointer.Bool(false) + }) + It("should not update instance with the secret change", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) @@ -1411,6 +1414,25 @@ var _ = Describe("ServiceInstance controller", func() { Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(0)) }) + It("should not update instance with the secret change after removing SubscribeToSecretChanges", func() { + instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) + smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) + + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + + serviceInstance.Spec.SubscribeToSecretChanges = pointer.Bool(false) + updateInstance(ctx, serviceInstance) + waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Updated, "") + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) + + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") + paramsSecret.Data = credentialsMap + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(1)) + }) }) }) From 4a6f2878eaad75d2ae97daaa5a1cc3288dfc5431 Mon Sep 17 00:00:00 2001 From: i065450 Date: Sun, 8 Dec 2024 17:03:06 +0200 Subject: [PATCH 38/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/v1/suite_test.go | 1 + controllers/secret_controller.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/v1/suite_test.go b/api/v1/suite_test.go index 39dfb954..7fa64993 100644 --- a/api/v1/suite_test.go +++ b/api/v1/suite_test.go @@ -77,6 +77,7 @@ func getInstance() *ServiceInstance { }, }, }, + SubscribeToSecretChanges: &[]bool{true}[0], UserInfo: &v1.UserInfo{ Username: "test-user", Groups: []string{"test-group"}, diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index 4bac2860..bd78aa4c 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -43,7 +43,7 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) var secret corev1.Secret if err := r.Get(ctx, req.NamespacedName, &secret); err != nil { if !apierrors.IsNotFound(err) { - log.Error(err, "unable to fetch ServiceInstance") + log.Error(err, "unable to fetch Secret") } // we'll ignore not-found errors, since they can't be fixed by an immediate // requeue (we'll need to wait for a new notification), and we can get them From 4da933c0fe9eab2c0cb37d975ff532b5ed179b11 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 9 Dec 2024 12:10:54 +0200 Subject: [PATCH 39/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/common/common.go | 1 + controllers/secret_controller.go | 31 +++++++++++++------ controllers/servicebinding_controller.go | 4 +-- controllers/serviceinstance_controller.go | 6 ++-- .../serviceinstance_controller_test.go | 17 +++------- internal/utils/controller_util.go | 6 ++-- internal/utils/parameters.go | 3 ++ 7 files changed, 39 insertions(+), 29 deletions(-) diff --git a/api/common/common.go b/api/common/common.go index a0ecf534..708fa219 100644 --- a/api/common/common.go +++ b/api/common/common.go @@ -15,6 +15,7 @@ type ControllerName string const ( ServiceInstanceController ControllerName = "ServiceInstance" ServiceBindingController ControllerName = "ServiceBinding" + SecretController ControllerName = "Secret" FinalizerName string = "services.cloud.sap.com/sap-btp-finalizer" StaleBindingIDLabel string = "services.cloud.sap.com/stale" StaleBindingRotationOfLabel string = "services.cloud.sap.com/rotationOf" diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index bd78aa4c..f7ca4f30 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -5,6 +5,8 @@ import ( "fmt" "reflect" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -29,8 +31,6 @@ type SecretReconciler struct { Log logr.Logger } -// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=servicebindings,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=servicebindings/status,verbs=get;update;patch // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;create;update @@ -40,8 +40,8 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) ctx = context.WithValue(ctx, utils.LogKey{}, log) log.Info(fmt.Sprintf("reconciling secret %s", req.NamespacedName)) // Fetch the Secret - var secret corev1.Secret - if err := r.Get(ctx, req.NamespacedName, &secret); err != nil { + secret := &corev1.Secret{} + if err := r.Get(ctx, req.NamespacedName, secret); err != nil { if !apierrors.IsNotFound(err) { log.Error(err, "unable to fetch Secret") } @@ -50,10 +50,7 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) // on deleted requests. return ctrl.Result{}, client.IgnoreNotFound(err) } - if utils.IsMarkedForDeletion(secret.ObjectMeta) { - err := fmt.Errorf("secret %s is marked for deletion but have instance using it", secret.Name) - return reconcile.Result{}, err - } + var instances v1.ServiceInstanceList labelSelector := client.MatchingLabels{common.InstanceSecretLabel + common.Separator + string(secret.GetUID()): secret.Name} if err := r.Client.List(ctx, &instances, labelSelector); err != nil { @@ -68,6 +65,9 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) return reconcile.Result{}, err } } + if utils.IsMarkedForDeletion(secret.ObjectMeta) { + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, secret, common.FinalizerName, common.SecretController) + } return reconcile.Result{}, nil } @@ -76,7 +76,7 @@ func (r *SecretReconciler) SetupWithManager(mgr ctrl.Manager) error { labelSelector := labels.SelectorFromSet(map[string]string{common.WatchSecretLabel: "true"}) labelPredicate := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { - return labelSelector.Matches(labels.Set(e.ObjectNew.GetLabels())) && isSecretDataChanged(e) + return labelSelector.Matches(labels.Set(e.ObjectNew.GetLabels())) && (isSecretDataChanged(e) || isSecretInDelete(e)) }, CreateFunc: func(e event.CreateEvent) bool { return false @@ -108,3 +108,16 @@ func isSecretDataChanged(e event.UpdateEvent) bool { // Compare the Data field (byte slices) return !reflect.DeepEqual(oldSecret.Data, newSecret.Data) || !reflect.DeepEqual(oldSecret.StringData, newSecret.StringData) } + +func isSecretInDelete(e event.UpdateEvent) bool { + // Type assert to *v1.Secret + + newSecret, okNew := e.ObjectNew.(*corev1.Secret) + if !okNew { + // If the objects are not Secrets, skip the event + return false + } + + // Compare the Data field (byte slices) + return !newSecret.GetDeletionTimestamp().IsZero() && controllerutil.ContainsFinalizer(newSecret, common.FinalizerName) +} diff --git a/controllers/servicebinding_controller.go b/controllers/servicebinding_controller.go index fa372197..7c954946 100644 --- a/controllers/servicebinding_controller.go +++ b/controllers/servicebinding_controller.go @@ -345,7 +345,7 @@ func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v } log.Info("Binding does not exists in SM, removing finalizer") - if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName); err != nil { + if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName, serviceBinding.GetControllerName()); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -807,7 +807,7 @@ func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Co return ctrl.Result{}, err } - return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName) + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName, serviceBinding.GetControllerName()) } func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) { diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index f24e7859..35121184 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -283,7 +283,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } log.Info("instance does not exists in SM, removing finalizer") - return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName) + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()) } if len(serviceInstance.Status.OperationURL) > 0 && serviceInstance.Status.OperationType == smClientTypes.DELETE { @@ -313,7 +313,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI } log.Info("Instance was deleted successfully, removing finalizer") // remove our finalizer from the list and update it. - return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName) + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()) } return ctrl.Result{}, nil } @@ -419,7 +419,7 @@ func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *v serviceInstance.Status.Ready = metav1.ConditionTrue } else if serviceInstance.Status.OperationType == smClientTypes.DELETE { // delete was successful - remove our finalizer from the list and update it. - if err := utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName); err != nil { + if err := utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()); err != nil { return ctrl.Result{}, err } } diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 47c36782..6c0f5c35 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1330,6 +1330,7 @@ var _ = Describe("ServiceInstance controller", func() { SubscribeToSecretChanges: pointer.Bool(true), } serviceInstance = createInstance(ctx, anotherInstanceName, newInstanceSpec, nil, false) + waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, "secrets \"instance-params-secret-new\" not found") Expect(fakeClient.ProvisionCallCount()).To(Equal(0)) anotherSecret = createParamsSecret(ctx, "instance-params-secret-new", testNamespace) @@ -1352,7 +1353,6 @@ var _ = Describe("ServiceInstance controller", func() { deleteAndWait(ctx, serviceInstance) checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{}) }) - It("should update two instances with the secret change", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) @@ -1382,19 +1382,13 @@ var _ = Describe("ServiceInstance controller", func() { checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) }) - It("should prevent delete of secret when secret is watched", func() { - + It("delete of secret when secret is watched", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) - Expect(k8sClient.Delete(ctx, paramsSecret)).To(Succeed()) - Eventually(func() bool { - err := k8sClient.Get(ctx, types.NamespacedName{Name: paramsSecret.Name, Namespace: paramsSecret.Namespace}, paramsSecret) - if err != nil { - return false - } - return len(paramsSecret.Finalizers) == 1 - }, timeout, interval).Should(BeTrue()) + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + deleteAndWait(ctx, paramsSecret) + waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.UpdateInProgress, "secrets \"instance-params-secret\" not found") }) }) When("secret updated and instance don't watch secret", func() { @@ -1419,7 +1413,6 @@ var _ = Describe("ServiceInstance controller", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) - checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) serviceInstance.Spec.SubscribeToSecretChanges = pointer.Bool(false) diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index fa3c2ac9..fd97dca5 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -44,7 +44,7 @@ type format string type LogKey struct { } -func RemoveFinalizer(ctx context.Context, k8sClient client.Client, object common.SAPBTPResource, finalizerName string) error { +func RemoveFinalizer(ctx context.Context, k8sClient client.Client, object client.Object, finalizerName string, controllerName common.ControllerName) error { log := GetLogger(ctx) if controllerutil.ContainsFinalizer(object, finalizerName) { log.Info(fmt.Sprintf("removing finalizer %s", finalizerName)) @@ -58,7 +58,7 @@ func RemoveFinalizer(ctx context.Context, k8sClient client.Client, object common return fmt.Errorf("failed to remove the finalizer '%s'. Error: %v", finalizerName, err) } } - log.Info(fmt.Sprintf("removed finalizer %s from %s", finalizerName, object.GetControllerName())) + log.Info(fmt.Sprintf("removed finalizer %s from %s", finalizerName, controllerName)) return nil } return nil @@ -274,7 +274,7 @@ func RemoveSecretWatch(ctx context.Context, k8sClient client.Client, namespace s secret := &v12.Secret{} err := k8sClient.Get(ctx, apimachinerytypes.NamespacedName{Name: name, Namespace: namespace}, secret) if err != nil { - return err + return client.IgnoreNotFound(err) } if secret.Annotations == nil { return nil diff --git a/internal/utils/parameters.go b/internal/utils/parameters.go index 88de29c1..bbce343e 100644 --- a/internal/utils/parameters.go +++ b/internal/utils/parameters.go @@ -115,6 +115,9 @@ func fetchParametersFromSource(namespace string, parametersFrom *servicesv1.Para if err != nil { return nil, nil, err } + if secret.DeletionTimestamp != nil { + return nil, nil, fmt.Errorf("secret %s is marked for deletion", secret.Name) + } p, err := unmarshalJSON(data) if err != nil { return nil, nil, err From 5aa3abbf4ed1716a17dff89a2db952b4ba1d6db8 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 9 Dec 2024 16:31:51 +0200 Subject: [PATCH 40/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- .../serviceinstance_controller_test.go | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 6c0f5c35..7c88ca28 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1353,6 +1353,31 @@ var _ = Describe("ServiceInstance controller", func() { deleteAndWait(ctx, serviceInstance) checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{}) }) + It("update instance parameterFrom", func() { + + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) + smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) + + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + + anotherSecret = createParamsSecret(ctx, "instance-params-secret-new", testNamespace) + + serviceInstance.Spec.ParametersFrom = []v1.ParametersFromSource{ + { + SecretKeyRef: &v1.SecretKeyReference{ + Name: "instance-params-secret-new", + Key: "secret-parameter", + }, + }, + } + serviceInstance = updateInstance(ctx, serviceInstance) + waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Updated, "") + + checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{serviceInstance}) + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) + // Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(1)) + }) It("should update two instances with the secret change", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) @@ -1424,7 +1449,7 @@ var _ = Describe("ServiceInstance controller", func() { credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") paramsSecret.Data = credentialsMap Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) - Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(1)) + //Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(1)) }) }) From 684b491b7e68927a5441994279adced3012f8200 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 9 Dec 2024 17:09:04 +0200 Subject: [PATCH 41/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- .../serviceinstance_controller_test.go | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 7c88ca28..63b58d04 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1311,6 +1311,31 @@ var _ = Describe("ServiceInstance controller", func() { deleteAndWait(ctx, serviceInstance) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) }) + It("should update instance with the secret change and secret have labels", func() { + paramsSecret.Labels = map[string]string{"label": "value"} + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + + serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) + smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) + + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"new-secret-value\"}") + paramsSecret.Data = credentialsMap + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + Eventually(func() bool { + return fakeClient.UpdateInstanceCallCount() == 1 + }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) + deleteAndWait(ctx, serviceInstance) + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) + Expect(paramsSecret.Labels["label"]).To(Equal("value")) + + }) It("create instance before secret", func() { newInstanceSpec := v1.ServiceInstanceSpec{ ExternalName: fakeInstanceExternalName, @@ -1521,7 +1546,7 @@ func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Clien if len(instances) == 0 { Expect(len(paramsSecret.Finalizers)).To(Equal(0)) - Expect(len(paramsSecret.Labels)).To(Equal(0)) + Expect(paramsSecret.Labels[common.WatchSecretLabel]).To(BeEmpty()) Expect(len(paramsSecret.Annotations)).To(Equal(0)) return } From 78b9e1e9cb1b3af6d829dd6e1d53c3d956784813 Mon Sep 17 00:00:00 2001 From: i065450 Date: Tue, 10 Dec 2024 08:24:48 +0200 Subject: [PATCH 42/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- internal/utils/condition_utils_test.go | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/internal/utils/condition_utils_test.go b/internal/utils/condition_utils_test.go index a29e552d..db278169 100644 --- a/internal/utils/condition_utils_test.go +++ b/internal/utils/condition_utils_test.go @@ -304,6 +304,34 @@ var _ = Describe("Condition Utils", func() { Expect(result).Should(BeFalse()) }) }) + + Context("getLastObservedGen", func() { + It("should return the last observed generation from the conditions", func() { + resource := &v1.ServiceBinding{ + Status: v1.ServiceBindingStatus{ + Conditions: []metav1.Condition{ + { + Type: common.ConditionSucceeded, + Status: metav1.ConditionTrue, + ObservedGeneration: 5, + }, + }, + }, + } + + Expect(getLastObservedGen(resource)).To(Equal(int64(5))) + }) + + It("should return 0 if the ConditionSucceeded condition is not present", func() { + resource := &v1.ServiceBinding{ + Status: v1.ServiceBindingStatus{ + Conditions: []metav1.Condition{}, + }, + } + + Expect(getLastObservedGen(resource)).To(Equal(int64(0))) + }) + }) }) func getBinding() *v1.ServiceBinding { From 5d7025c05c2c9b31f6101512bf2a09f2ebd8c918 Mon Sep 17 00:00:00 2001 From: I501080 Date: Tue, 10 Dec 2024 09:09:41 +0200 Subject: [PATCH 43/74] sharing instance update --- api/v1/serviceinstance_types.go | 2 +- controllers/serviceinstance_controller.go | 57 ++++++++++------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 25b8d6bc..259916ac 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -203,7 +203,7 @@ func init() { func (si *ServiceInstance) Hub() {} -func (si *ServiceInstance) ShouldBeShared() bool { +func (si *ServiceInstance) GetShared() bool { return si.Spec.Shared != nil && *si.Spec.Shared } diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 35121184..d447f5a3 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -118,8 +118,6 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } } - log.Info(fmt.Sprintf("instance is not in final state, handling... (generation: %d, observedGen: %d", serviceInstance.Generation, common.GetObservedGeneration(serviceInstance))) - smClient, err := r.GetSMClient(ctx, serviceInstance) if err != nil { log.Error(err, "failed to get sm client") @@ -143,16 +141,11 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ // Update if updateRequired(serviceInstance) { - if res, err := r.updateInstance(ctx, smClient, serviceInstance); err != nil { - log.Info("got error while trying to update instance") - return ctrl.Result{}, err - } else if res.Requeue { - return res, nil - } + return r.updateInstance(ctx, smClient, serviceInstance) } - // Handle instance share if needed - if sharingUpdateRequired(serviceInstance) { + // share/unshare + if shareOrUnshareRequired(serviceInstance) { return r.handleInstanceSharing(ctx, serviceInstance, smClient) } @@ -322,8 +315,8 @@ func (r *ServiceInstanceReconciler) handleInstanceSharing(ctx context.Context, s log := utils.GetLogger(ctx) log.Info("Handling change in instance sharing") - if serviceInstance.ShouldBeShared() { - log.Info("Service instance is shouldBeShared, sharing the instance") + if serviceInstance.GetShared() { + log.Info("Service instance appears to be unshared, sharing the instance") err := smClient.ShareInstance(serviceInstance.Status.InstanceID, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo)) if err != nil { log.Error(err, "failed to share instance") @@ -332,7 +325,7 @@ func (r *ServiceInstanceReconciler) handleInstanceSharing(ctx context.Context, s log.Info("instance shared successfully") setSharedCondition(serviceInstance, metav1.ConditionTrue, common.ShareSucceeded, "instance shared successfully") } else { //un-share - log.Info("Service instance is un-shouldBeShared, un-sharing the instance") + log.Info("Service instance appears to be shared, un-sharing the instance") err := smClient.UnShareInstance(serviceInstance.Status.InstanceID, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo)) if err != nil { log.Error(err, "failed to un-share instance") @@ -626,10 +619,16 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool { log := utils.GetLogger(ctx) + if serviceInstance.Status.ForceReconcile { + log.Info("instance is not in final state, ForceReconcile is true") + return false + } + if len(serviceInstance.Status.OperationURL) > 0 { log.Info(fmt.Sprintf("instance is not in final state, async operation is in progress (%s)", serviceInstance.Status.OperationURL)) return false } + observedGen := common.GetObservedGeneration(serviceInstance) if serviceInstance.Generation != observedGen { log.Info(fmt.Sprintf("instance is not in final state, generation: %d, observedGen: %d", serviceInstance.Generation, observedGen)) @@ -642,17 +641,14 @@ func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool return false } - if sharingUpdateRequired(serviceInstance) { + if shareOrUnshareRequired(serviceInstance) { log.Info("instance is not in final state, need to sync sharing status") if len(serviceInstance.Status.HashedSpec) == 0 { updateHashedSpecValue(serviceInstance) } return false } - if serviceInstance.Status.ForceReconcile { - log.Info("instance is not in final state, SubscribeToSecretChanges is true") - return false - } + log.Info(fmt.Sprintf("instance is in final state (generation: %d)", serviceInstance.Generation)) return true } @@ -663,43 +659,40 @@ func updateRequired(serviceInstance *v1.ServiceInstance) bool { return false } - cond := meta.FindStatusCondition(serviceInstance.Status.Conditions, common.ConditionSucceeded) - if cond != nil && cond.Reason == common.UpdateInProgress { //in case of transient error occurred + if serviceInstance.Status.ForceReconcile { return true } - if serviceInstance.Status.ForceReconcile { + + cond := meta.FindStatusCondition(serviceInstance.Status.Conditions, common.ConditionSucceeded) + if cond != nil && cond.Reason == common.UpdateInProgress { //in case of transient error occurred return true } return getSpecHash(serviceInstance) != serviceInstance.Status.HashedSpec } -func sharingUpdateRequired(serviceInstance *v1.ServiceInstance) bool { +func shareOrUnshareRequired(serviceInstance *v1.ServiceInstance) bool { //relevant only for non-shared instances - sharing instance is possible only for usable instances if serviceInstance.Status.Ready != metav1.ConditionTrue { return false } sharedCondition := meta.FindStatusCondition(serviceInstance.GetConditions(), common.ConditionShared) - shouldBeShared := serviceInstance.ShouldBeShared() - if sharedCondition == nil { - return shouldBeShared + return serviceInstance.GetShared() } if sharedCondition.Reason == common.ShareNotSupported { return false } - if sharedCondition.Reason == common.InProgress || sharedCondition.Reason == common.ShareFailed || sharedCondition.Reason == common.UnShareFailed { - return true - } - - if shouldBeShared { - return sharedCondition.Status == metav1.ConditionFalse + if sharedCondition.Status == metav1.ConditionFalse { + // instance does not appear to be shared, should share it if shared is requested + return serviceInstance.GetShared() } - return sharedCondition.Status == metav1.ConditionTrue + // instance appears to be shared, should unshare it if shared is not requested + return !serviceInstance.GetShared() } func getOfferingTags(smClient sm.Client, planID string) ([]string, error) { From 1f7799ca3dc42eb2a99bc938bd61c2434b71861b Mon Sep 17 00:00:00 2001 From: i065450 Date: Tue, 10 Dec 2024 11:27:11 +0200 Subject: [PATCH 44/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- internal/utils/parameters_test.go | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/internal/utils/parameters_test.go b/internal/utils/parameters_test.go index 53b08d38..031db4c3 100644 --- a/internal/utils/parameters_test.go +++ b/internal/utils/parameters_test.go @@ -1,10 +1,15 @@ package utils import ( + "github.com/SAP/sap-btp-service-operator/internal/config" + v1 "github.com/SAP/sap-btp-service-operator/api/v1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var _ = Describe("Parameters", func() { @@ -31,5 +36,52 @@ var _ = Describe("Parameters", func() { Expect(rawParam).To(Equal([]byte(`{"key":"value"}`))) Expect(len(secrets)).To(BeZero()) }) + It("handles parameters from source with secrets", func() { + // Setup + namespace := "test-namespace" + parameters := &runtime.RawExtension{Raw: []byte(`{"param1":"value1"}`)} + secretData := map[string][]byte{"secret-parameter": []byte(`{"param2":"value2"}`)} + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "param-secret", + Namespace: namespace, + }, + Data: secretData, + } + parametersFrom := []v1.ParametersFromSource{ + { + SecretKeyRef: &v1.SecretKeyReference{ + Name: "param-secret", + Key: "secret-parameter", + }, + }, + } + + // Create a fake client with the secret + k8sClient := fake.NewClientBuilder().WithObjects(secret).Build() + + // Initialize the secrets client + InitializeSecretsClient(k8sClient, k8sClient, config.Config{ + ManagementNamespace: "management-namespace", + ReleaseNamespace: "release-namespace", + EnableNamespaceSecrets: true, + EnableLimitedCache: true, + }) + + // Test + parametersRaw, secretsSet, err := BuildSMRequestParameters(namespace, parameters, parametersFrom) + + // Assertions + Expect(err).To(BeNil()) + expectedParams := map[string]interface{}{ + "param1": "value1", + "param2": "value2", + } + rawParameters, err := MarshalRawParameters(expectedParams) + Expect(err).To(BeNil()) + Expect(parametersRaw).To(Equal(rawParameters)) + Expect(len(secretsSet)).To(Equal(1)) + Expect(secretsSet[string(secret.UID)]).To(Equal(secret)) + }) }) }) From 165ab28f6a16a75693fa718e8ba678c9f9580bc9 Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 11 Dec 2024 17:35:36 +0200 Subject: [PATCH 45/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 56 +++++++++-------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index d447f5a3..4fbfc166 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -546,26 +546,14 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context return nil, err } shouldUpdate := false + newLabels := make(map[string]string) if serviceInstance.IsSubscribedToSecretChange() { - existingSecrets := make(map[string]string) - if serviceInstance.Labels == nil { - serviceInstance.Labels = make(map[string]string) - } else { // remove old secret labels - for labelKey := range serviceInstance.Labels { - if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { - existingSecrets[labelKey] = "false" - } - } - } + // find all new secrets on the instance for key := range newSecretsMap { - if _, ok := existingSecrets[common.InstanceSecretLabel+common.Separator+key]; ok { - // this secret was already on the instance and should stay - existingSecrets[common.InstanceSecretLabel+common.Separator+key] = "true" - } else { - // this is a new secret on the instance + secret := newSecretsMap[key] + newLabels[common.InstanceSecretLabel+common.Separator+key] = secret.Name + if _, ok := serviceInstance.Labels[common.InstanceSecretLabel+common.Separator+key]; !ok { shouldUpdate = true - secret := newSecretsMap[key] - serviceInstance.Labels[common.InstanceSecretLabel+common.Separator+key] = secret.Name err = utils.AddSecretHaveWatch(ctx, secret, r.Client, serviceInstance.Name) if err != nil { log.Error(err, fmt.Sprintf("failed to increase secret watch label with key %s", key)) @@ -573,41 +561,41 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } } } - for key := range existingSecrets { - if existingSecrets[key] == "false" { - // this secret is not on the instance anymore and should be deleted - shouldUpdate = true - err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) - if err != nil { - log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) - return nil, err + // find all removed secrets on the instance + for key := range serviceInstance.Labels { + if strings.HasPrefix(key, common.InstanceSecretLabel) { + if _, ok := newLabels[key]; !ok { + shouldUpdate = true + // this secret is not on the instance anymore and should be deleted + err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) + if err != nil { + log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) + return nil, err + } } - delete(serviceInstance.Labels, key) + } else { + newLabels[key] = serviceInstance.Labels[key] } } } else { if serviceInstance.Labels != nil { - // remove all secret labels - var keysToDelete []string for key := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretLabel) { shouldUpdate = true - keysToDelete = append(keysToDelete, key) err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) if err != nil { log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) return nil, err } + } else { + newLabels[key] = serviceInstance.Labels[key] } } - // Perform deletions after the iteration - for _, key := range keysToDelete { - delete(serviceInstance.Labels, key) - } } } if shouldUpdate { - err := r.Client.Update(ctx, serviceInstance) + serviceInstance.Labels = newLabels + err = r.Client.Update(ctx, serviceInstance) if err != nil { log.Error(err, "failed to Update instance with secret labels") return nil, err From 8e8b332c581b4138d2fc3b47d51b1dda20e6551f Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 12 Dec 2024 11:26:32 +0200 Subject: [PATCH 46/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 9 ++++++++- internal/utils/controller_util.go | 14 ++++++++++++-- internal/utils/secret_resolver.go | 10 +--------- internal/utils/sm_utils.go | 3 ++- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 4fbfc166..06a1840c 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -149,6 +149,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ return r.handleInstanceSharing(ctx, serviceInstance, smClient) } + log.Info("No action required") return ctrl.Result{}, nil } @@ -297,7 +298,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI } for labelKey := range serviceInstance.Labels { if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { - err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[labelKey], serviceInstance.Name) + log.Info(fmt.Sprintf("decreasing secret watch label with key %s", labelKey)) if err != nil { log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", labelKey)) return ctrl.Result{}, err @@ -553,6 +554,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context secret := newSecretsMap[key] newLabels[common.InstanceSecretLabel+common.Separator+key] = secret.Name if _, ok := serviceInstance.Labels[common.InstanceSecretLabel+common.Separator+key]; !ok { + log.Info(fmt.Sprintf("adding secret watch with key %s", key)) shouldUpdate = true err = utils.AddSecretHaveWatch(ctx, secret, r.Client, serviceInstance.Name) if err != nil { @@ -565,6 +567,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context for key := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretLabel) { if _, ok := newLabels[key]; !ok { + log.Info(fmt.Sprintf("removing secret watch with key %s", key)) shouldUpdate = true // this secret is not on the instance anymore and should be deleted err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) @@ -574,13 +577,16 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } } } else { + // this label not related to secrets newLabels[key] = serviceInstance.Labels[key] } } } else { + // need to remove all secrets labels if serviceInstance.Labels != nil { for key := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretLabel) { + log.Info(fmt.Sprintf("removing secret watch with key %s", key)) shouldUpdate = true err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) if err != nil { @@ -595,6 +601,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } if shouldUpdate { serviceInstance.Labels = newLabels + log.Info("updating instance with secret labels") err = r.Client.Update(ctx, serviceInstance) if err != nil { log.Error(err, "failed to Update instance with secret labels") diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index fd97dca5..50aa2d23 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -281,13 +281,14 @@ func RemoveSecretWatch(ctx context.Context, k8sClient client.Client, namespace s } if _, exists := secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName]; exists { delete(secret.Annotations, common.WatchSecretLabel+common.Separator+instanceName) - if len(secret.Annotations) == 0 { + + if HasNoWatchSecretAnnotations(secret) { delete(secret.Labels, common.WatchSecretLabel) if controllerutil.ContainsFinalizer(secret, common.FinalizerName) { controllerutil.RemoveFinalizer(secret, common.FinalizerName) } } - if err := k8sClient.Update(ctx, secret); err != nil { + if err = k8sClient.Update(ctx, secret); err != nil { return err } } @@ -303,3 +304,12 @@ func IsSecretWatched(secret client.Object) bool { } return false } + +func HasNoWatchSecretAnnotations(secret *v12.Secret) bool { + for key := range secret.Annotations { + if strings.HasPrefix(key, common.WatchSecretLabel) { + return false + } + } + return true +} diff --git a/internal/utils/secret_resolver.go b/internal/utils/secret_resolver.go index a77f97ac..1ffc1d55 100644 --- a/internal/utils/secret_resolver.go +++ b/internal/utils/secret_resolver.go @@ -61,10 +61,8 @@ func GetSecretForResource(ctx context.Context, namespace, name string) (*v1.Secr func (sr *secretClient) getSecretFromManagementNamespace(ctx context.Context, name string) (*v1.Secret, error) { secretForResource := &v1.Secret{} - sr.Log.Info(fmt.Sprintf("Searching for secret %s in management namespace %s", name, sr.ManagementNamespace)) err := sr.getWithClientFallback(ctx, types.NamespacedName{Name: name, Namespace: sr.ManagementNamespace}, secretForResource) if err != nil { - sr.Log.Info(fmt.Sprintf("Could not fetch secret %s from management namespace %s", name, sr.ManagementNamespace)) return nil, err } return secretForResource, nil @@ -75,14 +73,12 @@ func (sr *secretClient) getSecretForResource(ctx context.Context, namespace, nam // search namespace secret if sr.EnableNamespaceSecrets { - sr.Log.Info(fmt.Sprintf("Searching for secret %s in namespace %s", name, namespace)) err := sr.getWithClientFallback(ctx, types.NamespacedName{Name: name, Namespace: namespace}, secretForResource) if err == nil { return secretForResource, nil } if client.IgnoreNotFound(err) != nil { - sr.Log.Error(err, fmt.Sprintf("Could not fetch secret %s from namespace %s", name, namespace)) return nil, err } } @@ -95,17 +91,15 @@ func (sr *secretClient) getSecretForResource(ctx context.Context, namespace, nam } if client.IgnoreNotFound(err) != nil { - sr.Log.Error(err, fmt.Sprintf("Could not fetch secret %s-%s in the management namespace %s", namespace, name, sr.ManagementNamespace)) return nil, err } - // namespace-specific secret not found in management namespace, fallback to central cluster secret return sr.getClusterDefaultSecret(ctx, name) } func (sr *secretClient) getClusterDefaultSecret(ctx context.Context, name string) (*v1.Secret, error) { secretForResource := &v1.Secret{} - sr.Log.Info(fmt.Sprintf("Searching for cluster secret %s in releaseNamespace %s", name, sr.ReleaseNamespace)) + // sr.Log.Info(fmt.Sprintf("Searching for cluster secret %s in releaseNamespace %s", name, sr.ReleaseNamespace)) err := sr.getWithClientFallback(ctx, types.NamespacedName{Namespace: sr.ReleaseNamespace, Name: name}, secretForResource) if err != nil { sr.Log.Error(err, fmt.Sprintf("Could not fetch cluster secret %s from releaseNamespace %s", name, sr.ReleaseNamespace)) @@ -118,12 +112,10 @@ func (sr *secretClient) getWithClientFallback(ctx context.Context, key types.Nam err := sr.Client.Get(ctx, key, secretForResource) if err != nil { if errors.IsNotFound(err) && sr.LimitedCacheEnabled { - sr.Log.Info(fmt.Sprintf("secret %s not found in cache, falling back to non-cached client", key.String())) err = sr.NonCachedClient.Get(ctx, key, secretForResource) if err != nil { return err } - sr.Log.Info(fmt.Sprintf("secret %s found using non-cached client", key.String())) return nil } return err diff --git a/internal/utils/sm_utils.go b/internal/utils/sm_utils.go index 9d30597d..a2bf9e82 100644 --- a/internal/utils/sm_utils.go +++ b/internal/utils/sm_utils.go @@ -33,6 +33,7 @@ func GetSMClient(ctx context.Context, serviceInstance *v1.ServiceInstance) (sm.C log.Error(err, "failed to get secret for instance") return nil, err } + log.Info(fmt.Sprintf("using secret %s in namespace %s", secret.Name, secret.Namespace)) } clientConfig := &sm.ClientConfig{ @@ -62,7 +63,7 @@ func GetSMClient(ctx context.Context, serviceInstance *v1.ServiceInstance) (sm.C if client.IgnoreNotFound(err) != nil { return nil, err } - + log.Info(fmt.Sprintf("using tls secret %s in namespace %s", secret.Name, secret.Namespace)) if tlsSecret == nil || len(tlsSecret.Data) == 0 || len(tlsSecret.Data[corev1.TLSCertKey]) == 0 || len(tlsSecret.Data[corev1.TLSPrivateKeyKey]) == 0 { log.Info("clientsecret not found in SM credentials, and tls secret is invalid") return nil, &InvalidCredentialsError{} From e8a397435b0f9aff412cea8a718c155dbdaecda6 Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 12 Dec 2024 15:34:04 +0200 Subject: [PATCH 47/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- api/v1/serviceinstance_types.go | 14 ++++ api/v1/serviceinstance_types_test.go | 81 +++++++++++++++++++++++ controllers/serviceinstance_controller.go | 23 +------ 3 files changed, 98 insertions(+), 20 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 259916ac..4c391a85 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -17,11 +17,16 @@ limitations under the License. package v1 import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "github.com/SAP/sap-btp-service-operator/api/common" "github.com/SAP/sap-btp-service-operator/client/sm/types" v1 "k8s.io/api/authentication/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -210,3 +215,12 @@ func (si *ServiceInstance) GetShared() bool { func (si *ServiceInstance) IsSubscribedToSecretChange() bool { return si.Spec.SubscribeToSecretChanges != nil && *si.Spec.SubscribeToSecretChanges } + +func (si *ServiceInstance) GetSpecHash() string { + spec := si.Spec + spec.Shared = ptr.To(false) + specBytes, _ := json.Marshal(spec) + s := string(specBytes) + hash := md5.Sum([]byte(s)) + return hex.EncodeToString(hash[:]) +} diff --git a/api/v1/serviceinstance_types_test.go b/api/v1/serviceinstance_types_test.go index 12735357..15fb011e 100644 --- a/api/v1/serviceinstance_types_test.go +++ b/api/v1/serviceinstance_types_test.go @@ -1,12 +1,17 @@ package v1 import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "github.com/SAP/sap-btp-service-operator/api/common" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" ) var _ = Describe("Service Instance Type Test", func() { @@ -128,4 +133,80 @@ var _ = Describe("Service Instance Type Test", func() { instance.Spec.SubscribeToSecretChanges = &[]bool{true}[0] Expect(instance.IsSubscribedToSecretChange()).To(BeTrue()) }) + + It("should return correct spec hash", func() { + // Calculate expected hash + spec := instance.Spec + spec.Shared = ptr.To(false) + specBytes, _ := json.Marshal(spec) + hash := md5.Sum(specBytes) + expectedHash := hex.EncodeToString(hash[:]) + + // Get actual hash + actualHash := instance.GetSpecHash() + + // Compare hashes + Expect(actualHash).To(Equal(expectedHash)) + }) + It("should update spec hash when spec changes", func() { + // Calculate initial hash + initialHash := instance.GetSpecHash() + + // Modify the spec + instance.Spec.ServicePlanName = "new-plan" + + // Calculate new hash + newHash := instance.GetSpecHash() + + // Ensure the hash has changed + Expect(initialHash).NotTo(Equal(newHash)) + }) + It("should update spec hash when parametersFrom changes", func() { + // Calculate initial hash + initialHash := instance.GetSpecHash() + + // Modify the parametersFrom field + instance.Spec.ParametersFrom = []ParametersFromSource{ + { + SecretKeyRef: &SecretKeyReference{ + Name: "new-param-secret", + Key: "new-secret-parameter", + }, + }, + } + + // Calculate new hash + newHash := instance.GetSpecHash() + + // Ensure the hash has changed + Expect(initialHash).NotTo(Equal(newHash)) + }) + It("should update spec hash when parametersFrom changes with initial object", func() { + // Initialize ParametersFrom with an object + instance.Spec.ParametersFrom = []ParametersFromSource{ + { + SecretKeyRef: &SecretKeyReference{ + Name: "initial-param-secret", + Key: "initial-secret-parameter", + }, + }, + } + + // Calculate initial hash + initialHash := instance.GetSpecHash() + + // Modify the parametersFrom field + instance.Spec.ParametersFrom = append(instance.Spec.ParametersFrom, ParametersFromSource{ + SecretKeyRef: &SecretKeyReference{ + Name: "new-param-secret", + Key: "new-secret-parameter", + }, + }) + + // Calculate new hash + newHash := instance.GetSpecHash() + + // Ensure the hash has changed + Expect(initialHash).NotTo(Equal(newHash)) + }) }) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 06a1840c..59ec14c8 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -18,14 +18,11 @@ package controllers import ( "context" - "crypto/md5" - "encoding/hex" "encoding/json" "fmt" "net/http" "strings" - "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/predicate" "github.com/SAP/sap-btp-service-operator/api/common" @@ -217,13 +214,12 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient log := utils.GetLogger(ctx) log.Info(fmt.Sprintf("updating instance %s in SM", serviceInstance.Status.InstanceID)) - updateHashedSpecValue(serviceInstance) - instanceParameters, err := r.buildSMRequestParameters(ctx, serviceInstance) if err != nil { log.Error(err, "failed to parse instance parameters") return utils.MarkAsTransientError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance) } + updateHashedSpecValue(serviceInstance) _, operationURL, err := smClient.UpdateInstance(serviceInstance.Status.InstanceID, &smClientTypes.ServiceInstance{ Name: serviceInstance.Spec.ExternalName, @@ -663,7 +659,7 @@ func updateRequired(serviceInstance *v1.ServiceInstance) bool { return true } - return getSpecHash(serviceInstance) != serviceInstance.Status.HashedSpec + return serviceInstance.GetSpecHash() != serviceInstance.Status.HashedSpec } func shareOrUnshareRequired(serviceInstance *v1.ServiceInstance) bool { @@ -730,19 +726,6 @@ func getTags(tags []byte) ([]string, error) { return tagsArr, nil } -func getSpecHash(serviceInstance *v1.ServiceInstance) string { - spec := serviceInstance.Spec - spec.Shared = ptr.To(false) - specBytes, _ := json.Marshal(spec) - s := string(specBytes) - return generateEncodedMD5Hash(s) -} - -func generateEncodedMD5Hash(str string) string { - hash := md5.Sum([]byte(str)) - return hex.EncodeToString(hash[:]) -} - func setSharedCondition(object common.SAPBTPResource, status metav1.ConditionStatus, reason, msg string) { conditions := object.GetConditions() // align all conditions to latest generation @@ -769,7 +752,7 @@ func setSharedCondition(object common.SAPBTPResource, status metav1.ConditionSta } func updateHashedSpecValue(serviceInstance *v1.ServiceInstance) { - serviceInstance.Status.HashedSpec = getSpecHash(serviceInstance) + serviceInstance.Status.HashedSpec = serviceInstance.GetSpecHash() } func getErrorMsgFromLastOperation(status *smClientTypes.Operation) string { From 0eb41ceaa40d0e0200f72daa1442568c4feb1f6b Mon Sep 17 00:00:00 2001 From: i065450 Date: Thu, 12 Dec 2024 15:37:11 +0200 Subject: [PATCH 48/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- .../serviceinstance_controller_test.go | 47 +++++++++++++++---- controllers/suite_test.go | 4 ++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 63b58d04..4b632e73 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -218,7 +218,7 @@ var _ = Describe("ServiceInstance controller", func() { Expect(serviceInstance.Name).To(Equal(fakeInstanceName)) Expect(serviceInstance.Status.HashedSpec).To(Not(BeNil())) Expect(string(serviceInstance.Spec.Parameters.Raw)).To(ContainSubstring("\"key\":\"value\"")) - Expect(serviceInstance.Status.HashedSpec).To(Equal(getSpecHash(serviceInstance))) + Expect(serviceInstance.Status.HashedSpec).To(Equal(serviceInstance.GetSpecHash())) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) params := smInstance.Parameters Expect(params).To(ContainSubstring("\"key\":\"value\"")) @@ -1379,29 +1379,59 @@ var _ = Describe("ServiceInstance controller", func() { checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{}) }) It("update instance parameterFrom", func() { + credentialsMap := make(map[string][]byte) + credentialsMap["secret-parameter2"] = []byte("{\"secret-key2\":\"secret-value2\"}") + anotherSecret = createSecret(ctx, "instance-params-secret-new", testNamespace, credentialsMap) serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) - checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) - anotherSecret = createParamsSecret(ctx, "instance-params-secret-new", testNamespace) - + // update instance parametersFrom serviceInstance.Spec.ParametersFrom = []v1.ParametersFromSource{ { SecretKeyRef: &v1.SecretKeyReference{ - Name: "instance-params-secret-new", + Name: "instance-params-secret", Key: "secret-parameter", }, }, + { + SecretKeyRef: &v1.SecretKeyReference{ + Name: "instance-params-secret-new", + Key: "secret-parameter2", + }, + }, } serviceInstance = updateInstance(ctx, serviceInstance) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Updated, "") - + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\"", "\"secret-key2\":\"secret-value2\""}) checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{serviceInstance}) - checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) - // Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(1)) + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + + // update instance parametersFrom + serviceInstance.Spec.ParametersFrom = []v1.ParametersFromSource{ + { + SecretKeyRef: &v1.SecretKeyReference{ + Name: "instance-params-secret", + Key: "secret-parameter", + }, + }, + } + serviceInstance = updateInstance(ctx, serviceInstance) + Eventually(func() bool { + return fakeClient.UpdateInstanceCallCount() > 1 + }, timeout*3, interval).Should(BeTrue(), + "dkd", + ) + _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(1) + checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) + checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{}) + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + + Expect(serviceInstance.Labels[common.InstanceSecretLabel+common.Separator+string(anotherSecret.GetUID())]).To(BeEmpty()) + }) It("should update two instances with the secret change", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) @@ -1554,6 +1584,7 @@ func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Clien Expect(paramsSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) Expect(len(paramsSecret.Annotations)).To(Equal(len(instances))) for _, instance := range instances { + Expect(k8sClient.Get(ctx, getResourceNamespacedName(instance), instance)).To(Succeed()) Expect(paramsSecret.Annotations[common.WatchSecretLabel+common.Separator+instance.Name]).To(Equal("true")) Expect(instance.Labels[common.InstanceSecretLabel+common.Separator+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) } diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 2fafc636..743648c9 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -306,6 +306,10 @@ func waitForResourceToBeDeleted(ctx context.Context, key types.NamespacedName, r func createParamsSecret(ctx context.Context, secretName, namespace string) *corev1.Secret { credentialsMap := make(map[string][]byte) credentialsMap["secret-parameter"] = []byte("{\"secret-key\":\"secret-value\"}") + return createSecret(ctx, secretName, namespace, credentialsMap) +} + +func createSecret(ctx context.Context, secretName string, namespace string, credentialsMap map[string][]byte) *corev1.Secret { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: secretName, From 9cb0f5533001e2cf8b98b3c23038b258e918d83c Mon Sep 17 00:00:00 2001 From: I501080 Date: Sun, 15 Dec 2024 10:59:58 +0200 Subject: [PATCH 49/74] review --- api/common/consts.go | 5 +- controllers/secret_controller.go | 12 ++- controllers/serviceinstance_controller.go | 91 ++++++------------- .../serviceinstance_controller_test.go | 6 +- internal/utils/controller_util.go | 71 ++------------- internal/utils/controller_util_test.go | 30 +----- 6 files changed, 56 insertions(+), 159 deletions(-) diff --git a/api/common/consts.go b/api/common/consts.go index 736644a0..e863ceee 100644 --- a/api/common/consts.go +++ b/api/common/consts.go @@ -3,9 +3,8 @@ package common const ( ManagedByBTPOperatorLabel = "services.cloud.sap.com/managed-by-sap-btp-operator" ClusterSecretLabel = "services.cloud.sap.com/cluster-secret" - InstanceSecretLabel = "services.cloud.sap.com/secretRef" - WatchSecretLabel = "services.cloud.sap.com/watchSecret" - Separator = "_" + InstanceSecretRefLabel = "services.cloud.sap.com/secret-ref_" + WatchSecretLabel = "services.cloud.sap.com/watch-secret" NamespaceLabel = "_namespace" K8sNameLabel = "_k8sname" diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index f7ca4f30..77277d1a 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -52,11 +52,19 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) } var instances v1.ServiceInstanceList - labelSelector := client.MatchingLabels{common.InstanceSecretLabel + common.Separator + string(secret.GetUID()): secret.Name} + labelSelector := client.MatchingLabels{common.InstanceSecretRefLabel + string(secret.GetUID()): secret.Name} if err := r.Client.List(ctx, &instances, labelSelector); err != nil { log.Error(err, "failed to list service instances") return ctrl.Result{}, err } + + if len(instances.Items) == 0 { + // No instances are using this secret + log.Info(fmt.Sprintf("no instances are using secret %s, removing watch label and finalizer", req.NamespacedName)) + delete(secret.Labels, common.WatchSecretLabel) + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, secret, common.FinalizerName, common.SecretController) + } + for _, instance := range instances.Items { log.Info(fmt.Sprintf("waking up instance %s", instance.Name)) instance.Status.ForceReconcile = true @@ -65,9 +73,11 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) return reconcile.Result{}, err } } + if utils.IsMarkedForDeletion(secret.ObjectMeta) { return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, secret, common.FinalizerName, common.SecretController) } + return reconcile.Result{}, nil } diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 59ec14c8..d207295b 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -219,8 +219,8 @@ func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient log.Error(err, "failed to parse instance parameters") return utils.MarkAsTransientError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance) } - updateHashedSpecValue(serviceInstance) + updateHashedSpecValue(serviceInstance) _, operationURL, err := smClient.UpdateInstance(serviceInstance.Status.InstanceID, &smClientTypes.ServiceInstance{ Name: serviceInstance.Spec.ExternalName, ServicePlanID: serviceInstance.Spec.ServicePlanID, @@ -292,15 +292,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI log.Info("Deleting instance async") return r.handleAsyncDelete(ctx, serviceInstance, operationURL) } - for labelKey := range serviceInstance.Labels { - if strings.HasPrefix(labelKey, common.InstanceSecretLabel) { - log.Info(fmt.Sprintf("decreasing secret watch label with key %s", labelKey)) - if err != nil { - log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", labelKey)) - return ctrl.Result{}, err - } - } - } + log.Info("Instance was deleted successfully, removing finalizer") // remove our finalizer from the list and update it. return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()) @@ -537,74 +529,47 @@ func (r *ServiceInstanceReconciler) handleInstanceSharingError(ctx context.Conte func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context, serviceInstance *v1.ServiceInstance) ([]byte, error) { log := utils.GetLogger(ctx) - instanceParameters, newSecretsMap, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom) + instanceParameters, paramSecrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom) if err != nil { log.Error(err, "failed to build instance parameters") return nil, err } - shouldUpdate := false - newLabels := make(map[string]string) + instanceLabelsChanged := false + instanceLabels := make(map[string]string) if serviceInstance.IsSubscribedToSecretChange() { // find all new secrets on the instance - for key := range newSecretsMap { - secret := newSecretsMap[key] - newLabels[common.InstanceSecretLabel+common.Separator+key] = secret.Name - if _, ok := serviceInstance.Labels[common.InstanceSecretLabel+common.Separator+key]; !ok { - log.Info(fmt.Sprintf("adding secret watch with key %s", key)) - shouldUpdate = true - err = utils.AddSecretHaveWatch(ctx, secret, r.Client, serviceInstance.Name) - if err != nil { - log.Error(err, fmt.Sprintf("failed to increase secret watch label with key %s", key)) + for secretUID := range paramSecrets { + secret := paramSecrets[secretUID] + instanceLabels[common.InstanceSecretRefLabel+secretUID] = secret.Name + if _, ok := serviceInstance.Labels[common.InstanceSecretRefLabel+secretUID]; !ok { + log.Info(fmt.Sprintf("adding secret watch for secret %s", secret.Name)) + instanceLabelsChanged = true + if err := utils.LabelSecretForWatch(ctx, r.Client, secret); err != nil { + log.Error(err, fmt.Sprintf("failed to mark secret for watch %s", secretUID)) return nil, err } } } - // find all removed secrets on the instance - for key := range serviceInstance.Labels { - if strings.HasPrefix(key, common.InstanceSecretLabel) { - if _, ok := newLabels[key]; !ok { - log.Info(fmt.Sprintf("removing secret watch with key %s", key)) - shouldUpdate = true - // this secret is not on the instance anymore and should be deleted - err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) - if err != nil { - log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) - return nil, err - } - } - } else { - // this label not related to secrets - newLabels[key] = serviceInstance.Labels[key] - } - } - } else { - // need to remove all secrets labels - if serviceInstance.Labels != nil { - for key := range serviceInstance.Labels { - if strings.HasPrefix(key, common.InstanceSecretLabel) { - log.Info(fmt.Sprintf("removing secret watch with key %s", key)) - shouldUpdate = true - err = utils.RemoveSecretWatch(ctx, r.Client, serviceInstance.Namespace, serviceInstance.Labels[key], serviceInstance.Name) - if err != nil { - log.Error(err, fmt.Sprintf("failed to decrease secret watch label with key %s", key)) - return nil, err - } - } else { - newLabels[key] = serviceInstance.Labels[key] - } + } + + //sync instance labels + for key := range serviceInstance.Labels { + if strings.HasPrefix(key, common.InstanceSecretRefLabel) { + if _, ok := instanceLabels[key]; !ok { + instanceLabelsChanged = true } + } else { + // this label not related to secrets, add it + instanceLabels[key] = serviceInstance.Labels[key] } } - if shouldUpdate { - serviceInstance.Labels = newLabels + if instanceLabelsChanged { + serviceInstance.Labels = instanceLabels log.Info("updating instance with secret labels") - err = r.Client.Update(ctx, serviceInstance) - if err != nil { - log.Error(err, "failed to Update instance with secret labels") - return nil, err - } + return instanceParameters, r.Client.Update(ctx, serviceInstance) } - return instanceParameters, err + + return instanceParameters, nil } func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool { diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 4b632e73..be2f09a7 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1430,7 +1430,7 @@ var _ = Describe("ServiceInstance controller", func() { checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{}) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) - Expect(serviceInstance.Labels[common.InstanceSecretLabel+common.Separator+string(anotherSecret.GetUID())]).To(BeEmpty()) + Expect(serviceInstance.Labels[common.InstanceSecretRefLabel+string(anotherSecret.GetUID())]).To(BeEmpty()) }) It("should update two instances with the secret change", func() { @@ -1585,8 +1585,8 @@ func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Clien Expect(len(paramsSecret.Annotations)).To(Equal(len(instances))) for _, instance := range instances { Expect(k8sClient.Get(ctx, getResourceNamespacedName(instance), instance)).To(Succeed()) - Expect(paramsSecret.Annotations[common.WatchSecretLabel+common.Separator+instance.Name]).To(Equal("true")) - Expect(instance.Labels[common.InstanceSecretLabel+common.Separator+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) + Expect(paramsSecret.Annotations[common.WatchSecretLabel+instance.Name]).To(Equal("true")) + Expect(instance.Labels[common.InstanceSecretRefLabel+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) } } diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 50aa2d23..28ff6a01 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -9,7 +9,7 @@ import ( "strings" "time" - v12 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" "github.com/SAP/sap-btp-service-operator/api/common" "github.com/SAP/sap-btp-service-operator/client/sm" @@ -247,69 +247,16 @@ func serialize(value interface{}) ([]byte, format, error) { return data, JSON, nil } -func AddSecretHaveWatch(ctx context.Context, secret *v12.Secret, k8sClient client.Client, instanceName string) error { - if secret != nil { - if secret.Annotations == nil { - secret.Annotations = make(map[string]string) - } - if secret.Labels == nil { - secret.Labels = make(map[string]string) - } - secret.Labels[common.WatchSecretLabel] = "true" - if !controllerutil.ContainsFinalizer(secret, common.FinalizerName) { - controllerutil.AddFinalizer(secret, common.FinalizerName) - } - - if _, exists := secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName]; !exists { - secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName] = "true" - if err := k8sClient.Update(ctx, secret); err != nil { - return err - } - } - } - return nil -} - -func RemoveSecretWatch(ctx context.Context, k8sClient client.Client, namespace string, name string, instanceName string) error { - secret := &v12.Secret{} - err := k8sClient.Get(ctx, apimachinerytypes.NamespacedName{Name: name, Namespace: namespace}, secret) - if err != nil { - return client.IgnoreNotFound(err) - } +func LabelSecretForWatch(ctx context.Context, k8sClient client.Client, secret *corev1.Secret) error { if secret.Annotations == nil { - return nil - } - if _, exists := secret.Annotations[common.WatchSecretLabel+common.Separator+instanceName]; exists { - delete(secret.Annotations, common.WatchSecretLabel+common.Separator+instanceName) - - if HasNoWatchSecretAnnotations(secret) { - delete(secret.Labels, common.WatchSecretLabel) - if controllerutil.ContainsFinalizer(secret, common.FinalizerName) { - controllerutil.RemoveFinalizer(secret, common.FinalizerName) - } - } - if err = k8sClient.Update(ctx, secret); err != nil { - return err - } + secret.Annotations = make(map[string]string) } - - return nil -} - -func IsSecretWatched(secret client.Object) bool { - for key := range secret.GetAnnotations() { - if strings.HasPrefix(key, common.WatchSecretLabel) { - return true - } + if secret.Labels == nil { + secret.Labels = make(map[string]string) } - return false -} - -func HasNoWatchSecretAnnotations(secret *v12.Secret) bool { - for key := range secret.Annotations { - if strings.HasPrefix(key, common.WatchSecretLabel) { - return false - } + secret.Labels[common.WatchSecretLabel] = "true" + if !controllerutil.ContainsFinalizer(secret, common.FinalizerName) { + controllerutil.AddFinalizer(secret, common.FinalizerName) } - return true + return k8sClient.Update(ctx, secret) } diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index d0b5d47e..2383e7da 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -4,7 +4,6 @@ import ( "encoding/json" "net/http" - "github.com/SAP/sap-btp-service-operator/api/common" v1 "github.com/SAP/sap-btp-service-operator/api/v1" "github.com/SAP/sap-btp-service-operator/client/sm" "github.com/go-logr/logr" @@ -202,7 +201,7 @@ var _ = Describe("Controller Util", func() { }) }) - Context("AddSecretHaveWatch", func() { + Context("LabelSecretForWatch", func() { It("should add the watch label to the secret if it is missing", func() { // Create a fake client @@ -217,11 +216,9 @@ var _ = Describe("Controller Util", func() { Expect(err).ToNot(HaveOccurred()) err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, secret) Expect(err).ToNot(HaveOccurred()) - Expect(IsSecretWatched(secret)).To(BeFalse()) // Call the function - name := "instancedName" - err = AddSecretHaveWatch(ctx, secret, k8sClient, name) + err = LabelSecretForWatch(ctx, k8sClient, secret) Expect(err).ToNot(HaveOccurred()) // Get the updated secret @@ -229,39 +226,18 @@ var _ = Describe("Controller Util", func() { err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) - // Verify the annotation was added - Expect(updatedSecret.Annotations[common.WatchSecretLabel+common.Separator+name]).To(Equal("true")) - - err = AddSecretHaveWatch(ctx, secret, k8sClient, "new-name") + err = LabelSecretForWatch(ctx, k8sClient, secret) Expect(err).ToNot(HaveOccurred()) err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) - // Verify the annotation was added - Expect(updatedSecret.Annotations[common.WatchSecretLabel+common.Separator+"new-name"]).To(Equal("true")) - - err = RemoveSecretWatch(ctx, k8sClient, secret.Namespace, secret.Name, name) - Expect(err).ToNot(HaveOccurred()) - err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - Expect(updatedSecret.Annotations[common.WatchSecretLabel+common.Separator+"new-name"]).To(Equal("true")) - _, exist := updatedSecret.Annotations[common.WatchSecretLabel+common.Separator+name] - Expect(exist).To(BeFalse()) - - Expect(IsSecretWatched(updatedSecret)).To(BeTrue()) - - err = RemoveSecretWatch(ctx, k8sClient, secret.Namespace, secret.Name, "new-name") - Expect(err).ToNot(HaveOccurred()) - err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - Expect(IsSecretWatched(updatedSecret)).To(BeFalse()) }) }) }) From 348fc74236a455ccbc8bd36e5c7d03a6b2db4985 Mon Sep 17 00:00:00 2001 From: i065450 Date: Sun, 15 Dec 2024 14:23:05 +0200 Subject: [PATCH 50/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/secret_controller.go | 4 +-- .../serviceinstance_controller_test.go | 31 +++++++++++-------- internal/utils/controller_util.go | 3 -- internal/utils/controller_util_test.go | 16 +++------- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index 77277d1a..a0a22011 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -89,13 +89,13 @@ func (r *SecretReconciler) SetupWithManager(mgr ctrl.Manager) error { return labelSelector.Matches(labels.Set(e.ObjectNew.GetLabels())) && (isSecretDataChanged(e) || isSecretInDelete(e)) }, CreateFunc: func(e event.CreateEvent) bool { - return false + return labelSelector.Matches(labels.Set(e.Object.GetLabels())) }, DeleteFunc: func(e event.DeleteEvent) bool { return labelSelector.Matches(labels.Set(e.Object.GetLabels())) }, GenericFunc: func(e event.GenericEvent) bool { - return false + return labelSelector.Matches(labels.Set(e.Object.GetLabels())) }, } diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index be2f09a7..3b3d306a 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1309,6 +1309,7 @@ var _ = Describe("ServiceInstance controller", func() { _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) deleteAndWait(ctx, serviceInstance) + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) }) It("should update instance with the secret change and secret have labels", func() { @@ -1572,21 +1573,25 @@ func updateInstanceStatus(ctx context.Context, instance *v1.ServiceInstance) *v1 } func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Client, paramsSecret *corev1.Secret, instances []*v1.ServiceInstance) { - Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) if len(instances) == 0 { - Expect(len(paramsSecret.Finalizers)).To(Equal(0)) - Expect(paramsSecret.Labels[common.WatchSecretLabel]).To(BeEmpty()) - Expect(len(paramsSecret.Annotations)).To(Equal(0)) - return - } - Expect(paramsSecret.Finalizers[0]).To(Equal(common.FinalizerName)) - Expect(paramsSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) - Expect(len(paramsSecret.Annotations)).To(Equal(len(instances))) - for _, instance := range instances { - Expect(k8sClient.Get(ctx, getResourceNamespacedName(instance), instance)).To(Succeed()) - Expect(paramsSecret.Annotations[common.WatchSecretLabel+instance.Name]).To(Equal("true")) - Expect(instance.Labels[common.InstanceSecretRefLabel+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) + Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) + // Add an data to wake up the secret + paramsSecret.Data["secret-parameter2"] = []byte("{\"secret-key\":\"new-secret-value\"}") + // Update the secret in the Kubernetes cluster + Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) + Eventually(func() bool { + Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) + return len(paramsSecret.Labels[common.WatchSecretLabel]) == 0 && len(paramsSecret.Finalizers) == 0 + }, timeout, interval).Should(BeTrue()) + } else { + Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) + for _, instance := range instances { + Expect(k8sClient.Get(ctx, getResourceNamespacedName(instance), instance)).To(Succeed()) + Expect(instance.Labels[common.InstanceSecretRefLabel+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) + } + Expect(paramsSecret.Finalizers[0]).To(Equal(common.FinalizerName)) + Expect(paramsSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) } } diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 28ff6a01..8d44d290 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -248,9 +248,6 @@ func serialize(value interface{}) ([]byte, format, error) { } func LabelSecretForWatch(ctx context.Context, k8sClient client.Client, secret *corev1.Secret) error { - if secret.Annotations == nil { - secret.Annotations = make(map[string]string) - } if secret.Labels == nil { secret.Labels = make(map[string]string) } diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index 2383e7da..f80fb6df 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -4,6 +4,8 @@ import ( "encoding/json" "net/http" + "github.com/SAP/sap-btp-service-operator/api/common" + v1 "github.com/SAP/sap-btp-service-operator/api/v1" "github.com/SAP/sap-btp-service-operator/client/sm" "github.com/go-logr/logr" @@ -225,18 +227,8 @@ var _ = Describe("Controller Util", func() { updatedSecret := &corev1.Secret{} err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) - - err = LabelSecretForWatch(ctx, k8sClient, secret) - Expect(err).ToNot(HaveOccurred()) - - err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) - Expect(err).ToNot(HaveOccurred()) - - err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) - Expect(err).ToNot(HaveOccurred()) - - err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) - Expect(err).ToNot(HaveOccurred()) + Expect(updatedSecret.Finalizers[0]).To(Equal(common.FinalizerName)) + Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) }) }) From 8edfb831a2e32dbab3a9957b7f57b2040b11d468 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 16 Dec 2024 09:33:21 +0200 Subject: [PATCH 51/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- config/samples/services_v1_serviceinstance.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/config/samples/services_v1_serviceinstance.yaml b/config/samples/services_v1_serviceinstance.yaml index cfe841a8..9b13548e 100644 --- a/config/samples/services_v1_serviceinstance.yaml +++ b/config/samples/services_v1_serviceinstance.yaml @@ -5,11 +5,5 @@ metadata: spec: serviceOfferingName: service-manager servicePlanName: subaccount-audit - parametersFrom: - - secretKeyRef: - name: my-secret - key: secret-parameter - - secretKeyRef: - name: my-secret1 - key: secret-parameter + From 210b3f6f56fdf7a9c1afdb9feca44f98db814057 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 16 Dec 2024 09:33:50 +0200 Subject: [PATCH 52/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- config/samples/services_v1_serviceinstance.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/samples/services_v1_serviceinstance.yaml b/config/samples/services_v1_serviceinstance.yaml index 9b13548e..b960180a 100644 --- a/config/samples/services_v1_serviceinstance.yaml +++ b/config/samples/services_v1_serviceinstance.yaml @@ -6,4 +6,3 @@ spec: serviceOfferingName: service-manager servicePlanName: subaccount-audit - From d34a3fbafbd6ac3a8e439795c3ccfc609548ec71 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 16 Dec 2024 09:34:37 +0200 Subject: [PATCH 53/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- config/samples/services_v1_serviceinstance.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/samples/services_v1_serviceinstance.yaml b/config/samples/services_v1_serviceinstance.yaml index b960180a..197de5a2 100644 --- a/config/samples/services_v1_serviceinstance.yaml +++ b/config/samples/services_v1_serviceinstance.yaml @@ -4,5 +4,4 @@ metadata: name: sample-instance-1 spec: serviceOfferingName: service-manager - servicePlanName: subaccount-audit - + servicePlanName: subaccount-audit \ No newline at end of file From 37ffa4a1a156269f4422efef3ec8a908cf2140a2 Mon Sep 17 00:00:00 2001 From: I501080 Date: Mon, 16 Dec 2024 13:40:14 +0200 Subject: [PATCH 54/74] . --- api/common/consts.go | 2 +- controllers/secret_controller.go | 22 ++++--------- controllers/serviceinstance_controller.go | 11 +++++-- .../serviceinstance_controller_test.go | 4 +-- internal/utils/controller_util.go | 33 +++++++++++++++---- internal/utils/controller_util_test.go | 6 ++-- 6 files changed, 47 insertions(+), 31 deletions(-) diff --git a/api/common/consts.go b/api/common/consts.go index e863ceee..8a6a3860 100644 --- a/api/common/consts.go +++ b/api/common/consts.go @@ -4,7 +4,7 @@ const ( ManagedByBTPOperatorLabel = "services.cloud.sap.com/managed-by-sap-btp-operator" ClusterSecretLabel = "services.cloud.sap.com/cluster-secret" InstanceSecretRefLabel = "services.cloud.sap.com/secret-ref_" - WatchSecretLabel = "services.cloud.sap.com/watch-secret" + WatchSecretAnnotation = "services.cloud.sap.com/watch-secret-" NamespaceLabel = "_namespace" K8sNameLabel = "_k8sname" diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index a0a22011..bef70449 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -4,10 +4,8 @@ import ( "context" "fmt" "reflect" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -58,13 +56,6 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) return ctrl.Result{}, err } - if len(instances.Items) == 0 { - // No instances are using this secret - log.Info(fmt.Sprintf("no instances are using secret %s, removing watch label and finalizer", req.NamespacedName)) - delete(secret.Labels, common.WatchSecretLabel) - return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, secret, common.FinalizerName, common.SecretController) - } - for _, instance := range instances.Items { log.Info(fmt.Sprintf("waking up instance %s", instance.Name)) instance.Status.ForceReconcile = true @@ -83,25 +74,24 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) // SetupWithManager sets up the controller with the Manager. func (r *SecretReconciler) SetupWithManager(mgr ctrl.Manager) error { - labelSelector := labels.SelectorFromSet(map[string]string{common.WatchSecretLabel: "true"}) - labelPredicate := predicate.Funcs{ + predicates := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { - return labelSelector.Matches(labels.Set(e.ObjectNew.GetLabels())) && (isSecretDataChanged(e) || isSecretInDelete(e)) + return utils.IsSecretWatched(e.ObjectNew.GetAnnotations()) && (isSecretDataChanged(e) || isSecretInDelete(e)) }, CreateFunc: func(e event.CreateEvent) bool { - return labelSelector.Matches(labels.Set(e.Object.GetLabels())) + return utils.IsSecretWatched(e.Object.GetAnnotations()) }, DeleteFunc: func(e event.DeleteEvent) bool { - return labelSelector.Matches(labels.Set(e.Object.GetLabels())) + return utils.IsSecretWatched(e.Object.GetAnnotations()) }, GenericFunc: func(e event.GenericEvent) bool { - return labelSelector.Matches(labels.Set(e.Object.GetLabels())) + return utils.IsSecretWatched(e.Object.GetAnnotations()) }, } return ctrl.NewControllerManagedBy(mgr). For(&corev1.Secret{}). - WithEventFilter(labelPredicate). + WithEventFilter(predicates). WithOptions(controller.Options{MaxConcurrentReconciles: 1}). Complete(r) } diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index d207295b..df78d26b 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/types" "net/http" "strings" @@ -544,8 +545,8 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context if _, ok := serviceInstance.Labels[common.InstanceSecretRefLabel+secretUID]; !ok { log.Info(fmt.Sprintf("adding secret watch for secret %s", secret.Name)) instanceLabelsChanged = true - if err := utils.LabelSecretForWatch(ctx, r.Client, secret); err != nil { - log.Error(err, fmt.Sprintf("failed to mark secret for watch %s", secretUID)) + if err := utils.AddWatchForSecret(ctx, r.Client, secret, string(serviceInstance.UID)); err != nil { + log.Error(err, fmt.Sprintf("failed to mark secret for watch %s", secret.Name)) return nil, err } } @@ -555,8 +556,12 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context //sync instance labels for key := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretRefLabel) { - if _, ok := instanceLabels[key]; !ok { + if secretName, ok := instanceLabels[key]; !ok { instanceLabelsChanged = true + if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { + log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName)) + return nil, err + } } } else { // this label not related to secrets, add it diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 3b3d306a..d615091c 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1582,7 +1582,7 @@ func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Clien Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Eventually(func() bool { Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) - return len(paramsSecret.Labels[common.WatchSecretLabel]) == 0 && len(paramsSecret.Finalizers) == 0 + return len(paramsSecret.Labels[common.WatchSecretAnnotation]) == 0 && len(paramsSecret.Finalizers) == 0 }, timeout, interval).Should(BeTrue()) } else { Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) @@ -1591,7 +1591,7 @@ func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Clien Expect(instance.Labels[common.InstanceSecretRefLabel+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) } Expect(paramsSecret.Finalizers[0]).To(Equal(common.FinalizerName)) - Expect(paramsSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) + Expect(paramsSecret.Labels[common.WatchSecretAnnotation]).To(Equal("true")) } } diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 8d44d290..16c9852a 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -247,13 +247,34 @@ func serialize(value interface{}) ([]byte, format, error) { return data, JSON, nil } -func LabelSecretForWatch(ctx context.Context, k8sClient client.Client, secret *corev1.Secret) error { - if secret.Labels == nil { - secret.Labels = make(map[string]string) +func AddWatchForSecret(ctx context.Context, k8sClient client.Client, secret *corev1.Secret, instanceUid string) error { + if secret.Annotations == nil { + secret.Annotations = make(map[string]string) } - secret.Labels[common.WatchSecretLabel] = "true" - if !controllerutil.ContainsFinalizer(secret, common.FinalizerName) { - controllerutil.AddFinalizer(secret, common.FinalizerName) + secret.Annotations[common.WatchSecretAnnotation+instanceUid] = "true" + controllerutil.AddFinalizer(secret, common.FinalizerName) + + return k8sClient.Update(ctx, secret) +} + +func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKey apimachinerytypes.NamespacedName, instanceUid string) error { + secret := &corev1.Secret{} + if err := k8sClient.Get(ctx, secretKey, secret); err != nil { + return err + } + delete(secret.Annotations, common.WatchSecretAnnotation+instanceUid) + if !IsSecretWatched(secret.Annotations) { + controllerutil.RemoveFinalizer(secret, common.FinalizerName) } + return k8sClient.Update(ctx, secret) } + +func IsSecretWatched(secretAnnotations map[string]string) bool { + for key := range secretAnnotations { + if strings.HasPrefix(common.WatchSecretAnnotation, key) { + return true + } + } + return false +} diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index f80fb6df..dab41cba 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -203,7 +203,7 @@ var _ = Describe("Controller Util", func() { }) }) - Context("LabelSecretForWatch", func() { + Context("AddWatchForSecret", func() { It("should add the watch label to the secret if it is missing", func() { // Create a fake client @@ -220,7 +220,7 @@ var _ = Describe("Controller Util", func() { Expect(err).ToNot(HaveOccurred()) // Call the function - err = LabelSecretForWatch(ctx, k8sClient, secret) + err = AddWatchForSecret(ctx, k8sClient, secret, "") Expect(err).ToNot(HaveOccurred()) // Get the updated secret @@ -228,7 +228,7 @@ var _ = Describe("Controller Util", func() { err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) Expect(updatedSecret.Finalizers[0]).To(Equal(common.FinalizerName)) - Expect(updatedSecret.Labels[common.WatchSecretLabel]).To(Equal("true")) + Expect(updatedSecret.Labels[common.WatchSecretAnnotation]).To(Equal("true")) }) }) From ab9fbd749785eae63f6b79f1571decdf34550a81 Mon Sep 17 00:00:00 2001 From: I501080 Date: Mon, 16 Dec 2024 13:44:21 +0200 Subject: [PATCH 55/74] . --- controllers/secret_controller.go | 1 + controllers/serviceinstance_controller.go | 3 ++- internal/utils/controller_util.go | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index bef70449..84cabb38 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "reflect" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/event" diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index df78d26b..58a95932 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -20,10 +20,11 @@ import ( "context" "encoding/json" "fmt" - "k8s.io/apimachinery/pkg/types" "net/http" "strings" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/predicate" "github.com/SAP/sap-btp-service-operator/api/common" diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 16c9852a..88a888f1 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -247,22 +247,22 @@ func serialize(value interface{}) ([]byte, format, error) { return data, JSON, nil } -func AddWatchForSecret(ctx context.Context, k8sClient client.Client, secret *corev1.Secret, instanceUid string) error { +func AddWatchForSecret(ctx context.Context, k8sClient client.Client, secret *corev1.Secret, instanceUID string) error { if secret.Annotations == nil { secret.Annotations = make(map[string]string) } - secret.Annotations[common.WatchSecretAnnotation+instanceUid] = "true" + secret.Annotations[common.WatchSecretAnnotation+instanceUID] = "true" controllerutil.AddFinalizer(secret, common.FinalizerName) return k8sClient.Update(ctx, secret) } -func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKey apimachinerytypes.NamespacedName, instanceUid string) error { +func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKey apimachinerytypes.NamespacedName, instanceUID string) error { secret := &corev1.Secret{} if err := k8sClient.Get(ctx, secretKey, secret); err != nil { return err } - delete(secret.Annotations, common.WatchSecretAnnotation+instanceUid) + delete(secret.Annotations, common.WatchSecretAnnotation+instanceUID) if !IsSecretWatched(secret.Annotations) { controllerutil.RemoveFinalizer(secret, common.FinalizerName) } From fd679c5ffcc5b30b63f69967532fd4028efa7ef4 Mon Sep 17 00:00:00 2001 From: I501080 Date: Mon, 16 Dec 2024 13:48:50 +0200 Subject: [PATCH 56/74] . --- internal/utils/controller_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 88a888f1..cdf35b66 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -260,7 +260,7 @@ func AddWatchForSecret(ctx context.Context, k8sClient client.Client, secret *cor func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKey apimachinerytypes.NamespacedName, instanceUID string) error { secret := &corev1.Secret{} if err := k8sClient.Get(ctx, secretKey, secret); err != nil { - return err + return client.IgnoreNotFound(err) } delete(secret.Annotations, common.WatchSecretAnnotation+instanceUID) if !IsSecretWatched(secret.Annotations) { From 566bb9ed091b41a02769d8ed0b6838e546d530b3 Mon Sep 17 00:00:00 2001 From: I501080 Date: Mon, 16 Dec 2024 14:13:16 +0200 Subject: [PATCH 57/74] . --- controllers/serviceinstance_controller_test.go | 5 ++--- internal/utils/controller_util_test.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index d615091c..234f3aa5 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1573,7 +1573,6 @@ func updateInstanceStatus(ctx context.Context, instance *v1.ServiceInstance) *v1 } func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Client, paramsSecret *corev1.Secret, instances []*v1.ServiceInstance) { - if len(instances) == 0 { Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) // Add an data to wake up the secret @@ -1582,16 +1581,16 @@ func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Clien Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Eventually(func() bool { Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) - return len(paramsSecret.Labels[common.WatchSecretAnnotation]) == 0 && len(paramsSecret.Finalizers) == 0 + return !utils.IsSecretWatched(paramsSecret.Annotations) && len(paramsSecret.Finalizers) == 0 }, timeout, interval).Should(BeTrue()) } else { Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) for _, instance := range instances { Expect(k8sClient.Get(ctx, getResourceNamespacedName(instance), instance)).To(Succeed()) Expect(instance.Labels[common.InstanceSecretRefLabel+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) + Expect(paramsSecret.Annotations[common.WatchSecretAnnotation+string(instance.GetUID())]).To(Equal("true")) } Expect(paramsSecret.Finalizers[0]).To(Equal(common.FinalizerName)) - Expect(paramsSecret.Labels[common.WatchSecretAnnotation]).To(Equal("true")) } } diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index dab41cba..d646aa40 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -220,7 +220,7 @@ var _ = Describe("Controller Util", func() { Expect(err).ToNot(HaveOccurred()) // Call the function - err = AddWatchForSecret(ctx, k8sClient, secret, "") + err = AddWatchForSecret(ctx, k8sClient, secret, "123") Expect(err).ToNot(HaveOccurred()) // Get the updated secret @@ -228,7 +228,7 @@ var _ = Describe("Controller Util", func() { err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-secret", Namespace: "default"}, updatedSecret) Expect(err).ToNot(HaveOccurred()) Expect(updatedSecret.Finalizers[0]).To(Equal(common.FinalizerName)) - Expect(updatedSecret.Labels[common.WatchSecretAnnotation]).To(Equal("true")) + Expect(updatedSecret.Annotations[common.WatchSecretAnnotation+"123"]).To(Equal("true")) }) }) From 32bb602a27db098823570d1f2a633c1a73d822a7 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 16 Dec 2024 15:04:53 +0200 Subject: [PATCH 58/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/secret_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index 84cabb38..63ff9347 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -77,7 +77,7 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) func (r *SecretReconciler) SetupWithManager(mgr ctrl.Manager) error { predicates := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { - return utils.IsSecretWatched(e.ObjectNew.GetAnnotations()) && (isSecretDataChanged(e) || isSecretInDelete(e)) + return (utils.IsSecretWatched(e.ObjectNew.GetAnnotations()) && isSecretDataChanged(e)) || isSecretInDelete(e) }, CreateFunc: func(e event.CreateEvent) bool { return utils.IsSecretWatched(e.Object.GetAnnotations()) From 58074193ccb83bae0e81ffaf0eb48f97cf4c8886 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 16 Dec 2024 17:01:24 +0200 Subject: [PATCH 59/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 12 +++++++++++- .../serviceinstance_controller_test.go | 19 +++++++------------ internal/utils/controller_util.go | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 58a95932..df3835b8 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -294,7 +294,17 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI log.Info("Deleting instance async") return r.handleAsyncDelete(ctx, serviceInstance, operationURL) } - + if serviceInstance.Labels != nil { + for key := range serviceInstance.Labels { + if strings.HasPrefix(key, common.InstanceSecretRefLabel) { + if secretName, ok := serviceInstance.Labels[key]; !ok { + if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { + log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName)) + } + } + } + } + } log.Info("Instance was deleted successfully, removing finalizer") // remove our finalizer from the list and update it. return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()) diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 234f3aa5..4ea16149 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1303,8 +1303,8 @@ var _ = Describe("ServiceInstance controller", func() { paramsSecret.Data = credentialsMap Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Eventually(func() bool { - return fakeClient.UpdateInstanceCallCount() == 1 - }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + return fakeClient.UpdateInstanceCallCount() >= 1 + }, timeout, interval).Should(BeTrue(), "expected condition was not met") _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) @@ -1327,8 +1327,8 @@ var _ = Describe("ServiceInstance controller", func() { paramsSecret.Data = credentialsMap Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Eventually(func() bool { - return fakeClient.UpdateInstanceCallCount() == 1 - }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + return fakeClient.UpdateInstanceCallCount() >= 1 + }, timeout, interval).Should(BeTrue(), "expected condition was not met") _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) @@ -1372,7 +1372,7 @@ var _ = Describe("ServiceInstance controller", func() { Expect(k8sClient.Update(ctx, anotherSecret)).To(Succeed()) Eventually(func() bool { return fakeClient.UpdateInstanceCallCount() == 1 - }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + }, timeout, interval).Should(BeTrue(), "expected condition was not met") _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) @@ -1423,7 +1423,7 @@ var _ = Describe("ServiceInstance controller", func() { serviceInstance = updateInstance(ctx, serviceInstance) Eventually(func() bool { return fakeClient.UpdateInstanceCallCount() > 1 - }, timeout*3, interval).Should(BeTrue(), + }, timeout, interval).Should(BeTrue(), "dkd", ) _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(1) @@ -1451,7 +1451,7 @@ var _ = Describe("ServiceInstance controller", func() { Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Eventually(func() bool { return fakeClient.UpdateInstanceCallCount() == 2 - }, timeout*3, interval).Should(BeTrue(), "expected condition was not met") + }, timeout, interval).Should(BeTrue(), "expected condition was not met") _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"new-secret-value\""}) @@ -1574,11 +1574,6 @@ func updateInstanceStatus(ctx context.Context, instance *v1.ServiceInstance) *v1 func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Client, paramsSecret *corev1.Secret, instances []*v1.ServiceInstance) { if len(instances) == 0 { - Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) - // Add an data to wake up the secret - paramsSecret.Data["secret-parameter2"] = []byte("{\"secret-key\":\"new-secret-value\"}") - // Update the secret in the Kubernetes cluster - Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Eventually(func() bool { Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) return !utils.IsSecretWatched(paramsSecret.Annotations) && len(paramsSecret.Finalizers) == 0 diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index cdf35b66..c5bd82a5 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -272,7 +272,7 @@ func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKe func IsSecretWatched(secretAnnotations map[string]string) bool { for key := range secretAnnotations { - if strings.HasPrefix(common.WatchSecretAnnotation, key) { + if strings.HasPrefix(key, common.WatchSecretAnnotation) { return true } } From ba0e3f29fe5786351b45b2663e8b269525691a09 Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 18 Dec 2024 13:02:48 +0200 Subject: [PATCH 60/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 22 +++++++++---------- .../serviceinstance_controller_test.go | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 31e3bf65..467bc347 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -258,6 +258,15 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI log.Info("deleting instance") if controllerutil.ContainsFinalizer(serviceInstance, common.FinalizerName) { + if serviceInstance.Labels != nil { + for key, secretName := range serviceInstance.Labels { + if strings.HasPrefix(key, common.InstanceSecretRefLabel) { + if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { + log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName)) + } + } + } + } smClient, err := r.GetSMClient(ctx, serviceInstance) if err != nil { log.Error(err, "failed to get sm client") @@ -295,17 +304,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI log.Info("Deleting instance async") return r.handleAsyncDelete(ctx, serviceInstance, operationURL) } - if serviceInstance.Labels != nil { - for key := range serviceInstance.Labels { - if strings.HasPrefix(key, common.InstanceSecretRefLabel) { - if secretName, ok := serviceInstance.Labels[key]; !ok { - if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { - log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName)) - } - } - } - } - } + log.Info("Instance was deleted successfully, removing finalizer") // remove our finalizer from the list and update it. return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()) @@ -592,7 +591,6 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool { log := utils.GetLogger(ctx) - if serviceInstance.Status.ForceReconcile { log.Info("instance is not in final state, ForceReconcile is true") return false diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 4ea16149..48881d76 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1489,7 +1489,7 @@ var _ = Describe("ServiceInstance controller", func() { Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(0)) }) - It("should not update instance with the secret change after removing SubscribeToSecretChanges", func() { + FIt("should not update instance with the secret change after removing SubscribeToSecretChanges", func() { instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) From ece2c30b655b898d4097ce72add11d50f851d311 Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 18 Dec 2024 13:05:49 +0200 Subject: [PATCH 61/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 4 ++-- controllers/serviceinstance_controller_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 467bc347..36d78768 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -565,7 +565,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } //sync instance labels - for key := range serviceInstance.Labels { + for key, value := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretRefLabel) { if secretName, ok := instanceLabels[key]; !ok { instanceLabelsChanged = true @@ -576,7 +576,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context } } else { // this label not related to secrets, add it - instanceLabels[key] = serviceInstance.Labels[key] + instanceLabels[key] = value } } if instanceLabelsChanged { diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 48881d76..4ea16149 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1489,7 +1489,7 @@ var _ = Describe("ServiceInstance controller", func() { Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(0)) }) - FIt("should not update instance with the secret change after removing SubscribeToSecretChanges", func() { + It("should not update instance with the secret change after removing SubscribeToSecretChanges", func() { instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) From 7aaf27dba01075635d226f259f1117ead29a24c6 Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 18 Dec 2024 13:14:12 +0200 Subject: [PATCH 62/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 36d78768..7a62ee60 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -567,10 +567,10 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context //sync instance labels for key, value := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretRefLabel) { - if secretName, ok := instanceLabels[key]; !ok { + if _, ok := instanceLabels[key]; !ok { instanceLabelsChanged = true - if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { - log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName)) + if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: value, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { + log.Error(err, fmt.Sprintf("failed to unwatch secret %s", value)) return nil, err } } From 9fcbeed428acfe0ddb0e643b2a2c2428fc5b2f7d Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 18 Dec 2024 16:43:34 +0200 Subject: [PATCH 63/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- controllers/serviceinstance_controller.go | 4 ++-- controllers/serviceinstance_controller_test.go | 5 +++++ internal/utils/controller_util.go | 14 ++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 7a62ee60..e1d62e40 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -261,7 +261,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI if serviceInstance.Labels != nil { for key, secretName := range serviceInstance.Labels { if strings.HasPrefix(key, common.InstanceSecretRefLabel) { - if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { + if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID), key); err != nil { log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName)) } } @@ -569,7 +569,7 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context if strings.HasPrefix(key, common.InstanceSecretRefLabel) { if _, ok := instanceLabels[key]; !ok { instanceLabelsChanged = true - if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: value, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { + if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: value, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID), key); err != nil { log.Error(err, fmt.Sprintf("failed to unwatch secret %s", value)) return nil, err } diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 4ea16149..681106f6 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1470,6 +1470,11 @@ var _ = Describe("ServiceInstance controller", func() { deleteAndWait(ctx, paramsSecret) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.UpdateInProgress, "secrets \"instance-params-secret\" not found") + + paramsSecret = createParamsSecret(ctx, "instance-params-secret", testNamespace) + waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Updated, "") + checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) + }) }) When("secret updated and instance don't watch secret", func() { diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index c5bd82a5..bad8c424 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -257,17 +257,19 @@ func AddWatchForSecret(ctx context.Context, k8sClient client.Client, secret *cor return k8sClient.Update(ctx, secret) } -func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKey apimachinerytypes.NamespacedName, instanceUID string) error { +func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKey apimachinerytypes.NamespacedName, instanceUID string, key string) error { secret := &corev1.Secret{} if err := k8sClient.Get(ctx, secretKey, secret); err != nil { return client.IgnoreNotFound(err) } - delete(secret.Annotations, common.WatchSecretAnnotation+instanceUID) - if !IsSecretWatched(secret.Annotations) { - controllerutil.RemoveFinalizer(secret, common.FinalizerName) + if key == common.WatchSecretAnnotation+instanceUID { + delete(secret.Annotations, common.WatchSecretAnnotation+instanceUID) + if !IsSecretWatched(secret.Annotations) { + controllerutil.RemoveFinalizer(secret, common.FinalizerName) + } + return k8sClient.Update(ctx, secret) } - - return k8sClient.Update(ctx, secret) + return nil } func IsSecretWatched(secretAnnotations map[string]string) bool { From 11fd89cbd145335ae21371110c4b2a974f567357 Mon Sep 17 00:00:00 2001 From: i065450 Date: Wed, 18 Dec 2024 17:15:19 +0200 Subject: [PATCH 64/74] [SAPBTPCFS-15469] Update Service Instance on change of 'parametersFrom' secret --- internal/utils/controller_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index bad8c424..a817c3c4 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -262,7 +262,7 @@ func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKe if err := k8sClient.Get(ctx, secretKey, secret); err != nil { return client.IgnoreNotFound(err) } - if key == common.WatchSecretAnnotation+instanceUID { + if key == common.InstanceSecretRefLabel+string(secret.UID) { delete(secret.Annotations, common.WatchSecretAnnotation+instanceUID) if !IsSecretWatched(secret.Annotations) { controllerutil.RemoveFinalizer(secret, common.FinalizerName) From fecb04c79e2e87b764d46392511c1b83e294dbb5 Mon Sep 17 00:00:00 2001 From: I501080 Date: Thu, 19 Dec 2024 12:14:35 +0200 Subject: [PATCH 65/74] change secretUID to secretName --- api/v1/serviceinstance_types.go | 2 +- api/v1/serviceinstance_types_test.go | 2 +- ...ervices.cloud.sap.com_servicebindings.yaml | 42 +++++++++++++--- ...rvices.cloud.sap.com_serviceinstances.yaml | 42 +++++++++++++--- config/rbac/role.yaml | 31 +++++++++++- controllers/secret_controller.go | 6 +-- controllers/servicebinding_controller_test.go | 5 +- controllers/serviceinstance_controller.go | 50 ++++++++++--------- .../serviceinstance_controller_test.go | 16 +++--- internal/utils/controller_util.go | 31 +++++++----- internal/utils/controller_util_test.go | 4 +- 11 files changed, 162 insertions(+), 69 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 4c391a85..038a7975 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -212,7 +212,7 @@ func (si *ServiceInstance) GetShared() bool { return si.Spec.Shared != nil && *si.Spec.Shared } -func (si *ServiceInstance) IsSubscribedToSecretChange() bool { +func (si *ServiceInstance) IsSubscribedToParamSecretsChanges() bool { return si.Spec.SubscribeToSecretChanges != nil && *si.Spec.SubscribeToSecretChanges } diff --git a/api/v1/serviceinstance_types_test.go b/api/v1/serviceinstance_types_test.go index 15fb011e..20be3733 100644 --- a/api/v1/serviceinstance_types_test.go +++ b/api/v1/serviceinstance_types_test.go @@ -131,7 +131,7 @@ var _ = Describe("Service Instance Type Test", func() { It("should update SubscribeToSecretChanges", func() { instance.Spec.SubscribeToSecretChanges = &[]bool{true}[0] - Expect(instance.IsSubscribedToSecretChange()).To(BeTrue()) + Expect(instance.IsSubscribedToParamSecretsChanges()).To(BeTrue()) }) It("should return correct spec hash", func() { diff --git a/config/crd/bases/services.cloud.sap.com_servicebindings.yaml b/config/crd/bases/services.cloud.sap.com_servicebindings.yaml index 7a0fb9eb..0cfb92f0 100644 --- a/config/crd/bases/services.cloud.sap.com_servicebindings.yaml +++ b/config/crd/bases/services.cloud.sap.com_servicebindings.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.1 + controller-gen.kubebuilder.io/version: v0.15.0 name: servicebindings.services.cloud.sap.com spec: group: services.cloud.sap.com @@ -82,6 +82,7 @@ spec: description: |- Parameters for the binding. + The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -198,8 +199,16 @@ spec: conditions: description: Service binding conditions items: - description: Condition contains details for one aspect of the current - state of this API Resource. + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: description: |- @@ -240,7 +249,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -348,6 +362,7 @@ spec: description: |- Parameters for the binding. + The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -449,8 +464,16 @@ spec: conditions: description: Service binding conditions items: - description: Condition contains details for one aspect of the current - state of this API Resource. + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: description: |- @@ -491,7 +514,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index eef2bb72..5ebcdf45 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.1 + controller-gen.kubebuilder.io/version: v0.15.0 name: serviceinstances.services.cloud.sap.com spec: group: services.cloud.sap.com @@ -89,6 +89,7 @@ spec: description: |- Provisioning parameters for the instance. + The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -184,8 +185,16 @@ spec: conditions: description: Service instance conditions items: - description: Condition contains details for one aspect of the current - state of this API Resource. + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: description: |- @@ -226,7 +235,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -338,6 +352,7 @@ spec: description: |- Provisioning parameters for the instance. + The Parameters field is NOT secret or secured in any way and should NEVER be used to hold sensitive information. To set parameters that contain secret information, you should ALWAYS store that information @@ -429,8 +444,16 @@ spec: conditions: description: Service instance conditions items: - description: Condition contains details for one aspect of the current - state of this API Resource. + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" properties: lastTransitionTime: description: |- @@ -471,7 +494,12 @@ spec: - Unknown type: string type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 74f1babd..de9857dc 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -17,6 +17,17 @@ rules: - "" resources: - events + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: - secrets verbs: - create @@ -30,7 +41,6 @@ rules: - services.cloud.sap.com resources: - servicebindings - - serviceinstances verbs: - create - delete @@ -43,6 +53,25 @@ rules: - services.cloud.sap.com resources: - servicebindings/status + verbs: + - get + - patch + - update +- apiGroups: + - services.cloud.sap.com + resources: + - serviceinstances + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - services.cloud.sap.com + resources: - serviceinstances/status verbs: - get diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index 63ff9347..f1c1fcb6 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -50,9 +50,9 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) return ctrl.Result{}, client.IgnoreNotFound(err) } - var instances v1.ServiceInstanceList - labelSelector := client.MatchingLabels{common.InstanceSecretRefLabel + string(secret.GetUID()): secret.Name} - if err := r.Client.List(ctx, &instances, labelSelector); err != nil { + instances := &v1.ServiceInstanceList{} + labelSelector := client.MatchingLabels{utils.GetLabelKeyForInstanceSecret(secret.Name): secret.Name} + if err := r.Client.List(ctx, instances, client.InNamespace(secret.Namespace), labelSelector); err != nil { log.Error(err, "failed to list service instances") return ctrl.Result{}, err } diff --git a/controllers/servicebinding_controller_test.go b/controllers/servicebinding_controller_test.go index c7003100..f223b29f 100644 --- a/controllers/servicebinding_controller_test.go +++ b/controllers/servicebinding_controller_test.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" "errors" - "github.com/lithammer/dedent" - authv1 "k8s.io/api/authentication/v1" "net/http" "strings" + "github.com/lithammer/dedent" + authv1 "k8s.io/api/authentication/v1" + "github.com/SAP/sap-btp-service-operator/api/common" "github.com/SAP/sap-btp-service-operator/internal/utils" "k8s.io/utils/pointer" diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index e1d62e40..d1151e89 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -258,15 +258,15 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI log.Info("deleting instance") if controllerutil.ContainsFinalizer(serviceInstance, common.FinalizerName) { - if serviceInstance.Labels != nil { - for key, secretName := range serviceInstance.Labels { - if strings.HasPrefix(key, common.InstanceSecretRefLabel) { - if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID), key); err != nil { - log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName)) - } + for key, secretName := range serviceInstance.Labels { + if strings.HasPrefix(key, common.InstanceSecretRefLabel) { + if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { + log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName)) + return ctrl.Result{}, err } } } + smClient, err := r.GetSMClient(ctx, serviceInstance) if err != nil { log.Error(err, "failed to get sm client") @@ -547,40 +547,42 @@ func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context return nil, err } instanceLabelsChanged := false - instanceLabels := make(map[string]string) - if serviceInstance.IsSubscribedToSecretChange() { + newInstanceLabels := make(map[string]string) + if serviceInstance.IsSubscribedToParamSecretsChanges() { // find all new secrets on the instance - for secretUID := range paramSecrets { - secret := paramSecrets[secretUID] - instanceLabels[common.InstanceSecretRefLabel+secretUID] = secret.Name - if _, ok := serviceInstance.Labels[common.InstanceSecretRefLabel+secretUID]; !ok { - log.Info(fmt.Sprintf("adding secret watch for secret %s", secret.Name)) + for _, secret := range paramSecrets { + labelKey := utils.GetLabelKeyForInstanceSecret(secret.Name) + newInstanceLabels[labelKey] = secret.Name + if _, ok := serviceInstance.Labels[labelKey]; !ok { instanceLabelsChanged = true - if err := utils.AddWatchForSecret(ctx, r.Client, secret, string(serviceInstance.UID)); err != nil { - log.Error(err, fmt.Sprintf("failed to mark secret for watch %s", secret.Name)) - return nil, err - } } + + if err := utils.AddWatchForSecretIfNeeded(ctx, r.Client, secret, string(serviceInstance.UID)); err != nil { + log.Error(err, fmt.Sprintf("failed to mark secret for watch %s", secret.Name)) + return nil, err + } + } } //sync instance labels - for key, value := range serviceInstance.Labels { - if strings.HasPrefix(key, common.InstanceSecretRefLabel) { - if _, ok := instanceLabels[key]; !ok { + for labelKey, labelValue := range serviceInstance.Labels { + if strings.HasPrefix(labelKey, common.InstanceSecretRefLabel) { + if _, ok := newInstanceLabels[labelKey]; !ok { + log.Info(fmt.Sprintf("params secret named %s was removed, unwatching it", labelValue)) instanceLabelsChanged = true - if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: value, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID), key); err != nil { - log.Error(err, fmt.Sprintf("failed to unwatch secret %s", value)) + if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: labelValue, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil { + log.Error(err, fmt.Sprintf("failed to unwatch secret %s", labelValue)) return nil, err } } } else { // this label not related to secrets, add it - instanceLabels[key] = value + newInstanceLabels[labelKey] = labelValue } } if instanceLabelsChanged { - serviceInstance.Labels = instanceLabels + serviceInstance.Labels = newInstanceLabels log.Info("updating instance with secret labels") return instanceParameters, r.Client.Update(ctx, serviceInstance) } diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 681106f6..808a0a8e 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1337,7 +1337,7 @@ var _ = Describe("ServiceInstance controller", func() { Expect(paramsSecret.Labels["label"]).To(Equal("value")) }) - It("create instance before secret", func() { + It("create instance before secret should succeed eventually", func() { newInstanceSpec := v1.ServiceInstanceSpec{ ExternalName: fakeInstanceExternalName, ServicePlanName: fakePlanName, @@ -1379,7 +1379,7 @@ var _ = Describe("ServiceInstance controller", func() { deleteAndWait(ctx, serviceInstance) checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{}) }) - It("update instance parameterFrom", func() { + It("update instance parameterFrom should watch new secrets", func() { credentialsMap := make(map[string][]byte) credentialsMap["secret-parameter2"] = []byte("{\"secret-key2\":\"secret-value2\"}") anotherSecret = createSecret(ctx, "instance-params-secret-new", testNamespace, credentialsMap) @@ -1423,18 +1423,16 @@ var _ = Describe("ServiceInstance controller", func() { serviceInstance = updateInstance(ctx, serviceInstance) Eventually(func() bool { return fakeClient.UpdateInstanceCallCount() > 1 - }, timeout, interval).Should(BeTrue(), - "dkd", - ) + }, timeout, interval).Should(BeTrue()) _, smInstance, _, _, _, _, _ = fakeClient.UpdateInstanceArgsForCall(1) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) checkSecretAnnotationsAndLabels(ctx, k8sClient, anotherSecret, []*v1.ServiceInstance{}) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) - Expect(serviceInstance.Labels[common.InstanceSecretRefLabel+string(anotherSecret.GetUID())]).To(BeEmpty()) + Expect(serviceInstance.Labels[utils.GetLabelKeyForInstanceSecret(anotherSecret.Name)]).To(BeEmpty()) }) - It("should update two instances with the secret change", func() { + It("when watched secret changed, referencing instances should be updated", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) @@ -1463,7 +1461,7 @@ var _ = Describe("ServiceInstance controller", func() { checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) }) - It("delete of secret when secret is watched", func() { + It("instance should fail when watched secret is deleted", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) @@ -1587,7 +1585,7 @@ func checkSecretAnnotationsAndLabels(ctx context.Context, k8sClient client.Clien Expect(k8sClient.Get(ctx, getResourceNamespacedName(paramsSecret), paramsSecret)).To(Succeed()) for _, instance := range instances { Expect(k8sClient.Get(ctx, getResourceNamespacedName(instance), instance)).To(Succeed()) - Expect(instance.Labels[common.InstanceSecretRefLabel+string(paramsSecret.GetUID())]).To(Equal(paramsSecret.Name)) + Expect(instance.Labels[utils.GetLabelKeyForInstanceSecret(paramsSecret.Name)]).To(Equal(paramsSecret.Name)) Expect(paramsSecret.Annotations[common.WatchSecretAnnotation+string(instance.GetUID())]).To(Equal("true")) } Expect(paramsSecret.Finalizers[0]).To(Equal(common.FinalizerName)) diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index a817c3c4..be8a79ae 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -247,29 +247,32 @@ func serialize(value interface{}) ([]byte, format, error) { return data, JSON, nil } -func AddWatchForSecret(ctx context.Context, k8sClient client.Client, secret *corev1.Secret, instanceUID string) error { +func AddWatchForSecretIfNeeded(ctx context.Context, k8sClient client.Client, secret *corev1.Secret, instanceUID string) error { + log := GetLogger(ctx) if secret.Annotations == nil { secret.Annotations = make(map[string]string) } - secret.Annotations[common.WatchSecretAnnotation+instanceUID] = "true" - controllerutil.AddFinalizer(secret, common.FinalizerName) + if len(secret.Annotations[common.WatchSecretAnnotation+string(instanceUID)]) == 0 { + log.Info(fmt.Sprintf("adding secret watch for secret %s", secret.Name)) + secret.Annotations[common.WatchSecretAnnotation+instanceUID] = "true" + controllerutil.AddFinalizer(secret, common.FinalizerName) + return k8sClient.Update(ctx, secret) + } - return k8sClient.Update(ctx, secret) + return nil } -func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKey apimachinerytypes.NamespacedName, instanceUID string, key string) error { +func RemoveWatchForSecret(ctx context.Context, k8sClient client.Client, secretKey apimachinerytypes.NamespacedName, instanceUID string) error { secret := &corev1.Secret{} if err := k8sClient.Get(ctx, secretKey, secret); err != nil { return client.IgnoreNotFound(err) } - if key == common.InstanceSecretRefLabel+string(secret.UID) { - delete(secret.Annotations, common.WatchSecretAnnotation+instanceUID) - if !IsSecretWatched(secret.Annotations) { - controllerutil.RemoveFinalizer(secret, common.FinalizerName) - } - return k8sClient.Update(ctx, secret) + + delete(secret.Annotations, common.WatchSecretAnnotation+instanceUID) + if !IsSecretWatched(secret.Annotations) { + controllerutil.RemoveFinalizer(secret, common.FinalizerName) } - return nil + return k8sClient.Update(ctx, secret) } func IsSecretWatched(secretAnnotations map[string]string) bool { @@ -280,3 +283,7 @@ func IsSecretWatched(secretAnnotations map[string]string) bool { } return false } + +func GetLabelKeyForInstanceSecret(secretName string) string { + return common.InstanceSecretRefLabel + secretName +} diff --git a/internal/utils/controller_util_test.go b/internal/utils/controller_util_test.go index d646aa40..e7610bc6 100644 --- a/internal/utils/controller_util_test.go +++ b/internal/utils/controller_util_test.go @@ -203,7 +203,7 @@ var _ = Describe("Controller Util", func() { }) }) - Context("AddWatchForSecret", func() { + Context("AddWatchForSecretIfNeeded", func() { It("should add the watch label to the secret if it is missing", func() { // Create a fake client @@ -220,7 +220,7 @@ var _ = Describe("Controller Util", func() { Expect(err).ToNot(HaveOccurred()) // Call the function - err = AddWatchForSecret(ctx, k8sClient, secret, "123") + err = AddWatchForSecretIfNeeded(ctx, k8sClient, secret, "123") Expect(err).ToNot(HaveOccurred()) // Get the updated secret From a24b64064cfb1122ad0160a982e5a5658ce4a4a8 Mon Sep 17 00:00:00 2001 From: I501080 Date: Sun, 22 Dec 2024 13:16:07 +0200 Subject: [PATCH 66/74] merge main --- controllers/secret_controller.go | 9 ++++++--- controllers/servicebinding_controller.go | 4 ++-- controllers/servicebinding_controller_test.go | 2 -- controllers/serviceinstance_controller.go | 6 +++--- internal/utils/controller_util.go | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index f1c1fcb6..5dd7eca2 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -37,7 +37,7 @@ type SecretReconciler struct { func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { log := r.Log.WithValues("secret", req.NamespacedName).WithValues("correlation_id", uuid.New().String()) ctx = context.WithValue(ctx, utils.LogKey{}, log) - log.Info(fmt.Sprintf("reconciling secret %s", req.NamespacedName)) + log.Info(fmt.Sprintf("reconciling params secret %s", req.NamespacedName)) // Fetch the Secret secret := &corev1.Secret{} if err := r.Get(ctx, req.NamespacedName, secret); err != nil { @@ -58,7 +58,7 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) } for _, instance := range instances.Items { - log.Info(fmt.Sprintf("waking up instance %s", instance.Name)) + log.Info(fmt.Sprintf("waking up referencing instance %s", instance.Name)) instance.Status.ForceReconcile = true err := utils.UpdateStatus(ctx, r.Client, &instance) if err != nil { @@ -67,9 +67,12 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) } if utils.IsMarkedForDeletion(secret.ObjectMeta) { - return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, secret, common.FinalizerName, common.SecretController) + log.Info("secret is marked for deletion, removing finalizer") + controllerutil.RemoveFinalizer(secret, common.FinalizerName) + return ctrl.Result{}, r.Update(ctx, secret) } + log.Info("finished reconciling params secret") return reconcile.Result{}, nil } diff --git a/controllers/servicebinding_controller.go b/controllers/servicebinding_controller.go index d49ffaf1..ace1a51b 100644 --- a/controllers/servicebinding_controller.go +++ b/controllers/servicebinding_controller.go @@ -308,7 +308,7 @@ func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v } log.Info("Binding does not exists in SM, removing finalizer") - if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName, serviceBinding.GetControllerName()); err != nil { + if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -779,7 +779,7 @@ func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Co return ctrl.Result{}, err } - return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName, serviceBinding.GetControllerName()) + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName) } func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) { diff --git a/controllers/servicebinding_controller_test.go b/controllers/servicebinding_controller_test.go index 0cc338e0..451232b2 100644 --- a/controllers/servicebinding_controller_test.go +++ b/controllers/servicebinding_controller_test.go @@ -12,8 +12,6 @@ import ( "github.com/SAP/sap-btp-service-operator/api/common" "github.com/SAP/sap-btp-service-operator/internal/utils" - "github.com/lithammer/dedent" - authv1 "k8s.io/api/authentication/v1" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" diff --git a/controllers/serviceinstance_controller.go b/controllers/serviceinstance_controller.go index 09d6fc4a..0e037aad 100644 --- a/controllers/serviceinstance_controller.go +++ b/controllers/serviceinstance_controller.go @@ -284,7 +284,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance) } log.Info("instance does not exists in SM, removing finalizer") - return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()) + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName) } if len(serviceInstance.Status.OperationURL) > 0 && serviceInstance.Status.OperationType == smClientTypes.DELETE { @@ -306,7 +306,7 @@ func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceI log.Info("Instance was deleted successfully, removing finalizer") // remove our finalizer from the list and update it. - return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()) + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName) } return ctrl.Result{}, nil } @@ -412,7 +412,7 @@ func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *v serviceInstance.Status.Ready = metav1.ConditionTrue } else if serviceInstance.Status.OperationType == smClientTypes.DELETE { // delete was successful - remove our finalizer from the list and update it. - if err := utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName, serviceInstance.GetControllerName()); err != nil { + if err := utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName); err != nil { return ctrl.Result{}, err } } diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index be8a79ae..2bf3f146 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -44,7 +44,7 @@ type format string type LogKey struct { } -func RemoveFinalizer(ctx context.Context, k8sClient client.Client, object client.Object, finalizerName string, controllerName common.ControllerName) error { +func RemoveFinalizer(ctx context.Context, k8sClient client.Client, object common.SAPBTPResource, finalizerName string) error { log := GetLogger(ctx) if controllerutil.ContainsFinalizer(object, finalizerName) { log.Info(fmt.Sprintf("removing finalizer %s", finalizerName)) @@ -58,7 +58,7 @@ func RemoveFinalizer(ctx context.Context, k8sClient client.Client, object client return fmt.Errorf("failed to remove the finalizer '%s'. Error: %v", finalizerName, err) } } - log.Info(fmt.Sprintf("removed finalizer %s from %s", finalizerName, controllerName)) + log.Info(fmt.Sprintf("removed finalizer %s from %s", finalizerName, object.GetControllerName())) return nil } return nil From bd10c031ad2907bbc144161b124042a453b9888f Mon Sep 17 00:00:00 2001 From: I501080 Date: Sun, 22 Dec 2024 13:21:48 +0200 Subject: [PATCH 67/74] review --- controllers/secret_controller.go | 3 +-- internal/utils/controller_util.go | 19 ++++--------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/controllers/secret_controller.go b/controllers/secret_controller.go index 5dd7eca2..60a7f955 100644 --- a/controllers/secret_controller.go +++ b/controllers/secret_controller.go @@ -68,8 +68,7 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req reconcile.Request) if utils.IsMarkedForDeletion(secret.ObjectMeta) { log.Info("secret is marked for deletion, removing finalizer") - controllerutil.RemoveFinalizer(secret, common.FinalizerName) - return ctrl.Result{}, r.Update(ctx, secret) + return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, secret, common.FinalizerName) } log.Info("finished reconciling params secret") diff --git a/internal/utils/controller_util.go b/internal/utils/controller_util.go index 2bf3f146..04d3b7ec 100644 --- a/internal/utils/controller_util.go +++ b/internal/utils/controller_util.go @@ -44,22 +44,11 @@ type format string type LogKey struct { } -func RemoveFinalizer(ctx context.Context, k8sClient client.Client, object common.SAPBTPResource, finalizerName string) error { +func RemoveFinalizer(ctx context.Context, k8sClient client.Client, object client.Object, finalizerName string) error { log := GetLogger(ctx) - if controllerutil.ContainsFinalizer(object, finalizerName) { - log.Info(fmt.Sprintf("removing finalizer %s", finalizerName)) - controllerutil.RemoveFinalizer(object, finalizerName) - if err := k8sClient.Update(ctx, object); err != nil { - if err := k8sClient.Get(ctx, apimachinerytypes.NamespacedName{Name: object.GetName(), Namespace: object.GetNamespace()}, object); err != nil { - return client.IgnoreNotFound(err) - } - controllerutil.RemoveFinalizer(object, finalizerName) - if err := k8sClient.Update(ctx, object); err != nil { - return fmt.Errorf("failed to remove the finalizer '%s'. Error: %v", finalizerName, err) - } - } - log.Info(fmt.Sprintf("removed finalizer %s from %s", finalizerName, object.GetControllerName())) - return nil + if controllerutil.RemoveFinalizer(object, finalizerName) { + log.Info(fmt.Sprintf("removing finalizer %s from resource %s named '%s' in namespace '%s'", finalizerName, object.GetObjectKind(), object.GetName(), object.GetNamespace())) + return k8sClient.Update(ctx, object) } return nil } From 95c867cc808e6374a50a2d10b2a84956fce86fc8 Mon Sep 17 00:00:00 2001 From: I501080 Date: Sun, 22 Dec 2024 13:38:10 +0200 Subject: [PATCH 68/74] fix test --- controllers/servicebinding_controller_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/controllers/servicebinding_controller_test.go b/controllers/servicebinding_controller_test.go index 451232b2..ee6703a1 100644 --- a/controllers/servicebinding_controller_test.go +++ b/controllers/servicebinding_controller_test.go @@ -583,7 +583,10 @@ var _ = Describe("ServiceBinding controller", func() { createdBinding, err := createBindingWithoutAssertions(ctx, bindingName, bindingTestNamespace, instanceName, "", "binding-external-name", "", false) Expect(err).ToNot(HaveOccurred()) waitForResourceCondition(ctx, createdBinding, common.ConditionSucceeded, metav1.ConditionFalse, common.Blocked, "") - Expect(utils.RemoveFinalizer(ctx, k8sClient, createdInstance, "fake/finalizer")).To(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, getResourceNamespacedName(createdInstance), createdInstance) + return err == nil && utils.RemoveFinalizer(ctx, k8sClient, createdInstance, "fake/finalizer") == nil + }, timeout, interval).Should(BeTrue()) }) }) From 2ad240aab91127953a6a18eebe090a1f0787a34c Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 23 Dec 2024 10:14:14 +0200 Subject: [PATCH 69/74] Add option for adding annotations --- api/v1/serviceinstance_types.go | 4 ++-- api/v1/serviceinstance_types_test.go | 4 ++-- api/v1/suite_test.go | 2 +- api/v1/zz_generated.deepcopy.go | 4 ++-- .../services.cloud.sap.com_serviceinstances.yaml | 2 +- controllers/serviceinstance_controller_test.go | 14 +++++++------- sapbtp-operator-charts/templates/crd.yml | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index 038a7975..afc4f088 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -79,7 +79,7 @@ type ServiceInstanceSpec struct { // indicate instance will update on secrets from parametersFrom change // +optional - SubscribeToSecretChanges *bool `json:"subscribeToSecretChanges,omitempty"` + WatchParameterFromChanges *bool `json:"watchParameterFromChanges,omitempty"` // List of custom tags describing the ServiceInstance, will be copied to `ServiceBinding` secret in the key called `tags`. // +optional @@ -213,7 +213,7 @@ func (si *ServiceInstance) GetShared() bool { } func (si *ServiceInstance) IsSubscribedToParamSecretsChanges() bool { - return si.Spec.SubscribeToSecretChanges != nil && *si.Spec.SubscribeToSecretChanges + return si.Spec.WatchParameterFromChanges != nil && *si.Spec.WatchParameterFromChanges } func (si *ServiceInstance) GetSpecHash() string { diff --git a/api/v1/serviceinstance_types_test.go b/api/v1/serviceinstance_types_test.go index 20be3733..d41331ea 100644 --- a/api/v1/serviceinstance_types_test.go +++ b/api/v1/serviceinstance_types_test.go @@ -129,8 +129,8 @@ var _ = Describe("Service Instance Type Test", func() { Expect(instance.GetAnnotations()).To(Equal(annotation)) }) - It("should update SubscribeToSecretChanges", func() { - instance.Spec.SubscribeToSecretChanges = &[]bool{true}[0] + It("should update WatchParameterFromChanges", func() { + instance.Spec.WatchParameterFromChanges = &[]bool{true}[0] Expect(instance.IsSubscribedToParamSecretsChanges()).To(BeTrue()) }) diff --git a/api/v1/suite_test.go b/api/v1/suite_test.go index 7fa64993..64b88afb 100644 --- a/api/v1/suite_test.go +++ b/api/v1/suite_test.go @@ -77,7 +77,7 @@ func getInstance() *ServiceInstance { }, }, }, - SubscribeToSecretChanges: &[]bool{true}[0], + WatchParameterFromChanges: &[]bool{true}[0], UserInfo: &v1.UserInfo{ Username: "test-user", Groups: []string{"test-group"}, diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index ea90a066..70dc8356 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -287,8 +287,8 @@ func (in *ServiceInstanceSpec) DeepCopyInto(out *ServiceInstanceSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.SubscribeToSecretChanges != nil { - in, out := &in.SubscribeToSecretChanges, &out.SubscribeToSecretChanges + if in.WatchParameterFromChanges != nil { + in, out := &in.WatchParameterFromChanges, &out.WatchParameterFromChanges *out = new(bool) **out = **in } diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index 5ebcdf45..496c4923 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -140,7 +140,7 @@ spec: shared: description: Indicates the desired shared state type: boolean - subscribeToSecretChanges: + watchParameterFromChanges: description: indicate instance will update on secrets from parametersFrom change type: boolean diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 808a0a8e..2e8a8c0a 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1280,10 +1280,10 @@ var _ = Describe("ServiceInstance controller", func() { var anotherInstance *v1.ServiceInstance var anotherSecret *corev1.Secret BeforeEach(func() { - instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) + instanceSpec.WatchParameterFromChanges = pointer.Bool(true) }) AfterEach(func() { - instanceSpec.SubscribeToSecretChanges = pointer.Bool(false) + instanceSpec.WatchParameterFromChanges = pointer.Bool(false) if anotherInstance != nil { deleteAndWait(ctx, anotherInstance) } @@ -1353,7 +1353,7 @@ var _ = Describe("ServiceInstance controller", func() { }, }, }, - SubscribeToSecretChanges: pointer.Bool(true), + WatchParameterFromChanges: pointer.Bool(true), } serviceInstance = createInstance(ctx, anotherInstanceName, newInstanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, "secrets \"instance-params-secret-new\" not found") @@ -1477,7 +1477,7 @@ var _ = Describe("ServiceInstance controller", func() { }) When("secret updated and instance don't watch secret", func() { AfterEach(func() { - instanceSpec.SubscribeToSecretChanges = pointer.Bool(false) + instanceSpec.WatchParameterFromChanges = pointer.Bool(false) }) It("should not update instance with the secret change", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) @@ -1492,14 +1492,14 @@ var _ = Describe("ServiceInstance controller", func() { Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(0)) }) - It("should not update instance with the secret change after removing SubscribeToSecretChanges", func() { - instanceSpec.SubscribeToSecretChanges = pointer.Bool(true) + It("should not update instance with the secret change after removing WatchParameterFromChanges", func() { + instanceSpec.WatchParameterFromChanges = pointer.Bool(true) serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) - serviceInstance.Spec.SubscribeToSecretChanges = pointer.Bool(false) + serviceInstance.Spec.WatchParameterFromChanges = pointer.Bool(false) updateInstance(ctx, serviceInstance) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Updated, "") checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) diff --git a/sapbtp-operator-charts/templates/crd.yml b/sapbtp-operator-charts/templates/crd.yml index 899611fe..9335ec9d 100644 --- a/sapbtp-operator-charts/templates/crd.yml +++ b/sapbtp-operator-charts/templates/crd.yml @@ -676,7 +676,7 @@ spec: shared: description: Indicates the desired shared state type: boolean - subscribeToSecretChanges: + watchParameterFromChanges: description: indicate instance will update on secrets from parametersFrom change type: boolean From 87076a52422c60da64b659b787c10b78e01cb490 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 23 Dec 2024 13:39:20 +0200 Subject: [PATCH 70/74] Add option for adding annotations --- api/common/common.go | 1 - api/v1/serviceinstance_types.go | 4 +- api/v1/serviceinstance_types_test.go | 4 +- api/v1/suite_test.go | 2 +- api/v1/zz_generated.deepcopy.go | 4 +- ...rvices.cloud.sap.com_serviceinstances.yaml | 2 +- .../serviceinstance_controller_test.go | 14 +-- go.mod | 40 +++++---- go.sum | 89 ++++++++++--------- sapbtp-operator-charts/templates/crd.yml | 2 +- 10 files changed, 85 insertions(+), 77 deletions(-) diff --git a/api/common/common.go b/api/common/common.go index 708fa219..a0ecf534 100644 --- a/api/common/common.go +++ b/api/common/common.go @@ -15,7 +15,6 @@ type ControllerName string const ( ServiceInstanceController ControllerName = "ServiceInstance" ServiceBindingController ControllerName = "ServiceBinding" - SecretController ControllerName = "Secret" FinalizerName string = "services.cloud.sap.com/sap-btp-finalizer" StaleBindingIDLabel string = "services.cloud.sap.com/stale" StaleBindingRotationOfLabel string = "services.cloud.sap.com/rotationOf" diff --git a/api/v1/serviceinstance_types.go b/api/v1/serviceinstance_types.go index afc4f088..60e5b858 100644 --- a/api/v1/serviceinstance_types.go +++ b/api/v1/serviceinstance_types.go @@ -79,7 +79,7 @@ type ServiceInstanceSpec struct { // indicate instance will update on secrets from parametersFrom change // +optional - WatchParameterFromChanges *bool `json:"watchParameterFromChanges,omitempty"` + WatchParametersFromChanges *bool `json:"watchParametersFromChanges,omitempty"` // List of custom tags describing the ServiceInstance, will be copied to `ServiceBinding` secret in the key called `tags`. // +optional @@ -213,7 +213,7 @@ func (si *ServiceInstance) GetShared() bool { } func (si *ServiceInstance) IsSubscribedToParamSecretsChanges() bool { - return si.Spec.WatchParameterFromChanges != nil && *si.Spec.WatchParameterFromChanges + return si.Spec.WatchParametersFromChanges != nil && *si.Spec.WatchParametersFromChanges } func (si *ServiceInstance) GetSpecHash() string { diff --git a/api/v1/serviceinstance_types_test.go b/api/v1/serviceinstance_types_test.go index d41331ea..0c9532b3 100644 --- a/api/v1/serviceinstance_types_test.go +++ b/api/v1/serviceinstance_types_test.go @@ -129,8 +129,8 @@ var _ = Describe("Service Instance Type Test", func() { Expect(instance.GetAnnotations()).To(Equal(annotation)) }) - It("should update WatchParameterFromChanges", func() { - instance.Spec.WatchParameterFromChanges = &[]bool{true}[0] + It("should update WatchParametersFromChanges", func() { + instance.Spec.WatchParametersFromChanges = &[]bool{true}[0] Expect(instance.IsSubscribedToParamSecretsChanges()).To(BeTrue()) }) diff --git a/api/v1/suite_test.go b/api/v1/suite_test.go index 64b88afb..cf9e0491 100644 --- a/api/v1/suite_test.go +++ b/api/v1/suite_test.go @@ -77,7 +77,7 @@ func getInstance() *ServiceInstance { }, }, }, - WatchParameterFromChanges: &[]bool{true}[0], + WatchParametersFromChanges: &[]bool{true}[0], UserInfo: &v1.UserInfo{ Username: "test-user", Groups: []string{"test-group"}, diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 70dc8356..3028e03f 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -287,8 +287,8 @@ func (in *ServiceInstanceSpec) DeepCopyInto(out *ServiceInstanceSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.WatchParameterFromChanges != nil { - in, out := &in.WatchParameterFromChanges, &out.WatchParameterFromChanges + if in.WatchParametersFromChanges != nil { + in, out := &in.WatchParametersFromChanges, &out.WatchParametersFromChanges *out = new(bool) **out = **in } diff --git a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml index 496c4923..70fc85e3 100644 --- a/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml +++ b/config/crd/bases/services.cloud.sap.com_serviceinstances.yaml @@ -140,7 +140,7 @@ spec: shared: description: Indicates the desired shared state type: boolean - watchParameterFromChanges: + watchParametersFromChanges: description: indicate instance will update on secrets from parametersFrom change type: boolean diff --git a/controllers/serviceinstance_controller_test.go b/controllers/serviceinstance_controller_test.go index 2e8a8c0a..9bf5d506 100644 --- a/controllers/serviceinstance_controller_test.go +++ b/controllers/serviceinstance_controller_test.go @@ -1280,10 +1280,10 @@ var _ = Describe("ServiceInstance controller", func() { var anotherInstance *v1.ServiceInstance var anotherSecret *corev1.Secret BeforeEach(func() { - instanceSpec.WatchParameterFromChanges = pointer.Bool(true) + instanceSpec.WatchParametersFromChanges = pointer.Bool(true) }) AfterEach(func() { - instanceSpec.WatchParameterFromChanges = pointer.Bool(false) + instanceSpec.WatchParametersFromChanges = pointer.Bool(false) if anotherInstance != nil { deleteAndWait(ctx, anotherInstance) } @@ -1353,7 +1353,7 @@ var _ = Describe("ServiceInstance controller", func() { }, }, }, - WatchParameterFromChanges: pointer.Bool(true), + WatchParametersFromChanges: pointer.Bool(true), } serviceInstance = createInstance(ctx, anotherInstanceName, newInstanceSpec, nil, false) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionFalse, common.CreateInProgress, "secrets \"instance-params-secret-new\" not found") @@ -1477,7 +1477,7 @@ var _ = Describe("ServiceInstance controller", func() { }) When("secret updated and instance don't watch secret", func() { AfterEach(func() { - instanceSpec.WatchParameterFromChanges = pointer.Bool(false) + instanceSpec.WatchParametersFromChanges = pointer.Bool(false) }) It("should not update instance with the secret change", func() { serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) @@ -1492,14 +1492,14 @@ var _ = Describe("ServiceInstance controller", func() { Expect(k8sClient.Update(ctx, paramsSecret)).To(Succeed()) Expect(fakeClient.UpdateInstanceCallCount()).To(Equal(0)) }) - It("should not update instance with the secret change after removing WatchParameterFromChanges", func() { - instanceSpec.WatchParameterFromChanges = pointer.Bool(true) + It("should not update instance with the secret change after removing WatchParametersFromChanges", func() { + instanceSpec.WatchParametersFromChanges = pointer.Bool(true) serviceInstance = createInstance(ctx, fakeInstanceName, instanceSpec, nil, true) smInstance, _, _, _, _, _ := fakeClient.ProvisionArgsForCall(0) checkParams(string(smInstance.Parameters), []string{"\"key\":\"value\"", "\"secret-key\":\"secret-value\""}) checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{serviceInstance}) - serviceInstance.Spec.WatchParameterFromChanges = pointer.Bool(false) + serviceInstance.Spec.WatchParametersFromChanges = pointer.Bool(false) updateInstance(ctx, serviceInstance) waitForResourceCondition(ctx, serviceInstance, common.ConditionSucceeded, metav1.ConditionTrue, common.Updated, "") checkSecretAnnotationsAndLabels(ctx, k8sClient, paramsSecret, []*v1.ServiceInstance{}) diff --git a/go.mod b/go.mod index f4cda1a3..4cbb8e36 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/SAP/sap-btp-service-operator -go 1.22.7 +go 1.23.0 + +toolchain go1.23.4 require ( github.com/Masterminds/sprig/v3 v3.2.3 @@ -9,13 +11,13 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/lithammer/dedent v1.1.0 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.33.1 + github.com/onsi/gomega v1.35.1 github.com/pkg/errors v0.9.1 - golang.org/x/oauth2 v0.20.0 - k8s.io/api v0.30.1 - k8s.io/apimachinery v0.30.1 - k8s.io/client-go v0.30.1 - k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 + golang.org/x/oauth2 v0.23.0 + k8s.io/api v0.32.0 + k8s.io/apimachinery v0.32.0 + k8s.io/client-go v0.32.0 + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 sigs.k8s.io/controller-runtime v0.18.3 sigs.k8s.io/yaml v1.4.0 ) @@ -25,17 +27,17 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -56,10 +58,10 @@ require ( github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.31.0 // indirect @@ -68,16 +70,16 @@ require ( golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.30.1 // indirect - k8s.io/klog/v2 v2.120.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect ) diff --git a/go.sum b/go.sum index 6bad5dd2..16a6cfc9 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,9 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= @@ -22,24 +23,26 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -60,8 +63,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -110,16 +113,17 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= -github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= @@ -128,8 +132,8 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= @@ -145,8 +149,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -179,8 +185,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -214,16 +220,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -236,11 +242,13 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -248,32 +256,31 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= -k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= +k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= -k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= -k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= +k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= +k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.18.3 h1:B5Wmmo8WMWK7izei+2LlXLVDGzMwAHBNLX68lwtlSR4= sigs.k8s.io/controller-runtime v0.18.3/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/sapbtp-operator-charts/templates/crd.yml b/sapbtp-operator-charts/templates/crd.yml index 9335ec9d..1b62ad07 100644 --- a/sapbtp-operator-charts/templates/crd.yml +++ b/sapbtp-operator-charts/templates/crd.yml @@ -676,7 +676,7 @@ spec: shared: description: Indicates the desired shared state type: boolean - watchParameterFromChanges: + watchParametersFromChanges: description: indicate instance will update on secrets from parametersFrom change type: boolean From ae6d34468c53e4ac83eb2cbdf4fa6ae2cdeb412c Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 23 Dec 2024 13:49:28 +0200 Subject: [PATCH 71/74] . --- go.mod | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 4cbb8e36..e12cfc76 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/SAP/sap-btp-service-operator -go 1.23.0 - -toolchain go1.23.4 +go 1.22.7 require ( github.com/Masterminds/sprig/v3 v3.2.3 @@ -11,13 +9,13 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/lithammer/dedent v1.1.0 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.35.1 + github.com/onsi/gomega v1.33.1 github.com/pkg/errors v0.9.1 - golang.org/x/oauth2 v0.23.0 - k8s.io/api v0.32.0 - k8s.io/apimachinery v0.32.0 - k8s.io/client-go v0.32.0 - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 + golang.org/x/oauth2 v0.20.0 + k8s.io/api v0.30.1 + k8s.io/apimachinery v0.30.1 + k8s.io/client-go v0.30.1 + k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 sigs.k8s.io/controller-runtime v0.18.3 sigs.k8s.io/yaml v1.4.0 ) @@ -27,17 +25,17 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -58,10 +56,10 @@ require ( github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.31.0 // indirect @@ -70,16 +68,16 @@ require ( golang.org/x/sys v0.28.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.30.1 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect -) + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect +) \ No newline at end of file From bc64d799d21122393691c03ffbd7ede28d9a9bec Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 23 Dec 2024 13:50:37 +0200 Subject: [PATCH 72/74] . --- go.mod | 2 +- go.sum | 89 +++++++++++++++++++++++++++------------------------------- 2 files changed, 42 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index e12cfc76..f4cda1a3 100644 --- a/go.mod +++ b/go.mod @@ -80,4 +80,4 @@ require ( k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect -) \ No newline at end of file +) diff --git a/go.sum b/go.sum index 16a6cfc9..6bad5dd2 100644 --- a/go.sum +++ b/go.sum @@ -10,9 +10,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= @@ -23,26 +22,24 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -63,8 +60,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -113,17 +110,16 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= @@ -132,8 +128,8 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= @@ -149,10 +145,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -185,8 +179,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -220,16 +214,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -242,13 +236,11 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -256,31 +248,32 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= -k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= +k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= +k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= -k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= -k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= +k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= +k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.18.3 h1:B5Wmmo8WMWK7izei+2LlXLVDGzMwAHBNLX68lwtlSR4= sigs.k8s.io/controller-runtime v0.18.3/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From 589caea68a84119d828a2e2604102b83d834d819 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 23 Dec 2024 14:05:39 +0200 Subject: [PATCH 73/74] . --- sapbtp-operator-charts/templates/crd.yml | 2012 +++++++++++----------- 1 file changed, 1032 insertions(+), 980 deletions(-) diff --git a/sapbtp-operator-charts/templates/crd.yml b/sapbtp-operator-charts/templates/crd.yml index 1b62ad07..96b91fca 100644 --- a/sapbtp-operator-charts/templates/crd.yml +++ b/sapbtp-operator-charts/templates/crd.yml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.1 + controller-gen.kubebuilder.io/version: v0.15.0 name: servicebindings.services.cloud.sap.com spec: group: services.cloud.sap.com @@ -13,534 +13,562 @@ spec: singular: servicebinding scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.serviceInstanceName - name: Instance - type: string - - jsonPath: .status.conditions[0].reason - name: Status - type: string - - jsonPath: .status.ready - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.bindingID - name: ID - priority: 1 - type: string - - jsonPath: .status.conditions[0].message - name: Message - priority: 1 - type: string - name: v1 - schema: - openAPIV3Schema: - description: ServiceBinding is the Schema for the servicebindings API - 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: ServiceBindingSpec defines the desired state of ServiceBinding - properties: - credentialsRotationPolicy: - description: CredentialsRotationPolicy holds automatic credentials - rotation configuration. - properties: - enabled: - type: boolean - rotatedBindingTTL: - description: For how long to keep the rotated binding. - type: string - rotationFrequency: - description: What frequency to perform binding rotation. - type: string - required: - - enabled - type: object - externalName: - description: The name of the binding in Service Manager - type: string - parameters: - description: |- - Parameters for the binding. - - The Parameters field is NOT secret or secured in any way and should - NEVER be used to hold sensitive information. To set parameters that - contain secret information, you should ALWAYS store that information - in a Secret and use the ParametersFrom field. - type: object - x-kubernetes-preserve-unknown-fields: true - parametersFrom: - description: |- - List of sources to populate parameters. - If a top-level parameter name exists in multiples sources among - `Parameters` and `ParametersFrom` fields, it is - considered to be a user error in the specification - items: - description: ParametersFromSource represents the source of a set - of Parameters + - additionalPrinterColumns: + - jsonPath: .spec.serviceInstanceName + name: Instance + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .status.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.bindingID + name: ID + priority: 1 + type: string + - jsonPath: .status.conditions[0].message + name: Message + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: ServiceBinding is the Schema for the servicebindings API + 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: ServiceBindingSpec defines the desired state of ServiceBinding + properties: + credentialsRotationPolicy: + description: CredentialsRotationPolicy holds automatic credentials + rotation configuration. properties: - secretKeyRef: - description: |- - The Secret key to select from. - The value must be a JSON object. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the pod's namespace - to select from. + enabled: + type: boolean + rotatedBindingTTL: + description: For how long to keep the rotated binding. + type: string + rotationFrequency: + description: What frequency to perform binding rotation. + type: string + required: + - enabled + type: object + externalName: + description: The name of the binding in Service Manager + type: string + parameters: + description: |- + Parameters for the binding. + + + The Parameters field is NOT secret or secured in any way and should + NEVER be used to hold sensitive information. To set parameters that + contain secret information, you should ALWAYS store that information + in a Secret and use the ParametersFrom field. + type: object + x-kubernetes-preserve-unknown-fields: true + parametersFrom: + description: |- + List of sources to populate parameters. + If a top-level parameter name exists in multiples sources among + `Parameters` and `ParametersFrom` fields, it is + considered to be a user error in the specification + items: + description: ParametersFromSource represents the source of a set + of Parameters + properties: + secretKeyRef: + description: |- + The Secret key to select from. + The value must be a JSON object. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the pod's namespace + to select from. + type: string + required: + - key + - name + type: object + type: object + type: array + secretKey: + description: |- + SecretKey is used as the key inside the secret to store the credentials + returned by the broker encoded as json to support complex data structures. + If not specified, the credentials returned by the broker will be used + directly as the secrets data. + type: string + secretName: + description: SecretName is the name of the secret where credentials + will be stored + type: string + secretRootKey: + description: |- + SecretRootKey is used as the key inside the secret to store all binding + data including credentials returned by the broker and additional info under single key. + Convenient way to store whole binding data in single file when using `volumeMounts`. + type: string + secretTemplate: + description: |- + SecretTemplate is a Go template that generates a custom Kubernetes + v1/Secret based on data from the service binding returned by Service Manager and the instance information. + The generated secret is used instead of the default secret. + This is useful if the consumer of service binding data expects them in + a specific format. + For Go templates see https://pkg.go.dev/text/template. + For supported funcs see: https://pkg.go.dev/text/template#hdr-Functions, https://masterminds.github.io/sprig/ + type: string + x-kubernetes-preserve-unknown-fields: true + serviceInstanceName: + description: The k8s name of the service instance to bind, should + be in the namespace of the binding + minLength: 1 + type: string + serviceInstanceNamespace: + description: The namespace of the referenced instance, if empty Binding's + namespace will be used + type: string + userInfo: + description: |- + UserInfo contains information about the user that last modified this + instance. This field is set by the API server and not settable by the + end-user. User-provided values for this field are not saved. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate + items: type: string - required: - - key - - name + type: array + description: Any additional information provided by the authenticator. type: object - type: object - type: array - secretKey: - description: |- - SecretKey is used as the key inside the secret to store the credentials - returned by the broker encoded as json to support complex data structures. - If not specified, the credentials returned by the broker will be used - directly as the secrets data. - type: string - secretName: - description: SecretName is the name of the secret where credentials - will be stored - type: string - secretRootKey: - description: |- - SecretRootKey is used as the key inside the secret to store all binding - data including credentials returned by the broker and additional info under single key. - Convenient way to store whole binding data in single file when using `volumeMounts`. - type: string - secretTemplate: - description: |- - SecretTemplate is a Go template that generates a custom Kubernetes - v1/Secret based on data from the service binding returned by Service Manager and the instance information. - The generated secret is used instead of the default secret. - This is useful if the consumer of service binding data expects them in - a specific format. - For Go templates see https://pkg.go.dev/text/template. - For supported funcs see: https://pkg.go.dev/text/template#hdr-Functions, https://masterminds.github.io/sprig/ - type: string - x-kubernetes-preserve-unknown-fields: true - serviceInstanceName: - description: The k8s name of the service instance to bind, should - be in the namespace of the binding - minLength: 1 - type: string - serviceInstanceNamespace: - description: The namespace of the referenced instance, if empty Binding's - namespace will be used - type: string - userInfo: - description: |- - UserInfo contains information about the user that last modified this - instance. This field is set by the API server and not settable by the - end-user. User-provided values for this field are not saved. - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf can generate + groups: + description: The names of groups this user is a part of. items: type: string type: array - description: Any additional information provided by the authenticator. - type: object - groups: - description: The names of groups this user is a part of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user among - all active users. - type: string - type: object - required: - - serviceInstanceName - type: object - status: - description: ServiceBindingStatus defines the observed state of ServiceBinding - properties: - bindingID: - description: The generated ID of the binding, will be automatically - filled once the binding is created - type: string - conditions: - description: Service binding conditions - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: + x-kubernetes-list-type: atomic + uid: description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + username: + description: The name that uniquely identifies this user among + all active users. type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown + type: object + required: + - serviceInstanceName + type: object + status: + description: ServiceBindingStatus defines the observed state of ServiceBinding + properties: + bindingID: + description: The generated ID of the binding, will be automatically + filled once the binding is created + type: string + conditions: + description: Service binding conditions + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + instanceID: + description: The ID of the instance in SM associated with binding + type: string + lastCredentialsRotationTime: + description: Indicates when binding secret was rotated + format: date-time + type: string + observedGeneration: + description: Last generation that was acted on + format: int64 + type: integer + operationType: + description: The operation type (CREATE/UPDATE/DELETE) for ongoing + operation + type: string + operationURL: + description: URL of ongoing operation for the service binding + type: string + ready: + description: Indicates whether binding is ready for usage + type: string + subaccountID: + description: The subaccount id of the service binding + type: string + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.serviceInstanceName + name: Instance + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .status.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.bindingID + name: ID + priority: 1 + type: string + - jsonPath: .status.conditions[0].message + name: Message + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ServiceBinding is the Schema for the servicebindings API + 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: ServiceBindingSpec defines the desired state of ServiceBinding + properties: + credentialsRotationPolicy: + description: CredentialsRotationPolicy holds automatic credentials + rotation configuration. + properties: + enabled: + type: boolean + rotatedBindingTTL: + description: For how long to keep the rotated binding. type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + rotationFrequency: + description: What frequency to perform binding rotation. type: string required: - - lastTransitionTime - - message - - reason - - status - - type + - enabled type: object - type: array - instanceID: - description: The ID of the instance in SM associated with binding - type: string - lastCredentialsRotationTime: - description: Indicates when binding secret was rotated - format: date-time - type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer - operationType: - description: The operation type (CREATE/UPDATE/DELETE) for ongoing - operation - type: string - operationURL: - description: URL of ongoing operation for the service binding - type: string - ready: - description: Indicates whether binding is ready for usage - type: string - subaccountID: - description: The subaccount id of the service binding - type: string - required: - - conditions - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.serviceInstanceName - name: Instance - type: string - - jsonPath: .status.conditions[0].reason - name: Status - type: string - - jsonPath: .status.ready - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.bindingID - name: ID - priority: 1 - type: string - - jsonPath: .status.conditions[0].message - name: Message - priority: 1 - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: ServiceBinding is the Schema for the servicebindings API - 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: ServiceBindingSpec defines the desired state of ServiceBinding - properties: - credentialsRotationPolicy: - description: CredentialsRotationPolicy holds automatic credentials - rotation configuration. - properties: - enabled: - type: boolean - rotatedBindingTTL: - description: For how long to keep the rotated binding. - type: string - rotationFrequency: - description: What frequency to perform binding rotation. - type: string - required: - - enabled - type: object - externalName: - description: The name of the binding in Service Manager - type: string - parameters: - description: |- - Parameters for the binding. - - The Parameters field is NOT secret or secured in any way and should - NEVER be used to hold sensitive information. To set parameters that - contain secret information, you should ALWAYS store that information - in a Secret and use the ParametersFrom field. - type: object - x-kubernetes-preserve-unknown-fields: true - parametersFrom: - description: |- - List of sources to populate parameters. - If a top-level parameter name exists in multiples sources among - `Parameters` and `ParametersFrom` fields, it is - considered to be a user error in the specification - items: - description: ParametersFromSource represents the source of a set - of Parameters + externalName: + description: The name of the binding in Service Manager + type: string + parameters: + description: |- + Parameters for the binding. + + + The Parameters field is NOT secret or secured in any way and should + NEVER be used to hold sensitive information. To set parameters that + contain secret information, you should ALWAYS store that information + in a Secret and use the ParametersFrom field. + type: object + x-kubernetes-preserve-unknown-fields: true + parametersFrom: + description: |- + List of sources to populate parameters. + If a top-level parameter name exists in multiples sources among + `Parameters` and `ParametersFrom` fields, it is + considered to be a user error in the specification + items: + description: ParametersFromSource represents the source of a set + of Parameters + properties: + secretKeyRef: + description: |- + The Secret key to select from. + The value must be a JSON object. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the pod's namespace + to select from. + type: string + required: + - key + - name + type: object + type: object + type: array + secretKey: + description: |- + SecretKey is used as the key inside the secret to store the credentials + returned by the broker encoded as json to support complex data structures. + If not specified, the credentials returned by the broker will be used + directly as the secrets data. + type: string + secretName: + description: SecretName is the name of the secret where credentials + will be stored + type: string + secretRootKey: + description: |- + SecretRootKey is used as the key inside the secret to store all binding + data including credentials returned by the broker and additional info under single key. + Convenient way to store whole binding data in single file when using `volumeMounts`. + type: string + serviceInstanceName: + description: The k8s name of the service instance to bind, should + be in the namespace of the binding + minLength: 1 + type: string + userInfo: + description: |- + UserInfo contains information about the user that last modified this + instance. This field is set by the API server and not settable by the + end-user. User-provided values for this field are not saved. properties: - secretKeyRef: - description: |- - The Secret key to select from. - The value must be a JSON object. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate + items: type: string - name: - description: The name of the secret in the pod's namespace - to select from. - type: string - required: - - key - - name + type: array + description: Any additional information provided by the authenticator. type: object - type: object - type: array - secretKey: - description: |- - SecretKey is used as the key inside the secret to store the credentials - returned by the broker encoded as json to support complex data structures. - If not specified, the credentials returned by the broker will be used - directly as the secrets data. - type: string - secretName: - description: SecretName is the name of the secret where credentials - will be stored - type: string - secretRootKey: - description: |- - SecretRootKey is used as the key inside the secret to store all binding - data including credentials returned by the broker and additional info under single key. - Convenient way to store whole binding data in single file when using `volumeMounts`. - type: string - serviceInstanceName: - description: The k8s name of the service instance to bind, should - be in the namespace of the binding - minLength: 1 - type: string - userInfo: - description: |- - UserInfo contains information about the user that last modified this - instance. This field is set by the API server and not settable by the - end-user. User-provided values for this field are not saved. - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf can generate + groups: + description: The names of groups this user is a part of. items: type: string type: array - description: Any additional information provided by the authenticator. - type: object - groups: - description: The names of groups this user is a part of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user among - all active users. - type: string - type: object - required: - - serviceInstanceName - type: object - status: - description: ServiceBindingStatus defines the observed state of ServiceBinding - properties: - bindingID: - description: The generated ID of the binding, will be automatically - filled once the binding is created - type: string - conditions: - description: Service binding conditions - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: + x-kubernetes-list-type: atomic + uid: description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + username: + description: The name that uniquely identifies this user among + all active users. type: string - required: - - lastTransitionTime - - message - - reason - - status - - type type: object - type: array - instanceID: - description: The ID of the instance in SM associated with binding - type: string - lastCredentialsRotationTime: - description: Indicates when binding secret was rotated - format: date-time - type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer - operationType: - description: The operation type (CREATE/UPDATE/DELETE) for ongoing - operation - type: string - operationURL: - description: URL of ongoing operation for the service binding - type: string - ready: - description: Indicates whether binding is ready for usage - type: string - required: - - conditions - type: object - type: object - served: true - storage: false - subresources: - status: {} + required: + - serviceInstanceName + type: object + status: + description: ServiceBindingStatus defines the observed state of ServiceBinding + properties: + bindingID: + description: The generated ID of the binding, will be automatically + filled once the binding is created + type: string + conditions: + description: Service binding conditions + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + instanceID: + description: The ID of the instance in SM associated with binding + type: string + lastCredentialsRotationTime: + description: Indicates when binding secret was rotated + format: date-time + type: string + observedGeneration: + description: Last generation that was acted on + format: int64 + type: integer + operationType: + description: The operation type (CREATE/UPDATE/DELETE) for ongoing + operation + type: string + operationURL: + description: URL of ongoing operation for the service binding + type: string + ready: + description: Indicates whether binding is ready for usage + type: string + required: + - conditions + type: object + type: object + served: true + storage: false + subresources: + status: {} --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.1 + controller-gen.kubebuilder.io/version: v0.15.0 name: serviceinstances.services.cloud.sap.com spec: group: services.cloud.sap.com @@ -551,506 +579,530 @@ spec: singular: serviceinstance scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.serviceOfferingName - name: Offering - type: string - - jsonPath: .spec.servicePlanName - name: Plan - type: string - - jsonPath: .spec.shared - name: shared - type: boolean - - jsonPath: .spec.dataCenter - name: dataCenter - type: string - - jsonPath: .status.conditions[0].reason - name: Status - type: string - - jsonPath: .status.ready - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.instanceID - name: ID - priority: 1 - type: string - - jsonPath: .status.conditions[0].message - name: Message - priority: 1 - type: string - name: v1 - schema: - openAPIV3Schema: - description: ServiceInstance is the Schema for the serviceinstances API - 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: ServiceInstanceSpec defines the desired state of ServiceInstance - properties: - btpAccessCredentialsSecret: - description: The name of the btp access credentials secret - type: string - customTags: - description: List of custom tags describing the ServiceInstance, will - be copied to `ServiceBinding` secret in the key called `tags`. - items: + - additionalPrinterColumns: + - jsonPath: .spec.serviceOfferingName + name: Offering + type: string + - jsonPath: .spec.servicePlanName + name: Plan + type: string + - jsonPath: .spec.shared + name: shared + type: boolean + - jsonPath: .spec.dataCenter + name: dataCenter + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .status.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.instanceID + name: ID + priority: 1 + type: string + - jsonPath: .status.conditions[0].message + name: Message + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: ServiceInstance is the Schema for the serviceinstances API + 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: ServiceInstanceSpec defines the desired state of ServiceInstance + properties: + btpAccessCredentialsSecret: + description: The name of the btp access credentials secret + type: string + customTags: + description: List of custom tags describing the ServiceInstance, will + be copied to `ServiceBinding` secret in the key called `tags`. + items: + type: string + type: array + dataCenter: + description: The dataCenter in case service offering and plan name + exist in other data center and not on main + type: string + externalName: + description: The name of the instance in Service Manager type: string - type: array - dataCenter: - description: The dataCenter in case service offering and plan name - exist in other data center and not on main - type: string - externalName: - description: The name of the instance in Service Manager - type: string - parameters: - description: |- - Provisioning parameters for the instance. - - The Parameters field is NOT secret or secured in any way and should - NEVER be used to hold sensitive information. To set parameters that - contain secret information, you should ALWAYS store that information - in a Secret and use the ParametersFrom field. - type: object - x-kubernetes-preserve-unknown-fields: true - parametersFrom: - description: |- - List of sources to populate parameters. - If a top-level parameter name exists in multiples sources among - `Parameters` and `ParametersFrom` fields, it is - considered to be a user error in the specification - items: - description: ParametersFromSource represents the source of a set - of Parameters + parameters: + description: |- + Provisioning parameters for the instance. + + + The Parameters field is NOT secret or secured in any way and should + NEVER be used to hold sensitive information. To set parameters that + contain secret information, you should ALWAYS store that information + in a Secret and use the ParametersFrom field. + type: object + x-kubernetes-preserve-unknown-fields: true + parametersFrom: + description: |- + List of sources to populate parameters. + If a top-level parameter name exists in multiples sources among + `Parameters` and `ParametersFrom` fields, it is + considered to be a user error in the specification + items: + description: ParametersFromSource represents the source of a set + of Parameters + properties: + secretKeyRef: + description: |- + The Secret key to select from. + The value must be a JSON object. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the pod's namespace + to select from. + type: string + required: + - key + - name + type: object + type: object + type: array + serviceOfferingName: + description: The name of the service offering + minLength: 1 + type: string + servicePlanID: + description: The plan ID in case service offering and plan name are + ambiguous + type: string + servicePlanName: + description: The name of the service plan + minLength: 1 + type: string + shared: + description: Indicates the desired shared state + type: boolean + userInfo: + description: |- + UserInfo contains information about the user that last modified this + instance. This field is set by the API server and not settable by the + end-user. User-provided values for this field are not saved. properties: - secretKeyRef: - description: |- - The Secret key to select from. - The value must be a JSON object. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the pod's namespace - to select from. + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate + items: type: string - required: - - key - - name + type: array + description: Any additional information provided by the authenticator. type: object - type: object - type: array - serviceOfferingName: - description: The name of the service offering - minLength: 1 - type: string - servicePlanID: - description: The plan ID in case service offering and plan name are - ambiguous - type: string - servicePlanName: - description: The name of the service plan - minLength: 1 - type: string - shared: - description: Indicates the desired shared state - type: boolean - watchParametersFromChanges: - description: indicate instance will update on secrets from parametersFrom - change - type: boolean - userInfo: - description: |- - UserInfo contains information about the user that last modified this - instance. This field is set by the API server and not settable by the - end-user. User-provided values for this field are not saved. - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf can generate + groups: + description: The names of groups this user is a part of. items: type: string type: array - description: Any additional information provided by the authenticator. - type: object - groups: - description: The names of groups this user is a part of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user among - all active users. - type: string - type: object - required: - - serviceOfferingName - - servicePlanName - type: object - status: - description: ServiceInstanceStatus defines the observed state of ServiceInstance - properties: - conditions: - description: Service instance conditions - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: + x-kubernetes-list-type: atomic + uid: description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown + username: + description: The name that uniquely identifies this user among + all active users. type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type type: object - type: array - hashedSpec: - description: HashedSpec is the hashed spec without the shared property - type: string - instanceID: - description: The generated ID of the instance, will be automatically - filled once the instance is created - type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer - operationType: - description: The operation type (CREATE/UPDATE/DELETE) for ongoing - operation - type: string - operationURL: - description: URL of ongoing operation for the service instance - type: string - ready: - description: Indicates whether instance is ready for usage - type: string - subaccountID: - description: The subaccount id of the service instance - type: string - tags: - description: Tags describing the ServiceInstance as provided in service - catalog, will be copied to `ServiceBinding` secret in the key called - `tags`. - items: + required: + - serviceOfferingName + - servicePlanName + type: object + status: + description: ServiceInstanceStatus defines the observed state of ServiceInstance + properties: + conditions: + description: Service instance conditions + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + hashedSpec: + description: HashedSpec is the hashed spec without the shared property + type: string + instanceID: + description: The generated ID of the instance, will be automatically + filled once the instance is created + type: string + observedGeneration: + description: Last generation that was acted on + format: int64 + type: integer + operationType: + description: The operation type (CREATE/UPDATE/DELETE) for ongoing + operation + type: string + operationURL: + description: URL of ongoing operation for the service instance + type: string + ready: + description: Indicates whether instance is ready for usage + type: string + subaccountID: + description: The subaccount id of the service instance + type: string + tags: + description: Tags describing the ServiceInstance as provided in service + catalog, will be copied to `ServiceBinding` secret in the key called + `tags`. + items: + type: string + type: array + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.serviceOfferingName + name: Offering + type: string + - jsonPath: .spec.servicePlanName + name: Plan + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .status.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.instanceID + name: ID + priority: 1 + type: string + - jsonPath: .status.conditions[0].message + name: Message + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ServiceInstance is the Schema for the serviceinstances API + 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: ServiceInstanceSpec defines the desired state of ServiceInstance + properties: + customTags: + description: List of custom tags describing the ServiceInstance, will + be copied to `ServiceBinding` secret in the key called `tags`. + items: + type: string + type: array + externalName: + description: The name of the instance in Service Manager + type: string + parameters: + description: |- + Provisioning parameters for the instance. + + + The Parameters field is NOT secret or secured in any way and should + NEVER be used to hold sensitive information. To set parameters that + contain secret information, you should ALWAYS store that information + in a Secret and use the ParametersFrom field. + type: object + x-kubernetes-preserve-unknown-fields: true + parametersFrom: + description: |- + List of sources to populate parameters. + If a top-level parameter name exists in multiples sources among + `Parameters` and `ParametersFrom` fields, it is + considered to be a user error in the specification + items: + description: ParametersFromSource represents the source of a set + of Parameters + properties: + secretKeyRef: + description: |- + The Secret key to select from. + The value must be a JSON object. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the pod's namespace + to select from. + type: string + required: + - key + - name + type: object + type: object + type: array + serviceOfferingName: + description: The name of the service offering + minLength: 1 + type: string + servicePlanID: + description: The plan ID in case service offering and plan name are + ambiguous type: string - type: array - required: - - conditions - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.serviceOfferingName - name: Offering - type: string - - jsonPath: .spec.servicePlanName - name: Plan - type: string - - jsonPath: .status.conditions[0].reason - name: Status - type: string - - jsonPath: .status.ready - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.instanceID - name: ID - priority: 1 - type: string - - jsonPath: .status.conditions[0].message - name: Message - priority: 1 - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: ServiceInstance is the Schema for the serviceinstances API - 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: ServiceInstanceSpec defines the desired state of ServiceInstance - properties: - customTags: - description: List of custom tags describing the ServiceInstance, will - be copied to `ServiceBinding` secret in the key called `tags`. - items: + servicePlanName: + description: The name of the service plan + minLength: 1 type: string - type: array - externalName: - description: The name of the instance in Service Manager - type: string - parameters: - description: |- - Provisioning parameters for the instance. - - The Parameters field is NOT secret or secured in any way and should - NEVER be used to hold sensitive information. To set parameters that - contain secret information, you should ALWAYS store that information - in a Secret and use the ParametersFrom field. - type: object - x-kubernetes-preserve-unknown-fields: true - parametersFrom: - description: |- - List of sources to populate parameters. - If a top-level parameter name exists in multiples sources among - `Parameters` and `ParametersFrom` fields, it is - considered to be a user error in the specification - items: - description: ParametersFromSource represents the source of a set - of Parameters + shared: + description: Indicates the desired shared state + type: boolean + userInfo: + description: |- + UserInfo contains information about the user that last modified this + instance. This field is set by the API server and not settable by the + end-user. User-provided values for this field are not saved. properties: - secretKeyRef: - description: |- - The Secret key to select from. - The value must be a JSON object. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate + items: type: string - name: - description: The name of the secret in the pod's namespace - to select from. - type: string - required: - - key - - name + type: array + description: Any additional information provided by the authenticator. type: object - type: object - type: array - serviceOfferingName: - description: The name of the service offering - minLength: 1 - type: string - servicePlanID: - description: The plan ID in case service offering and plan name are - ambiguous - type: string - servicePlanName: - description: The name of the service plan - minLength: 1 - type: string - shared: - description: Indicates the desired shared state - type: boolean - userInfo: - description: |- - UserInfo contains information about the user that last modified this - instance. This field is set by the API server and not settable by the - end-user. User-provided values for this field are not saved. - properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf can generate + groups: + description: The names of groups this user is a part of. items: type: string type: array - description: Any additional information provided by the authenticator. - type: object - groups: - description: The names of groups this user is a part of. - items: - type: string - type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user among - all active users. - type: string - type: object - required: - - serviceOfferingName - - servicePlanName - type: object - status: - description: ServiceInstanceStatus defines the observed state of ServiceInstance - properties: - conditions: - description: Service instance conditions - items: - description: Condition contains details for one aspect of the current - state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: + x-kubernetes-list-type: atomic + uid: description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + username: + description: The name that uniquely identifies this user among + all active users. type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type type: object - type: array - instanceID: - description: The generated ID of the instance, will be automatically - filled once the instance is created - type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer - operationType: - description: The operation type (CREATE/UPDATE/DELETE) for ongoing - operation - type: string - operationURL: - description: URL of ongoing operation for the service instance - type: string - ready: - description: Indicates whether instance is ready for usage - type: string - tags: - description: Tags describing the ServiceInstance as provided in service - catalog, will be copied to `ServiceBinding` secret in the key called - `tags`. - items: + required: + - serviceOfferingName + - servicePlanName + type: object + status: + description: ServiceInstanceStatus defines the observed state of ServiceInstance + properties: + conditions: + description: Service instance conditions + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + instanceID: + description: The generated ID of the instance, will be automatically + filled once the instance is created type: string - type: array - required: - - conditions - type: object - type: object - served: true - storage: false - subresources: - status: {} + observedGeneration: + description: Last generation that was acted on + format: int64 + type: integer + operationType: + description: The operation type (CREATE/UPDATE/DELETE) for ongoing + operation + type: string + operationURL: + description: URL of ongoing operation for the service instance + type: string + ready: + description: Indicates whether instance is ready for usage + type: string + tags: + description: Tags describing the ServiceInstance as provided in service + catalog, will be copied to `ServiceBinding` secret in the key called + `tags`. + items: + type: string + type: array + required: + - conditions + type: object + type: object + served: true + storage: false + subresources: + status: {} \ No newline at end of file From feffffb3f6255687f906e45a8474bcb295996fe3 Mon Sep 17 00:00:00 2001 From: i065450 Date: Mon, 23 Dec 2024 14:07:57 +0200 Subject: [PATCH 74/74] . --- .../samples/services_v1_serviceinstance.yaml | 2 +- sapbtp-operator-charts/templates/crd.yml | 2006 ++++++++--------- 2 files changed, 1004 insertions(+), 1004 deletions(-) diff --git a/config/samples/services_v1_serviceinstance.yaml b/config/samples/services_v1_serviceinstance.yaml index 197de5a2..bd19f598 100644 --- a/config/samples/services_v1_serviceinstance.yaml +++ b/config/samples/services_v1_serviceinstance.yaml @@ -4,4 +4,4 @@ metadata: name: sample-instance-1 spec: serviceOfferingName: service-manager - servicePlanName: subaccount-audit \ No newline at end of file + servicePlanName: subaccount-audit diff --git a/sapbtp-operator-charts/templates/crd.yml b/sapbtp-operator-charts/templates/crd.yml index 96b91fca..551cbd69 100644 --- a/sapbtp-operator-charts/templates/crd.yml +++ b/sapbtp-operator-charts/templates/crd.yml @@ -13,192 +13,192 @@ spec: singular: servicebinding scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.serviceInstanceName - name: Instance - type: string - - jsonPath: .status.conditions[0].reason - name: Status - type: string - - jsonPath: .status.ready - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.bindingID - name: ID - priority: 1 - type: string - - jsonPath: .status.conditions[0].message - name: Message - priority: 1 - type: string - name: v1 - schema: - openAPIV3Schema: - description: ServiceBinding is the Schema for the servicebindings API - 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: ServiceBindingSpec defines the desired state of ServiceBinding - properties: - credentialsRotationPolicy: - description: CredentialsRotationPolicy holds automatic credentials - rotation configuration. - properties: - enabled: - type: boolean - rotatedBindingTTL: - description: For how long to keep the rotated binding. - type: string - rotationFrequency: - description: What frequency to perform binding rotation. - type: string - required: - - enabled - type: object - externalName: - description: The name of the binding in Service Manager - type: string - parameters: - description: |- - Parameters for the binding. - - - The Parameters field is NOT secret or secured in any way and should - NEVER be used to hold sensitive information. To set parameters that - contain secret information, you should ALWAYS store that information - in a Secret and use the ParametersFrom field. - type: object - x-kubernetes-preserve-unknown-fields: true - parametersFrom: - description: |- - List of sources to populate parameters. - If a top-level parameter name exists in multiples sources among - `Parameters` and `ParametersFrom` fields, it is - considered to be a user error in the specification - items: - description: ParametersFromSource represents the source of a set - of Parameters - properties: - secretKeyRef: - description: |- - The Secret key to select from. - The value must be a JSON object. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the pod's namespace - to select from. - type: string - required: - - key - - name - type: object - type: object - type: array - secretKey: - description: |- - SecretKey is used as the key inside the secret to store the credentials - returned by the broker encoded as json to support complex data structures. - If not specified, the credentials returned by the broker will be used - directly as the secrets data. - type: string - secretName: - description: SecretName is the name of the secret where credentials - will be stored - type: string - secretRootKey: - description: |- - SecretRootKey is used as the key inside the secret to store all binding - data including credentials returned by the broker and additional info under single key. - Convenient way to store whole binding data in single file when using `volumeMounts`. - type: string - secretTemplate: - description: |- - SecretTemplate is a Go template that generates a custom Kubernetes - v1/Secret based on data from the service binding returned by Service Manager and the instance information. - The generated secret is used instead of the default secret. - This is useful if the consumer of service binding data expects them in - a specific format. - For Go templates see https://pkg.go.dev/text/template. - For supported funcs see: https://pkg.go.dev/text/template#hdr-Functions, https://masterminds.github.io/sprig/ - type: string - x-kubernetes-preserve-unknown-fields: true - serviceInstanceName: - description: The k8s name of the service instance to bind, should - be in the namespace of the binding - minLength: 1 - type: string - serviceInstanceNamespace: - description: The namespace of the referenced instance, if empty Binding's - namespace will be used - type: string - userInfo: - description: |- - UserInfo contains information about the user that last modified this - instance. This field is set by the API server and not settable by the - end-user. User-provided values for this field are not saved. + - additionalPrinterColumns: + - jsonPath: .spec.serviceInstanceName + name: Instance + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .status.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.bindingID + name: ID + priority: 1 + type: string + - jsonPath: .status.conditions[0].message + name: Message + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: ServiceBinding is the Schema for the servicebindings API + 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: ServiceBindingSpec defines the desired state of ServiceBinding + properties: + credentialsRotationPolicy: + description: CredentialsRotationPolicy holds automatic credentials + rotation configuration. + properties: + enabled: + type: boolean + rotatedBindingTTL: + description: For how long to keep the rotated binding. + type: string + rotationFrequency: + description: What frequency to perform binding rotation. + type: string + required: + - enabled + type: object + externalName: + description: The name of the binding in Service Manager + type: string + parameters: + description: |- + Parameters for the binding. + + + The Parameters field is NOT secret or secured in any way and should + NEVER be used to hold sensitive information. To set parameters that + contain secret information, you should ALWAYS store that information + in a Secret and use the ParametersFrom field. + type: object + x-kubernetes-preserve-unknown-fields: true + parametersFrom: + description: |- + List of sources to populate parameters. + If a top-level parameter name exists in multiples sources among + `Parameters` and `ParametersFrom` fields, it is + considered to be a user error in the specification + items: + description: ParametersFromSource represents the source of a set + of Parameters properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf can generate - items: + secretKeyRef: + description: |- + The Secret key to select from. + The value must be a JSON object. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the pod's namespace + to select from. type: string - type: array - description: Any additional information provided by the authenticator. + required: + - key + - name type: object - groups: - description: The names of groups this user is a part of. + type: object + type: array + secretKey: + description: |- + SecretKey is used as the key inside the secret to store the credentials + returned by the broker encoded as json to support complex data structures. + If not specified, the credentials returned by the broker will be used + directly as the secrets data. + type: string + secretName: + description: SecretName is the name of the secret where credentials + will be stored + type: string + secretRootKey: + description: |- + SecretRootKey is used as the key inside the secret to store all binding + data including credentials returned by the broker and additional info under single key. + Convenient way to store whole binding data in single file when using `volumeMounts`. + type: string + secretTemplate: + description: |- + SecretTemplate is a Go template that generates a custom Kubernetes + v1/Secret based on data from the service binding returned by Service Manager and the instance information. + The generated secret is used instead of the default secret. + This is useful if the consumer of service binding data expects them in + a specific format. + For Go templates see https://pkg.go.dev/text/template. + For supported funcs see: https://pkg.go.dev/text/template#hdr-Functions, https://masterminds.github.io/sprig/ + type: string + x-kubernetes-preserve-unknown-fields: true + serviceInstanceName: + description: The k8s name of the service instance to bind, should + be in the namespace of the binding + minLength: 1 + type: string + serviceInstanceNamespace: + description: The namespace of the referenced instance, if empty Binding's + namespace will be used + type: string + userInfo: + description: |- + UserInfo contains information about the user that last modified this + instance. This field is set by the API server and not settable by the + end-user. User-provided values for this field are not saved. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate items: type: string type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user among - all active users. + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: type: string - type: object - required: - - serviceInstanceName - type: object - status: - description: ServiceBindingStatus defines the observed state of ServiceBinding - properties: - bindingID: - description: The generated ID of the binding, will be automatically - filled once the binding is created - type: string - conditions: - description: Service binding conditions - items: - description: "Condition contains details for one aspect of the current + type: array + x-kubernetes-list-type: atomic + uid: + description: |- + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. + type: string + username: + description: The name that uniquely identifies this user among + all active users. + type: string + type: object + required: + - serviceInstanceName + type: object + status: + description: ServiceBindingStatus defines the observed state of ServiceBinding + properties: + bindingID: + description: The generated ID of the binding, will be automatically + filled once the binding is created + type: string + conditions: + description: Service binding conditions + items: + description: "Condition contains details for one aspect of the current state of this API Resource.\n---\nThis struct is intended for direct use as an array at the field path .status.conditions. For example,\n\n\n\ttype FooStatus struct{\n\t // Represents the @@ -208,266 +208,266 @@ spec: \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - instanceID: - description: The ID of the instance in SM associated with binding - type: string - lastCredentialsRotationTime: - description: Indicates when binding secret was rotated - format: date-time - type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer - operationType: - description: The operation type (CREATE/UPDATE/DELETE) for ongoing - operation - type: string - operationURL: - description: URL of ongoing operation for the service binding - type: string - ready: - description: Indicates whether binding is ready for usage - type: string - subaccountID: - description: The subaccount id of the service binding - type: string - required: - - conditions - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.serviceInstanceName - name: Instance - type: string - - jsonPath: .status.conditions[0].reason - name: Status - type: string - - jsonPath: .status.ready - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.bindingID - name: ID - priority: 1 - type: string - - jsonPath: .status.conditions[0].message - name: Message - priority: 1 - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: ServiceBinding is the Schema for the servicebindings API - 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: ServiceBindingSpec defines the desired state of ServiceBinding - properties: - credentialsRotationPolicy: - description: CredentialsRotationPolicy holds automatic credentials - rotation configuration. properties: - enabled: - type: boolean - rotatedBindingTTL: - description: For how long to keep the rotated binding. + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown type: string - rotationFrequency: - description: What frequency to perform binding rotation. + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string required: - - enabled - type: object - externalName: - description: The name of the binding in Service Manager - type: string - parameters: - description: |- - Parameters for the binding. - - - The Parameters field is NOT secret or secured in any way and should - NEVER be used to hold sensitive information. To set parameters that - contain secret information, you should ALWAYS store that information - in a Secret and use the ParametersFrom field. + - lastTransitionTime + - message + - reason + - status + - type type: object - x-kubernetes-preserve-unknown-fields: true - parametersFrom: - description: |- - List of sources to populate parameters. - If a top-level parameter name exists in multiples sources among - `Parameters` and `ParametersFrom` fields, it is - considered to be a user error in the specification - items: - description: ParametersFromSource represents the source of a set - of Parameters - properties: - secretKeyRef: - description: |- - The Secret key to select from. - The value must be a JSON object. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the pod's namespace - to select from. - type: string - required: - - key - - name - type: object - type: object - type: array - secretKey: - description: |- - SecretKey is used as the key inside the secret to store the credentials - returned by the broker encoded as json to support complex data structures. - If not specified, the credentials returned by the broker will be used - directly as the secrets data. - type: string - secretName: - description: SecretName is the name of the secret where credentials - will be stored - type: string - secretRootKey: - description: |- - SecretRootKey is used as the key inside the secret to store all binding - data including credentials returned by the broker and additional info under single key. - Convenient way to store whole binding data in single file when using `volumeMounts`. - type: string - serviceInstanceName: - description: The k8s name of the service instance to bind, should - be in the namespace of the binding - minLength: 1 - type: string - userInfo: - description: |- - UserInfo contains information about the user that last modified this - instance. This field is set by the API server and not settable by the - end-user. User-provided values for this field are not saved. + type: array + instanceID: + description: The ID of the instance in SM associated with binding + type: string + lastCredentialsRotationTime: + description: Indicates when binding secret was rotated + format: date-time + type: string + observedGeneration: + description: Last generation that was acted on + format: int64 + type: integer + operationType: + description: The operation type (CREATE/UPDATE/DELETE) for ongoing + operation + type: string + operationURL: + description: URL of ongoing operation for the service binding + type: string + ready: + description: Indicates whether binding is ready for usage + type: string + subaccountID: + description: The subaccount id of the service binding + type: string + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.serviceInstanceName + name: Instance + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .status.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.bindingID + name: ID + priority: 1 + type: string + - jsonPath: .status.conditions[0].message + name: Message + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ServiceBinding is the Schema for the servicebindings API + 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: ServiceBindingSpec defines the desired state of ServiceBinding + properties: + credentialsRotationPolicy: + description: CredentialsRotationPolicy holds automatic credentials + rotation configuration. + properties: + enabled: + type: boolean + rotatedBindingTTL: + description: For how long to keep the rotated binding. + type: string + rotationFrequency: + description: What frequency to perform binding rotation. + type: string + required: + - enabled + type: object + externalName: + description: The name of the binding in Service Manager + type: string + parameters: + description: |- + Parameters for the binding. + + + The Parameters field is NOT secret or secured in any way and should + NEVER be used to hold sensitive information. To set parameters that + contain secret information, you should ALWAYS store that information + in a Secret and use the ParametersFrom field. + type: object + x-kubernetes-preserve-unknown-fields: true + parametersFrom: + description: |- + List of sources to populate parameters. + If a top-level parameter name exists in multiples sources among + `Parameters` and `ParametersFrom` fields, it is + considered to be a user error in the specification + items: + description: ParametersFromSource represents the source of a set + of Parameters properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf can generate - items: + secretKeyRef: + description: |- + The Secret key to select from. + The value must be a JSON object. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. type: string - type: array - description: Any additional information provided by the authenticator. + name: + description: The name of the secret in the pod's namespace + to select from. + type: string + required: + - key + - name type: object - groups: - description: The names of groups this user is a part of. + type: object + type: array + secretKey: + description: |- + SecretKey is used as the key inside the secret to store the credentials + returned by the broker encoded as json to support complex data structures. + If not specified, the credentials returned by the broker will be used + directly as the secrets data. + type: string + secretName: + description: SecretName is the name of the secret where credentials + will be stored + type: string + secretRootKey: + description: |- + SecretRootKey is used as the key inside the secret to store all binding + data including credentials returned by the broker and additional info under single key. + Convenient way to store whole binding data in single file when using `volumeMounts`. + type: string + serviceInstanceName: + description: The k8s name of the service instance to bind, should + be in the namespace of the binding + minLength: 1 + type: string + userInfo: + description: |- + UserInfo contains information about the user that last modified this + instance. This field is set by the API server and not settable by the + end-user. User-provided values for this field are not saved. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate items: type: string type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user among - all active users. + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: type: string - type: object - required: - - serviceInstanceName - type: object - status: - description: ServiceBindingStatus defines the observed state of ServiceBinding - properties: - bindingID: - description: The generated ID of the binding, will be automatically - filled once the binding is created - type: string - conditions: - description: Service binding conditions - items: - description: "Condition contains details for one aspect of the current + type: array + x-kubernetes-list-type: atomic + uid: + description: |- + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. + type: string + username: + description: The name that uniquely identifies this user among + all active users. + type: string + type: object + required: + - serviceInstanceName + type: object + status: + description: ServiceBindingStatus defines the observed state of ServiceBinding + properties: + bindingID: + description: The generated ID of the binding, will be automatically + filled once the binding is created + type: string + conditions: + description: Service binding conditions + items: + description: "Condition contains details for one aspect of the current state of this API Resource.\n---\nThis struct is intended for direct use as an array at the field path .status.conditions. For example,\n\n\n\ttype FooStatus struct{\n\t // Represents the @@ -477,92 +477,92 @@ spec: \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - instanceID: - description: The ID of the instance in SM associated with binding - type: string - lastCredentialsRotationTime: - description: Indicates when binding secret was rotated - format: date-time - type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer - operationType: - description: The operation type (CREATE/UPDATE/DELETE) for ongoing - operation - type: string - operationURL: - description: URL of ongoing operation for the service binding - type: string - ready: - description: Indicates whether binding is ready for usage - type: string - required: - - conditions - type: object - type: object - served: true - storage: false - subresources: - status: {} + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + instanceID: + description: The ID of the instance in SM associated with binding + type: string + lastCredentialsRotationTime: + description: Indicates when binding secret was rotated + format: date-time + type: string + observedGeneration: + description: Last generation that was acted on + format: int64 + type: integer + operationType: + description: The operation type (CREATE/UPDATE/DELETE) for ongoing + operation + type: string + operationURL: + description: URL of ongoing operation for the service binding + type: string + ready: + description: Indicates whether binding is ready for usage + type: string + required: + - conditions + type: object + type: object + served: true + storage: false + subresources: + status: {} --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -579,174 +579,174 @@ spec: singular: serviceinstance scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .spec.serviceOfferingName - name: Offering - type: string - - jsonPath: .spec.servicePlanName - name: Plan - type: string - - jsonPath: .spec.shared - name: shared - type: boolean - - jsonPath: .spec.dataCenter - name: dataCenter - type: string - - jsonPath: .status.conditions[0].reason - name: Status - type: string - - jsonPath: .status.ready - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.instanceID - name: ID - priority: 1 - type: string - - jsonPath: .status.conditions[0].message - name: Message - priority: 1 - type: string - name: v1 - schema: - openAPIV3Schema: - description: ServiceInstance is the Schema for the serviceinstances API - 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: ServiceInstanceSpec defines the desired state of ServiceInstance - properties: - btpAccessCredentialsSecret: - description: The name of the btp access credentials secret - type: string - customTags: - description: List of custom tags describing the ServiceInstance, will - be copied to `ServiceBinding` secret in the key called `tags`. - items: - type: string - type: array - dataCenter: - description: The dataCenter in case service offering and plan name - exist in other data center and not on main - type: string - externalName: - description: The name of the instance in Service Manager - type: string - parameters: - description: |- - Provisioning parameters for the instance. - - - The Parameters field is NOT secret or secured in any way and should - NEVER be used to hold sensitive information. To set parameters that - contain secret information, you should ALWAYS store that information - in a Secret and use the ParametersFrom field. - type: object - x-kubernetes-preserve-unknown-fields: true - parametersFrom: - description: |- - List of sources to populate parameters. - If a top-level parameter name exists in multiples sources among - `Parameters` and `ParametersFrom` fields, it is - considered to be a user error in the specification - items: - description: ParametersFromSource represents the source of a set - of Parameters - properties: - secretKeyRef: - description: |- - The Secret key to select from. - The value must be a JSON object. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the pod's namespace - to select from. - type: string - required: - - key - - name - type: object - type: object - type: array - serviceOfferingName: - description: The name of the service offering - minLength: 1 + - additionalPrinterColumns: + - jsonPath: .spec.serviceOfferingName + name: Offering + type: string + - jsonPath: .spec.servicePlanName + name: Plan + type: string + - jsonPath: .spec.shared + name: shared + type: boolean + - jsonPath: .spec.dataCenter + name: dataCenter + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .status.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.instanceID + name: ID + priority: 1 + type: string + - jsonPath: .status.conditions[0].message + name: Message + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: ServiceInstance is the Schema for the serviceinstances API + 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: ServiceInstanceSpec defines the desired state of ServiceInstance + properties: + btpAccessCredentialsSecret: + description: The name of the btp access credentials secret + type: string + customTags: + description: List of custom tags describing the ServiceInstance, will + be copied to `ServiceBinding` secret in the key called `tags`. + items: type: string - servicePlanID: - description: The plan ID in case service offering and plan name are - ambiguous - type: string - servicePlanName: - description: The name of the service plan - minLength: 1 - type: string - shared: - description: Indicates the desired shared state - type: boolean - userInfo: - description: |- - UserInfo contains information about the user that last modified this - instance. This field is set by the API server and not settable by the - end-user. User-provided values for this field are not saved. + type: array + dataCenter: + description: The dataCenter in case service offering and plan name + exist in other data center and not on main + type: string + externalName: + description: The name of the instance in Service Manager + type: string + parameters: + description: |- + Provisioning parameters for the instance. + + + The Parameters field is NOT secret or secured in any way and should + NEVER be used to hold sensitive information. To set parameters that + contain secret information, you should ALWAYS store that information + in a Secret and use the ParametersFrom field. + type: object + x-kubernetes-preserve-unknown-fields: true + parametersFrom: + description: |- + List of sources to populate parameters. + If a top-level parameter name exists in multiples sources among + `Parameters` and `ParametersFrom` fields, it is + considered to be a user error in the specification + items: + description: ParametersFromSource represents the source of a set + of Parameters properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf can generate - items: + secretKeyRef: + description: |- + The Secret key to select from. + The value must be a JSON object. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. type: string - type: array - description: Any additional information provided by the authenticator. + name: + description: The name of the secret in the pod's namespace + to select from. + type: string + required: + - key + - name type: object - groups: - description: The names of groups this user is a part of. + type: object + type: array + serviceOfferingName: + description: The name of the service offering + minLength: 1 + type: string + servicePlanID: + description: The plan ID in case service offering and plan name are + ambiguous + type: string + servicePlanName: + description: The name of the service plan + minLength: 1 + type: string + shared: + description: Indicates the desired shared state + type: boolean + userInfo: + description: |- + UserInfo contains information about the user that last modified this + instance. This field is set by the API server and not settable by the + end-user. User-provided values for this field are not saved. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate items: type: string type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user among - all active users. + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: type: string - type: object - required: - - serviceOfferingName - - servicePlanName - type: object - status: - description: ServiceInstanceStatus defines the observed state of ServiceInstance - properties: - conditions: - description: Service instance conditions - items: - description: "Condition contains details for one aspect of the current + type: array + x-kubernetes-list-type: atomic + uid: + description: |- + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. + type: string + username: + description: The name that uniquely identifies this user among + all active users. + type: string + type: object + required: + - serviceOfferingName + - servicePlanName + type: object + status: + description: ServiceInstanceStatus defines the observed state of ServiceInstance + properties: + conditions: + description: Service instance conditions + items: + description: "Condition contains details for one aspect of the current state of this API Resource.\n---\nThis struct is intended for direct use as an array at the field path .status.conditions. For example,\n\n\n\ttype FooStatus struct{\n\t // Represents the @@ -756,257 +756,257 @@ spec: \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - hashedSpec: - description: HashedSpec is the hashed spec without the shared property - type: string - instanceID: - description: The generated ID of the instance, will be automatically - filled once the instance is created - type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer - operationType: - description: The operation type (CREATE/UPDATE/DELETE) for ongoing - operation - type: string - operationURL: - description: URL of ongoing operation for the service instance - type: string - ready: - description: Indicates whether instance is ready for usage - type: string - subaccountID: - description: The subaccount id of the service instance - type: string - tags: - description: Tags describing the ServiceInstance as provided in service - catalog, will be copied to `ServiceBinding` secret in the key called - `tags`. - items: - type: string - type: array - required: - - conditions - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.serviceOfferingName - name: Offering - type: string - - jsonPath: .spec.servicePlanName - name: Plan - type: string - - jsonPath: .status.conditions[0].reason - name: Status - type: string - - jsonPath: .status.ready - name: Ready - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.instanceID - name: ID - priority: 1 - type: string - - jsonPath: .status.conditions[0].message - name: Message - priority: 1 - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: ServiceInstance is the Schema for the serviceinstances API - 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: ServiceInstanceSpec defines the desired state of ServiceInstance - properties: - customTags: - description: List of custom tags describing the ServiceInstance, will - be copied to `ServiceBinding` secret in the key called `tags`. - items: - type: string - type: array - externalName: - description: The name of the instance in Service Manager - type: string - parameters: - description: |- - Provisioning parameters for the instance. - - - The Parameters field is NOT secret or secured in any way and should - NEVER be used to hold sensitive information. To set parameters that - contain secret information, you should ALWAYS store that information - in a Secret and use the ParametersFrom field. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type type: object - x-kubernetes-preserve-unknown-fields: true - parametersFrom: - description: |- - List of sources to populate parameters. - If a top-level parameter name exists in multiples sources among - `Parameters` and `ParametersFrom` fields, it is - considered to be a user error in the specification - items: - description: ParametersFromSource represents the source of a set - of Parameters - properties: - secretKeyRef: - description: |- - The Secret key to select from. - The value must be a JSON object. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - name: - description: The name of the secret in the pod's namespace - to select from. - type: string - required: - - key - - name - type: object - type: object - type: array - serviceOfferingName: - description: The name of the service offering - minLength: 1 - type: string - servicePlanID: - description: The plan ID in case service offering and plan name are - ambiguous + type: array + hashedSpec: + description: HashedSpec is the hashed spec without the shared property + type: string + instanceID: + description: The generated ID of the instance, will be automatically + filled once the instance is created + type: string + observedGeneration: + description: Last generation that was acted on + format: int64 + type: integer + operationType: + description: The operation type (CREATE/UPDATE/DELETE) for ongoing + operation + type: string + operationURL: + description: URL of ongoing operation for the service instance + type: string + ready: + description: Indicates whether instance is ready for usage + type: string + subaccountID: + description: The subaccount id of the service instance + type: string + tags: + description: Tags describing the ServiceInstance as provided in service + catalog, will be copied to `ServiceBinding` secret in the key called + `tags`. + items: type: string - servicePlanName: - description: The name of the service plan - minLength: 1 + type: array + required: + - conditions + type: object + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.serviceOfferingName + name: Offering + type: string + - jsonPath: .spec.servicePlanName + name: Plan + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .status.ready + name: Ready + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.instanceID + name: ID + priority: 1 + type: string + - jsonPath: .status.conditions[0].message + name: Message + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ServiceInstance is the Schema for the serviceinstances API + 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: ServiceInstanceSpec defines the desired state of ServiceInstance + properties: + customTags: + description: List of custom tags describing the ServiceInstance, will + be copied to `ServiceBinding` secret in the key called `tags`. + items: type: string - shared: - description: Indicates the desired shared state - type: boolean - userInfo: - description: |- - UserInfo contains information about the user that last modified this - instance. This field is set by the API server and not settable by the - end-user. User-provided values for this field are not saved. + type: array + externalName: + description: The name of the instance in Service Manager + type: string + parameters: + description: |- + Provisioning parameters for the instance. + + + The Parameters field is NOT secret or secured in any way and should + NEVER be used to hold sensitive information. To set parameters that + contain secret information, you should ALWAYS store that information + in a Secret and use the ParametersFrom field. + type: object + x-kubernetes-preserve-unknown-fields: true + parametersFrom: + description: |- + List of sources to populate parameters. + If a top-level parameter name exists in multiples sources among + `Parameters` and `ParametersFrom` fields, it is + considered to be a user error in the specification + items: + description: ParametersFromSource represents the source of a set + of Parameters properties: - extra: - additionalProperties: - description: ExtraValue masks the value so protobuf can generate - items: + secretKeyRef: + description: |- + The Secret key to select from. + The value must be a JSON object. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: The name of the secret in the pod's namespace + to select from. type: string - type: array - description: Any additional information provided by the authenticator. + required: + - key + - name type: object - groups: - description: The names of groups this user is a part of. + type: object + type: array + serviceOfferingName: + description: The name of the service offering + minLength: 1 + type: string + servicePlanID: + description: The plan ID in case service offering and plan name are + ambiguous + type: string + servicePlanName: + description: The name of the service plan + minLength: 1 + type: string + shared: + description: Indicates the desired shared state + type: boolean + userInfo: + description: |- + UserInfo contains information about the user that last modified this + instance. This field is set by the API server and not settable by the + end-user. User-provided values for this field are not saved. + properties: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can generate items: type: string type: array - x-kubernetes-list-type: atomic - uid: - description: |- - A unique value that identifies this user across time. If this user is - deleted and another user by the same name is added, they will have - different UIDs. - type: string - username: - description: The name that uniquely identifies this user among - all active users. + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: type: string - type: object - required: - - serviceOfferingName - - servicePlanName - type: object - status: - description: ServiceInstanceStatus defines the observed state of ServiceInstance - properties: - conditions: - description: Service instance conditions - items: - description: "Condition contains details for one aspect of the current + type: array + x-kubernetes-list-type: atomic + uid: + description: |- + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. + type: string + username: + description: The name that uniquely identifies this user among + all active users. + type: string + type: object + required: + - serviceOfferingName + - servicePlanName + type: object + status: + description: ServiceInstanceStatus defines the observed state of ServiceInstance + properties: + conditions: + description: Service instance conditions + items: + description: "Condition contains details for one aspect of the current state of this API Resource.\n---\nThis struct is intended for direct use as an array at the field path .status.conditions. For example,\n\n\n\ttype FooStatus struct{\n\t // Represents the @@ -1016,93 +1016,93 @@ spec: \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t \ // other fields\n\t}" - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - instanceID: - description: The generated ID of the instance, will be automatically - filled once the instance is created - type: string - observedGeneration: - description: Last generation that was acted on - format: int64 - type: integer - operationType: - description: The operation type (CREATE/UPDATE/DELETE) for ongoing - operation - type: string - operationURL: - description: URL of ongoing operation for the service instance - type: string - ready: - description: Indicates whether instance is ready for usage + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + instanceID: + description: The generated ID of the instance, will be automatically + filled once the instance is created + type: string + observedGeneration: + description: Last generation that was acted on + format: int64 + type: integer + operationType: + description: The operation type (CREATE/UPDATE/DELETE) for ongoing + operation + type: string + operationURL: + description: URL of ongoing operation for the service instance + type: string + ready: + description: Indicates whether instance is ready for usage + type: string + tags: + description: Tags describing the ServiceInstance as provided in service + catalog, will be copied to `ServiceBinding` secret in the key called + `tags`. + items: type: string - tags: - description: Tags describing the ServiceInstance as provided in service - catalog, will be copied to `ServiceBinding` secret in the key called - `tags`. - items: - type: string - type: array - required: - - conditions - type: object - type: object - served: true - storage: false - subresources: - status: {} \ No newline at end of file + type: array + required: + - conditions + type: object + type: object + served: true + storage: false + subresources: + status: {}