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 18, 2024
1 parent 839d61f commit bdbf319
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 147 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
146 changes: 18 additions & 128 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 @@ -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)
}
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)
cfServiceBinding.Annotations = tools.SetMapValue(cfServiceBinding.Annotations, korifiv1alpha1.ServiceInstanceTypeAnnotationKey, 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 @@ -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
}
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
58 changes: 58 additions & 0 deletions controllers/controllers/services/bindings/sbio/servicebinding.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit bdbf319

Please sign in to comment.