Skip to content

Commit

Permalink
Refactor binding controllers
Browse files Browse the repository at this point in the history
The logic for servicebinding.io creation is moved to the respective
controllers and some consants are moved to the korifiv1alpha1 package
  • Loading branch information
uzabanov committed Dec 11, 2024
1 parent 2913cf9 commit 2f4e849
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 142 deletions.
4 changes: 3 additions & 1 deletion controllers/api/v1alpha1/cfservicebinding_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
140 changes: 17 additions & 123 deletions controllers/controllers/services/bindings/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -46,15 +43,15 @@ const (
)

type CredentialsReconciler interface {
ReconcileResource(ctx context.Context, cfServiceBinding *korifiv1alpha1.CFServiceBinding) (ctrl.Result, error)
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 CredentialsReconciler
managedReconciler CredentialsReconciler
}

func NewReconciler(
Expand All @@ -65,11 +62,11 @@ func NewReconciler(
managedCredentialsReconciler CredentialsReconciler,
) *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)
}
Expand Down Expand Up @@ -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)
setInstanceTypeAnnotation(cfServiceBinding, string(cfServiceInstance.Spec.Type))

err = controllerutil.SetOwnerReference(cfServiceInstance, cfServiceBinding, r.scheme)
if err != nil {
Expand All @@ -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 {
Expand All @@ -178,92 +158,6 @@ 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
func setInstanceTypeAnnotation(cfServiceBinding *korifiv1alpha1.CFServiceBinding, instanceType string) {
cfServiceBinding.Annotations = tools.SetMapValue(cfServiceBinding.Annotations, korifiv1alpha1.ServiceInstanceTypeAnnotationKey, instanceType)
}
5 changes: 2 additions & 3 deletions controllers/controllers/services/bindings/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{
Expand Down
29 changes: 28 additions & 1 deletion controllers/controllers/services/bindings/managed/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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")
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
}
Expand Down
64 changes: 64 additions & 0 deletions controllers/controllers/services/bindings/sbio/servicebinding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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 {
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
}
Loading

0 comments on commit 2f4e849

Please sign in to comment.