From bdbf31938455bad44f5f209819a600c33582cb3f Mon Sep 17 00:00:00 2001 From: Yusmen Zabanov Date: Fri, 6 Dec 2024 08:43:02 +0000 Subject: [PATCH] Refactor binding controllers The logic for servicebinding.io creation is moved to the respective controllers and some consants are moved to the korifiv1alpha1 package --- .../api/v1alpha1/cfservicebinding_types.go | 4 +- .../services/bindings/controller.go | 146 +++--------------- .../services/bindings/controller_test.go | 5 +- .../services/bindings/managed/controller.go | 29 +++- .../services/bindings/sbio/servicebinding.go | 58 +++++++ .../bindings/sbio/servicebinding_test.go | 81 ++++++++++ .../services/bindings/sbio/suite_test.go | 13 ++ .../services/bindings/upsi/controller.go | 54 +++++-- tools/collections.go | 9 ++ tools/collections_test.go | 10 ++ 10 files changed, 262 insertions(+), 147 deletions(-) create mode 100644 controllers/controllers/services/bindings/sbio/servicebinding.go create mode 100644 controllers/controllers/services/bindings/sbio/servicebinding_test.go create mode 100644 controllers/controllers/services/bindings/sbio/suite_test.go diff --git a/controllers/api/v1alpha1/cfservicebinding_types.go b/controllers/api/v1alpha1/cfservicebinding_types.go index fae76941c..f95858069 100644 --- a/controllers/api/v1alpha1/cfservicebinding_types.go +++ b/controllers/api/v1alpha1/cfservicebinding_types.go @@ -31,7 +31,9 @@ const ( ServiceInstanceTypeAnnotationKey = "korifi.cloudfoundry.org/service-instance-type" PlanGUIDLabelKey = "korifi.cloudfoundry.org/plan-guid" - CFServiceBindingFinalizerName = "cfServiceBinding.korifi.cloudfoundry.org" + ServiceBindingGUIDLabel = "korifi.cloudfoundry.org/service-binding-guid" + ServiceCredentialBindingTypeLabel = "korifi.cloudfoundry.org/service-credential-binding-type" + CFServiceBindingFinalizerName = "cfServiceBinding.korifi.cloudfoundry.org" ) // CFServiceBindingSpec defines the desired state of CFServiceBinding diff --git a/controllers/controllers/services/bindings/controller.go b/controllers/controllers/services/bindings/controller.go index 1127c1785..b1013044b 100644 --- a/controllers/controllers/services/bindings/controller.go +++ b/controllers/controllers/services/bindings/controller.go @@ -18,17 +18,14 @@ package bindings import ( "context" - "fmt" korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" "code.cloudfoundry.org/korifi/controllers/controllers/shared" + "code.cloudfoundry.org/korifi/tools" "code.cloudfoundry.org/korifi/tools/k8s" "github.com/go-logr/logr" servicebindingv1beta1 "github.com/servicebinding/runtime/apis/v1beta1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" @@ -45,31 +42,31 @@ const ( ServiceBindingSecretTypePrefix = "servicebinding.io/" ) -type CredentialsReconciler interface { - ReconcileResource(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding) (ctrl.Result, error) +type DelegateReconciler interface { + ReconcileResource(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding, cfServiceInstance *korifiv1alpha1.CFServiceInstance) (ctrl.Result, error) } type Reconciler struct { - k8sClient client.Client - scheme *runtime.Scheme - log logr.Logger - upsiCredentialsReconciler CredentialsReconciler - managedCredentialsReconciler CredentialsReconciler + k8sClient client.Client + scheme *runtime.Scheme + log logr.Logger + upsiReconciler DelegateReconciler + managedReconciler DelegateReconciler } func NewReconciler( k8sClient client.Client, scheme *runtime.Scheme, log logr.Logger, - upsiCredentialsReconciler CredentialsReconciler, - managedCredentialsReconciler CredentialsReconciler, + upsiCredentialsReconciler DelegateReconciler, + managedCredentialsReconciler DelegateReconciler, ) *k8s.PatchingReconciler[korifiv1alpha1.CFServiceBinding, *korifiv1alpha1.CFServiceBinding] { cfBindingReconciler := &Reconciler{ - k8sClient: k8sClient, - scheme: scheme, - log: log, - upsiCredentialsReconciler: upsiCredentialsReconciler, - managedCredentialsReconciler: managedCredentialsReconciler, + k8sClient: k8sClient, + scheme: scheme, + log: log, + upsiReconciler: upsiCredentialsReconciler, + managedReconciler: managedCredentialsReconciler, } return k8s.NewPatchingReconciler(log, k8sClient, cfBindingReconciler) } @@ -126,10 +123,7 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceBinding *ko return ctrl.Result{}, err } - if cfServiceBinding.Annotations == nil { - cfServiceBinding.Annotations = map[string]string{} - } - cfServiceBinding.Annotations[korifiv1alpha1.ServiceInstanceTypeAnnotationKey] = string(cfServiceInstance.Spec.Type) + cfServiceBinding.Annotations = tools.SetMapValue(cfServiceBinding.Annotations, korifiv1alpha1.ServiceInstanceTypeAnnotationKey, string(cfServiceInstance.Spec.Type)) err = controllerutil.SetOwnerReference(cfServiceInstance, cfServiceBinding, r.scheme) if err != nil { @@ -145,29 +139,15 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceBinding *ko return res, err } - sbServiceBinding, err := r.reconcileSBServiceBinding(ctx, cfServiceBinding) - if err != nil { - log.Info("error creating/updating servicebinding.io servicebinding", "reason", err) - return ctrl.Result{}, err - } - - if !isSbServiceBindingReady(sbServiceBinding) { - return ctrl.Result{}, k8s.NewNotReadyError().WithReason("ServiceBindingNotReady") - } - return ctrl.Result{}, nil } func (r *Reconciler) reconcileByType(ctx context.Context, cfServiceInstance *korifiv1alpha1.CFServiceInstance, cfServiceBinding *korifiv1alpha1.CFServiceBinding) (ctrl.Result, error) { if cfServiceInstance.Spec.Type == korifiv1alpha1.UserProvidedType { - return r.upsiCredentialsReconciler.ReconcileResource(ctx, cfServiceBinding) + return r.upsiReconciler.ReconcileResource(ctx, cfServiceBinding, cfServiceInstance) } - if cfServiceBinding.Labels == nil { - cfServiceBinding.Labels = map[string]string{} - } - cfServiceBinding.Labels[korifiv1alpha1.PlanGUIDLabelKey] = cfServiceInstance.Spec.PlanGUID - return r.managedCredentialsReconciler.ReconcileResource(ctx, cfServiceBinding) + return r.managedReconciler.ReconcileResource(ctx, cfServiceBinding, cfServiceInstance) } func needsRequeue(res ctrl.Result, err error) bool { @@ -177,93 +157,3 @@ func needsRequeue(res ctrl.Result, err error) bool { return !res.IsZero() } - -func isSbServiceBindingReady(sbServiceBinding *servicebindingv1beta1.ServiceBinding) bool { - readyCondition := meta.FindStatusCondition(sbServiceBinding.Status.Conditions, "Ready") - if readyCondition == nil { - return false - } - - if readyCondition.Status != metav1.ConditionTrue { - return false - } - - return sbServiceBinding.Generation == sbServiceBinding.Status.ObservedGeneration -} - -func (r *Reconciler) reconcileSBServiceBinding(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding) (*servicebindingv1beta1.ServiceBinding, error) { - bindingSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: cfServiceBinding.Status.Binding.Name, - Namespace: cfServiceBinding.Namespace, - }, - } - - err := r.k8sClient.Get(ctx, client.ObjectKeyFromObject(bindingSecret), bindingSecret) - if err != nil { - return nil, fmt.Errorf("failed to get service binding credentials secret %q: %w", cfServiceBinding.Status.Binding.Name, err) - } - - sbServiceBinding := r.toSBServiceBinding(cfServiceBinding) - - _, err = controllerutil.CreateOrPatch(ctx, r.k8sClient, sbServiceBinding, func() error { - sbServiceBinding.Spec.Name = getSBServiceBindingName(cfServiceBinding) - - if cfServiceBinding.Annotations[korifiv1alpha1.ServiceInstanceTypeAnnotationKey] == korifiv1alpha1.UserProvidedType { - secretType, hasType := bindingSecret.Data["type"] - if hasType && len(secretType) > 0 { - sbServiceBinding.Spec.Type = string(secretType) - } - - secretProvider, hasProvider := bindingSecret.Data["provider"] - if hasProvider { - sbServiceBinding.Spec.Provider = string(secretProvider) - } - } - return controllerutil.SetControllerReference(cfServiceBinding, sbServiceBinding, r.scheme) - }) - if err != nil { - return nil, err - } - - return sbServiceBinding, nil -} - -func (r *Reconciler) toSBServiceBinding(cfServiceBinding *korifiv1alpha1.CFServiceBinding) *servicebindingv1beta1.ServiceBinding { - return &servicebindingv1beta1.ServiceBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("cf-binding-%s", cfServiceBinding.Name), - Namespace: cfServiceBinding.Namespace, - Labels: map[string]string{ - ServiceBindingGUIDLabel: cfServiceBinding.Name, - korifiv1alpha1.CFAppGUIDLabelKey: cfServiceBinding.Spec.AppRef.Name, - ServiceCredentialBindingTypeLabel: "app", - }, - }, - Spec: servicebindingv1beta1.ServiceBindingSpec{ - Type: cfServiceBinding.Annotations[korifiv1alpha1.ServiceInstanceTypeAnnotationKey], - Workload: servicebindingv1beta1.ServiceBindingWorkloadReference{ - APIVersion: "apps/v1", - Kind: "StatefulSet", - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - korifiv1alpha1.CFAppGUIDLabelKey: cfServiceBinding.Spec.AppRef.Name, - }, - }, - }, - Service: servicebindingv1beta1.ServiceBindingServiceReference{ - APIVersion: "korifi.cloudfoundry.org/v1alpha1", - Kind: "CFServiceBinding", - Name: cfServiceBinding.Name, - }, - }, - } -} - -func getSBServiceBindingName(cfServiceBinding *korifiv1alpha1.CFServiceBinding) string { - if cfServiceBinding.Spec.DisplayName != nil { - return *cfServiceBinding.Spec.DisplayName - } - - return cfServiceBinding.Status.Binding.Name -} diff --git a/controllers/controllers/services/bindings/controller_test.go b/controllers/controllers/services/bindings/controller_test.go index 3f2723a1d..c258b486c 100644 --- a/controllers/controllers/services/bindings/controller_test.go +++ b/controllers/controllers/services/bindings/controller_test.go @@ -6,7 +6,6 @@ import ( "fmt" korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" - "code.cloudfoundry.org/korifi/controllers/controllers/services/bindings" "code.cloudfoundry.org/korifi/controllers/controllers/services/osbapi" "code.cloudfoundry.org/korifi/controllers/controllers/services/osbapi/fake" "code.cloudfoundry.org/korifi/model/services" @@ -198,9 +197,9 @@ var _ = Describe("CFServiceBinding", func() { g.Expect(sbServiceBinding.Spec.Provider).To(BeEmpty()) g.Expect(sbServiceBinding.Labels).To(SatisfyAll( - HaveKeyWithValue(bindings.ServiceBindingGUIDLabel, binding.Name), + HaveKeyWithValue(korifiv1alpha1.ServiceBindingGUIDLabel, binding.Name), HaveKeyWithValue(korifiv1alpha1.CFAppGUIDLabelKey, cfAppGUID), - HaveKeyWithValue(bindings.ServiceCredentialBindingTypeLabel, "app"), + HaveKeyWithValue(korifiv1alpha1.ServiceCredentialBindingTypeLabel, "app"), )) g.Expect(sbServiceBinding.OwnerReferences).To(ConsistOf(MatchFields(IgnoreExtras, Fields{ diff --git a/controllers/controllers/services/bindings/managed/controller.go b/controllers/controllers/services/bindings/managed/controller.go index e83e2fd5c..0c0763813 100644 --- a/controllers/controllers/services/bindings/managed/controller.go +++ b/controllers/controllers/services/bindings/managed/controller.go @@ -4,6 +4,7 @@ import ( "context" "time" + "code.cloudfoundry.org/korifi/controllers/controllers/services/bindings/sbio" "code.cloudfoundry.org/korifi/controllers/controllers/services/osbapi" "code.cloudfoundry.org/korifi/tools" "code.cloudfoundry.org/korifi/tools/k8s" @@ -12,6 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "code.cloudfoundry.org/korifi/controllers/controllers/services/credentials" + servicebindingv1beta1 "github.com/servicebinding/runtime/apis/v1beta1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -37,13 +39,15 @@ func NewReconciler(k8sClient client.Client, brokerClientFactory osbapi.BrokerCli } } -func (r *ManagedBindingsReconciler) ReconcileResource(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding) (ctrl.Result, error) { +func (r *ManagedBindingsReconciler) ReconcileResource(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding, cfServiceInstance *korifiv1alpha1.CFServiceInstance) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx).WithName("reconcile-managed-service-binding") if !cfServiceBinding.GetDeletionTimestamp().IsZero() { return r.finalizeCFServiceBinding(ctx, cfServiceBinding) } + cfServiceBinding.Labels = tools.SetMapValue(cfServiceBinding.Labels, korifiv1alpha1.PlanGUIDLabelKey, cfServiceInstance.Spec.PlanGUID) + assets, err := r.assets.GetServiceBindingAssets(ctx, cfServiceBinding) if err != nil { log.Error(err, "failed to get service binding assets") @@ -74,6 +78,16 @@ func (r *ManagedBindingsReconciler) ReconcileResource(ctx context.Context, cfSer return ctrl.Result{}, err } + sbServiceBinding, err := r.reconcileSBServiceBinding(ctx, cfServiceBinding) + if err != nil { + log.Info("error creating/updating servicebinding.io servicebinding", "reason", err) + return ctrl.Result{}, err + } + + if !sbio.IsSbServiceBindingReady(sbServiceBinding) { + return ctrl.Result{}, k8s.NewNotReadyError().WithReason("ServiceBindingNotReady") + } + return ctrl.Result{}, nil } @@ -254,6 +268,19 @@ func (r *ManagedBindingsReconciler) finalizeCFServiceBinding( return ctrl.Result{}, nil } +func (r *ManagedBindingsReconciler) reconcileSBServiceBinding(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding) (*servicebindingv1beta1.ServiceBinding, error) { + sbServiceBinding := sbio.ToSBServiceBinding(cfServiceBinding, korifiv1alpha1.ManagedType) + + _, err := controllerutil.CreateOrPatch(ctx, r.k8sClient, sbServiceBinding, func() error { + return controllerutil.SetControllerReference(cfServiceBinding, sbServiceBinding, r.scheme) + }) + if err != nil { + return nil, err + } + + return sbServiceBinding, nil +} + func isBindRequested(binding *korifiv1alpha1.CFServiceBinding) bool { return meta.IsStatusConditionTrue(binding.Status.Conditions, korifiv1alpha1.BindingRequestedCondition) } diff --git a/controllers/controllers/services/bindings/sbio/servicebinding.go b/controllers/controllers/services/bindings/sbio/servicebinding.go new file mode 100644 index 000000000..6dabe3f4a --- /dev/null +++ b/controllers/controllers/services/bindings/sbio/servicebinding.go @@ -0,0 +1,58 @@ +package sbio + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + + korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + servicebindingv1beta1 "github.com/servicebinding/runtime/apis/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func ToSBServiceBinding(cfServiceBinding *korifiv1alpha1.CFServiceBinding, bindingType string) *servicebindingv1beta1.ServiceBinding { + return &servicebindingv1beta1.ServiceBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("cf-binding-%s", cfServiceBinding.Name), + Namespace: cfServiceBinding.Namespace, + Labels: map[string]string{ + korifiv1alpha1.ServiceBindingGUIDLabel: cfServiceBinding.Name, + korifiv1alpha1.CFAppGUIDLabelKey: cfServiceBinding.Spec.AppRef.Name, + korifiv1alpha1.ServiceCredentialBindingTypeLabel: "app", + }, + }, + Spec: servicebindingv1beta1.ServiceBindingSpec{ + Name: getSBServiceBindingName(cfServiceBinding), + Type: bindingType, + Workload: servicebindingv1beta1.ServiceBindingWorkloadReference{ + APIVersion: "apps/v1", + Kind: "StatefulSet", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + korifiv1alpha1.CFAppGUIDLabelKey: cfServiceBinding.Spec.AppRef.Name, + }, + }, + }, + Service: servicebindingv1beta1.ServiceBindingServiceReference{ + APIVersion: "korifi.cloudfoundry.org/v1alpha1", + Kind: "CFServiceBinding", + Name: cfServiceBinding.Name, + }, + }, + } +} + +func getSBServiceBindingName(cfServiceBinding *korifiv1alpha1.CFServiceBinding) string { + if cfServiceBinding.Spec.DisplayName != nil { + return *cfServiceBinding.Spec.DisplayName + } + + return cfServiceBinding.Status.Binding.Name +} + +func IsSbServiceBindingReady(sbServiceBinding *servicebindingv1beta1.ServiceBinding) bool { + if meta.IsStatusConditionTrue(sbServiceBinding.Status.Conditions, korifiv1alpha1.StatusConditionReady) { + return true + } + return sbServiceBinding.Generation == sbServiceBinding.Status.ObservedGeneration +} diff --git a/controllers/controllers/services/bindings/sbio/servicebinding_test.go b/controllers/controllers/services/bindings/sbio/servicebinding_test.go new file mode 100644 index 000000000..02095b38b --- /dev/null +++ b/controllers/controllers/services/bindings/sbio/servicebinding_test.go @@ -0,0 +1,81 @@ +package sbio_test + +import ( + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + servicebindingv1beta1 "github.com/servicebinding/runtime/apis/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + "code.cloudfoundry.org/korifi/controllers/controllers/services/bindings/sbio" +) + +var _ = Describe("ToSBServiceBinding", func() { + var ( + cfServiceBinding *korifiv1alpha1.CFServiceBinding + bindingName string + ) + + BeforeEach(func() { + bindingName = "cf-binding" + + cfServiceBinding = &korifiv1alpha1.CFServiceBinding{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: bindingName, + Namespace: uuid.NewString(), + Finalizers: []string{ + korifiv1alpha1.CFServiceBindingFinalizerName, + }, + }, + Spec: korifiv1alpha1.CFServiceBindingSpec{ + Service: corev1.ObjectReference{ + Kind: "ServiceInstance", + Name: uuid.NewString(), + APIVersion: "korifi.cloudfoundry.org/v1alpha1", + }, + AppRef: corev1.LocalObjectReference{ + Name: uuid.NewString(), + }, + }, + Status: korifiv1alpha1.CFServiceBindingStatus{ + Binding: corev1.LocalObjectReference{ + Name: bindingName, + }, + }, + } + }) + It("should transform CFServiceBinding to SBerviceBinding correctly", func() { + Expect(sbio.ToSBServiceBinding(cfServiceBinding, korifiv1alpha1.ManagedType)).To(Equal(&servicebindingv1beta1.ServiceBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cf-binding-cf-binding", + Namespace: cfServiceBinding.Namespace, + Labels: map[string]string{ + korifiv1alpha1.ServiceBindingGUIDLabel: bindingName, + korifiv1alpha1.CFAppGUIDLabelKey: cfServiceBinding.Spec.AppRef.Name, + korifiv1alpha1.ServiceCredentialBindingTypeLabel: "app", + }, + }, + Spec: servicebindingv1beta1.ServiceBindingSpec{ + Name: "cf-binding", + Type: korifiv1alpha1.ManagedType, + Workload: servicebindingv1beta1.ServiceBindingWorkloadReference{ + APIVersion: "apps/v1", + Kind: "StatefulSet", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + korifiv1alpha1.CFAppGUIDLabelKey: cfServiceBinding.Spec.AppRef.Name, + }, + }, + }, + Service: servicebindingv1beta1.ServiceBindingServiceReference{ + APIVersion: "korifi.cloudfoundry.org/v1alpha1", + Kind: "CFServiceBinding", + Name: bindingName, + }, + }, + })) + }) +}) diff --git a/controllers/controllers/services/bindings/sbio/suite_test.go b/controllers/controllers/services/bindings/sbio/suite_test.go new file mode 100644 index 000000000..97ffdcd5b --- /dev/null +++ b/controllers/controllers/services/bindings/sbio/suite_test.go @@ -0,0 +1,13 @@ +package sbio_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestSbio(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Sbio Suite") +} diff --git a/controllers/controllers/services/bindings/upsi/controller.go b/controllers/controllers/services/bindings/upsi/controller.go index bdd33fb97..65c45a04f 100644 --- a/controllers/controllers/services/bindings/upsi/controller.go +++ b/controllers/controllers/services/bindings/upsi/controller.go @@ -9,12 +9,13 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + "code.cloudfoundry.org/korifi/controllers/controllers/services/bindings/sbio" "code.cloudfoundry.org/korifi/controllers/controllers/services/credentials" + servicebindingv1beta1 "github.com/servicebinding/runtime/apis/v1beta1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -33,7 +34,7 @@ func NewReconciler(k8sClient client.Client, scheme *runtime.Scheme) *UPSIBinding } } -func (r *UPSIBindingReconciler) ReconcileResource(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding) (ctrl.Result, error) { +func (r *UPSIBindingReconciler) ReconcileResource(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding, cfServiceInstance *korifiv1alpha1.CFServiceInstance) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) if !cfServiceBinding.GetDeletionTimestamp().IsZero() { @@ -44,13 +45,6 @@ func (r *UPSIBindingReconciler) ReconcileResource(ctx context.Context, cfService return ctrl.Result{}, nil } - cfServiceInstance := new(korifiv1alpha1.CFServiceInstance) - err := r.k8sClient.Get(ctx, types.NamespacedName{Name: cfServiceBinding.Spec.Service.Name, Namespace: cfServiceBinding.Namespace}, cfServiceInstance) - if err != nil { - log.Info("service instance not found", "service-instance", cfServiceBinding.Spec.Service.Name, "error", err) - return ctrl.Result{}, err - } - if cfServiceInstance.Status.Credentials.Name == "" { return ctrl.Result{}, k8s.NewNotReadyError(). WithReason("CredentialsSecretNotAvailable"). @@ -58,7 +52,7 @@ func (r *UPSIBindingReconciler) ReconcileResource(ctx context.Context, cfService WithRequeueAfter(time.Second) } - err = r.reconcileCredentials(ctx, cfServiceInstance, cfServiceBinding) + bindingSecret, err := r.reconcileCredentials(ctx, cfServiceInstance, cfServiceBinding) if err != nil { if k8serrors.IsInvalid(err) { err = r.k8sClient.Delete(ctx, &corev1.Secret{ @@ -74,10 +68,20 @@ func (r *UPSIBindingReconciler) ReconcileResource(ctx context.Context, cfService return ctrl.Result{}, err } + sbServiceBinding, err := r.reconcileSBServiceBinding(ctx, cfServiceBinding, bindingSecret) + if err != nil { + log.Info("error creating/updating servicebinding.io servicebinding", "reason", err) + return ctrl.Result{}, err + } + + if !sbio.IsSbServiceBindingReady(sbServiceBinding) { + return ctrl.Result{}, k8s.NewNotReadyError().WithReason("ServiceBindingNotReady") + } + return ctrl.Result{}, nil } -func (r *UPSIBindingReconciler) reconcileCredentials(ctx context.Context, cfServiceInstance *korifiv1alpha1.CFServiceInstance, cfServiceBinding *korifiv1alpha1.CFServiceBinding) error { +func (r *UPSIBindingReconciler) reconcileCredentials(ctx context.Context, cfServiceInstance *korifiv1alpha1.CFServiceInstance, cfServiceBinding *korifiv1alpha1.CFServiceBinding) (*corev1.Secret, error) { cfServiceBinding.Status.Credentials.Name = cfServiceInstance.Status.Credentials.Name credentialsSecret := &corev1.Secret{ @@ -88,7 +92,7 @@ func (r *UPSIBindingReconciler) reconcileCredentials(ctx context.Context, cfServ } err := r.k8sClient.Get(ctx, client.ObjectKeyFromObject(credentialsSecret), credentialsSecret) if err != nil { - return fmt.Errorf("failed to get service instance credentials secret %q: %w", cfServiceInstance.Status.Credentials.Name, err) + return nil, fmt.Errorf("failed to get service instance credentials secret %q: %w", cfServiceInstance.Status.Credentials.Name, err) } bindingSecret := &corev1.Secret{ @@ -111,10 +115,32 @@ func (r *UPSIBindingReconciler) reconcileCredentials(ctx context.Context, cfServ return controllerutil.SetControllerReference(cfServiceBinding, bindingSecret, r.scheme) }) if err != nil { - return errors.Wrap(err, "failed to create binding secret") + return nil, errors.Wrap(err, "failed to create binding secret") } cfServiceBinding.Status.Binding.Name = bindingSecret.Name - return nil + return bindingSecret, nil +} + +func (r *UPSIBindingReconciler) reconcileSBServiceBinding(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding, bindingSecret *corev1.Secret) (*servicebindingv1beta1.ServiceBinding, error) { + sbServiceBinding := sbio.ToSBServiceBinding(cfServiceBinding, korifiv1alpha1.UserProvidedType) + + _, err := controllerutil.CreateOrPatch(ctx, r.k8sClient, sbServiceBinding, func() error { + secretType, hasType := bindingSecret.Data["type"] + if hasType && len(secretType) > 0 { + sbServiceBinding.Spec.Type = string(secretType) + } + + secretProvider, hasProvider := bindingSecret.Data["provider"] + if hasProvider { + sbServiceBinding.Spec.Provider = string(secretProvider) + } + return controllerutil.SetControllerReference(cfServiceBinding, sbServiceBinding, r.scheme) + }) + if err != nil { + return nil, err + } + + return sbServiceBinding, nil } diff --git a/tools/collections.go b/tools/collections.go index 76161b080..2276d7045 100644 --- a/tools/collections.go +++ b/tools/collections.go @@ -25,3 +25,12 @@ func NilOrEquals[E comparable](value *E, expectedValue E) bool { return expectedValue == *value } + +func SetMapValue[K comparable, V any](m map[K]V, key K, value V) map[K]V { + if m == nil { + m = map[K]V{} + } + m[key] = value + + return m +} diff --git a/tools/collections_test.go b/tools/collections_test.go index 0383cc112..8dd8a9380 100644 --- a/tools/collections_test.go +++ b/tools/collections_test.go @@ -32,4 +32,14 @@ var _ = Describe("Collections", func() { Entry("equal", tools.PtrTo("needle"), BeTrue()), Entry("not-equal", tools.PtrTo("not-needle"), BeFalse()), ) + + DescribeTable("SetMapValue", + func(m map[string]int, key string, value int, expected map[string]int) { + result := tools.SetMapValue(m, key, value) + Expect(result).To(Equal(expected)) + }, + Entry("Set value in non-nil map", map[string]int{"a": 1}, "b", 2, map[string]int{"a": 1, "b": 2}), + Entry("Set value in nil map", nil, "a", 1, map[string]int{"a": 1}), + Entry("Update existing key in map", map[string]int{"a": 1}, "a", 2, map[string]int{"a": 2}), + ) })