Skip to content

Commit

Permalink
Add CFServiceBindings to CFBuildController (#649)
Browse files Browse the repository at this point in the history
#463
- Updates ServiceInstance Secret to include type
- Updates BuildController to add ServiceBinding

Co-authored-by: Clint Yoshimura <clinty@vmware.com>
Co-authored-by: Julian Hjortshoj <hjortshojj@vmware.com>
  • Loading branch information
3 people authored Feb 11, 2022
1 parent 1f9014e commit d84001c
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 45 deletions.
20 changes: 10 additions & 10 deletions api/repositories/service_instance_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,16 @@ import (
"sort"
"strings"

"sigs.k8s.io/controller-runtime/pkg/client"
servicesv1alpha1 "code.cloudfoundry.org/cf-k8s-controllers/controllers/apis/services/v1alpha1"

"code.cloudfoundry.org/cf-k8s-controllers/api/authorization"
"github.com/google/uuid"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
k8serrors "k8s.io/apimachinery/pkg/api/errors"

"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

corev1 "k8s.io/api/core/v1"

servicesv1alpha1 "code.cloudfoundry.org/cf-k8s-controllers/controllers/apis/services/v1alpha1"
"github.com/google/uuid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"code.cloudfoundry.org/cf-k8s-controllers/api/authorization"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

//+kubebuilder:rbac:groups=services.cloudfoundry.org,resources=cfserviceinstances,verbs=list;create
Expand Down Expand Up @@ -92,6 +88,10 @@ func (r *ServiceInstanceRepo) CreateServiceInstance(ctx context.Context, authInf
secretObj := cfServiceInstanceToSecret(cfServiceInstance)
_, err = controllerutil.CreateOrPatch(ctx, userClient, &secretObj, func() error {
secretObj.StringData = message.Credentials
if secretObj.StringData == nil {
secretObj.StringData = map[string]string{}
}
secretObj.StringData["type"] = servicesv1alpha1.UserProvidedType
return nil
})
if err != nil {
Expand Down
16 changes: 10 additions & 6 deletions api/repositories/service_instance_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ var _ = Describe("ServiceInstanceRepository", func() {
})

When("no ServiceInstance credentials are given", func() {
It("creates an empty secret and sets the secret ref on the ServiceInstance", func() {
It("creates a secret and sets the secret ref on the ServiceInstance", func() {
createdServceInstanceRecord, err := serviceInstanceRepo.CreateServiceInstance(testCtx, authInfo, serviceInstanceCreateMessage)
Expect(err).NotTo(HaveOccurred())
Expect(createdServceInstanceRecord).NotTo(BeNil())
Expand All @@ -96,16 +96,19 @@ var _ = Describe("ServiceInstanceRepository", func() {
return k8sClient.Get(context.Background(), secretLookupKey, createdSecret)
}, 10*time.Second, 250*time.Millisecond).Should(Succeed())

Expect(createdSecret.Data).To(BeEmpty())
Expect(createdSecret.Data).To(MatchAllKeys(Keys{
"type": BeEquivalentTo("user-provided"),
}))
Expect(createdSecret.Type).To(Equal(corev1.SecretType("servicebinding.io/user-provided")))
})
})

When("ServiceInstance credentials are given", func() {
BeforeEach(func() {
serviceInstanceCreateMessage.Credentials = map[string]string{
"foo": "bar",
"baz": "baz",
"type": "i get clobbered",
"foo": "bar",
"baz": "baz",
}
})

Expand All @@ -122,8 +125,9 @@ var _ = Describe("ServiceInstanceRepository", func() {
}, 10*time.Second, 250*time.Millisecond).Should(Succeed())

Expect(createdSecret.Data).To(MatchAllKeys(Keys{
"foo": BeEquivalentTo("bar"),
"baz": BeEquivalentTo("baz"),
"type": BeEquivalentTo("user-provided"),
"foo": BeEquivalentTo("bar"),
"baz": BeEquivalentTo("baz"),
}))
})
})
Expand Down
4 changes: 4 additions & 0 deletions controllers/apis/services/v1alpha1/cfserviceinstance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import (
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

const (
UserProvidedType = "user-provided"
)

// CFServiceInstanceSpec defines the desired state of CFServiceInstance
type CFServiceInstanceSpec struct {
// Name defines the name of the Service Instance
Expand Down
62 changes: 50 additions & 12 deletions controllers/controllers/workloads/cfbuild_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
"path"
"strings"

servicesv1alpha1 "code.cloudfoundry.org/cf-k8s-controllers/controllers/apis/services/v1alpha1"
"code.cloudfoundry.org/cf-k8s-controllers/controllers/controllers/shared"

workloadsv1alpha1 "code.cloudfoundry.org/cf-k8s-controllers/controllers/apis/workloads/v1alpha1"
"code.cloudfoundry.org/cf-k8s-controllers/controllers/config"

Expand Down Expand Up @@ -108,35 +111,35 @@ type CFBuildReconciler struct {
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
func (r *CFBuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var cfBuild workloadsv1alpha1.CFBuild
err := r.Client.Get(ctx, req.NamespacedName, &cfBuild)
cfBuild := new(workloadsv1alpha1.CFBuild)
err := r.Client.Get(ctx, req.NamespacedName, cfBuild)
if err != nil {
r.Log.Error(err, "Error when fetching CFBuild")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

var cfApp workloadsv1alpha1.CFApp
err = r.Client.Get(ctx, types.NamespacedName{Name: cfBuild.Spec.AppRef.Name, Namespace: cfBuild.Namespace}, &cfApp)
cfApp := new(workloadsv1alpha1.CFApp)
err = r.Client.Get(ctx, types.NamespacedName{Name: cfBuild.Spec.AppRef.Name, Namespace: cfBuild.Namespace}, cfApp)
if err != nil {
r.Log.Error(err, "Error when fetching CFApp")
return ctrl.Result{}, err
}

originalCFBuild := cfBuild.DeepCopy()
err = controllerutil.SetOwnerReference(&cfApp, &cfBuild, r.Scheme)
err = controllerutil.SetOwnerReference(cfApp, cfBuild, r.Scheme)
if err != nil {
r.Log.Error(err, "unable to set owner reference on CFBuild")
return ctrl.Result{}, err
}

err = r.Client.Patch(ctx, &cfBuild, client.MergeFrom(originalCFBuild))
err = r.Client.Patch(ctx, cfBuild, client.MergeFrom(originalCFBuild))
if err != nil {
r.Log.Error(err, fmt.Sprintf("Error setting owner reference on the CFBuild %s/%s", req.Namespace, cfBuild.Name))
return ctrl.Result{}, err
}

var cfPackage workloadsv1alpha1.CFPackage
err = r.Client.Get(ctx, types.NamespacedName{Name: cfBuild.Spec.PackageRef.Name, Namespace: cfBuild.Namespace}, &cfPackage)
cfPackage := new(workloadsv1alpha1.CFPackage)
err = r.Client.Get(ctx, types.NamespacedName{Name: cfBuild.Spec.PackageRef.Name, Namespace: cfBuild.Namespace}, cfPackage)
if err != nil {
r.Log.Error(err, "Error when fetching CFPackage")
return ctrl.Result{}, err
Expand All @@ -150,7 +153,14 @@ func (r *CFBuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
// Scenario: CFBuild newly created and all status conditions are unknown, it
// Creates a KpackImage resource to trigger staging.
// Updates status on CFBuild -> sets staging to True.
err = r.createKpackImageAndUpdateStatus(ctx, &cfBuild, &cfApp, &cfPackage)
var appServiceBindings []servicesv1alpha1.CFServiceBinding
appServiceBindings, err = r.getAppServiceBindings(ctx, cfApp.Name, cfApp.Namespace)
if err != nil {
r.Log.Error(err, "Error when listing CFServiceBindings")
return ctrl.Result{}, err
}

err = r.createKpackImageAndUpdateStatus(ctx, cfBuild, cfApp, cfPackage, appServiceBindings)
if err != nil {
return ctrl.Result{}, err
}
Expand All @@ -175,7 +185,7 @@ func (r *CFBuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
failureStatusConditionMessage := r.concatenateStrings(":", kpackReadyStatusCondition.Reason, kpackReadyStatusCondition.Message)
setStatusConditionOnLocalCopy(&cfBuild.Status.Conditions, workloadsv1alpha1.StagingConditionType, metav1.ConditionFalse, "kpack", "kpack")
setStatusConditionOnLocalCopy(&cfBuild.Status.Conditions, workloadsv1alpha1.SucceededConditionType, metav1.ConditionFalse, "kpack", failureStatusConditionMessage)
if err = r.Client.Status().Update(ctx, &cfBuild); err != nil {
if err = r.Client.Status().Update(ctx, cfBuild); err != nil {
r.Log.Error(err, "Error when updating CFBuild status")
return ctrl.Result{}, err
}
Expand All @@ -202,7 +212,7 @@ func (r *CFBuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
}

// Call Status().Update() tp push updates to the server
if err := r.Client.Status().Update(ctx, &cfBuild); err != nil {
if err := r.Client.Status().Update(ctx, cfBuild); err != nil {
r.Log.Error(err, "Error when updating CFBuild status")
return ctrl.Result{}, err
}
Expand All @@ -211,7 +221,24 @@ func (r *CFBuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
return ctrl.Result{}, nil
}

func (r *CFBuildReconciler) createKpackImageAndUpdateStatus(ctx context.Context, cfBuild *workloadsv1alpha1.CFBuild, cfApp *workloadsv1alpha1.CFApp, cfPackage *workloadsv1alpha1.CFPackage) error {
func (r *CFBuildReconciler) getAppServiceBindings(ctx context.Context, appGUID string, namespace string) ([]servicesv1alpha1.CFServiceBinding, error) {
serviceBindings := &servicesv1alpha1.CFServiceBindingList{}
err := r.Client.List(ctx, serviceBindings,
client.InNamespace(namespace),
client.MatchingFields{shared.IndexServiceBindingAppGUID: appGUID},
)
if err != nil {
return nil, fmt.Errorf("error listing CFServiceBindings: %w", err)
}

if len(serviceBindings.Items) == 0 {
return nil, nil
}

return serviceBindings.Items, nil
}

func (r *CFBuildReconciler) createKpackImageAndUpdateStatus(ctx context.Context, cfBuild *workloadsv1alpha1.CFBuild, cfApp *workloadsv1alpha1.CFApp, cfPackage *workloadsv1alpha1.CFPackage, serviceBindings []servicesv1alpha1.CFServiceBinding) error {
serviceAccountName := kpackServiceAccount
kpackImageTag := path.Join(r.ControllerConfig.KpackImageTag, cfBuild.Name)
kpackImageName := cfBuild.Name
Expand Down Expand Up @@ -239,8 +266,19 @@ func (r *CFBuildReconciler) createKpackImageAndUpdateStatus(ctx context.Context,
ImagePullSecrets: cfPackage.Spec.Source.Registry.ImagePullSecrets,
},
},
Build: &buildv1alpha2.ImageBuild{
Services: buildv1alpha2.Services{},
},
},
}
for _, serviceBinding := range serviceBindings {
objRef := corev1.ObjectReference{
Kind: "Secret",
Name: serviceBinding.Spec.SecretName,
APIVersion: "v1",
}
desiredKpackImage.Spec.Build.Services = append(desiredKpackImage.Spec.Build.Services, objRef)
}

err := controllerutil.SetOwnerReference(cfBuild, &desiredKpackImage, r.Scheme)
if err != nil {
Expand Down
11 changes: 11 additions & 0 deletions controllers/controllers/workloads/cfbuild_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,17 @@ var _ = Describe("CFBuildReconciler", func() {
})
})

When("list CFServiceBindings returns an error", func() {
BeforeEach(func() {
fakeClient.ListReturns(errors.New("failing on purpose"))
reconcileResult, reconcileErr = cfBuildReconciler.Reconcile(ctx, req)
})

It("should return an error", func() {
Expect(reconcileErr).To(HaveOccurred())
})
})

When("create Kpack Image returns an error", func() {
BeforeEach(func() {
fakeClient.CreateReturns(errors.New("failing on purpose"))
Expand Down
Loading

0 comments on commit d84001c

Please sign in to comment.