Skip to content

Commit

Permalink
Split the different env builders into separate files
Browse files Browse the repository at this point in the history
Co-authored-by: Kieron Browne <kbrowne@vmware.com>
  • Loading branch information
georgethebeatle and Kieron Browne committed Feb 15, 2023
1 parent d4564af commit 93aa43f
Show file tree
Hide file tree
Showing 12 changed files with 631 additions and 551 deletions.
2 changes: 1 addition & 1 deletion api/repositories/app_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ func getSystemEnv(ctx context.Context, userClient client.Client, app AppRecord)
}

if vcapServicesData, ok := vcapServiceSecret.Data["VCAP_SERVICES"]; ok {
vcapServicesPresenter := new(env.VcapServicesPresenter)
vcapServicesPresenter := new(env.VCAPServices)
if err = json.Unmarshal(vcapServicesData, &vcapServicesPresenter); err != nil {
return map[string]any{}, fmt.Errorf("error unmarshalling VCAP Service Secret %q for App %q: %w",
app.vcapServiceSecretName,
Expand Down
6 changes: 3 additions & 3 deletions api/repositories/app_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1359,7 +1359,7 @@ var _ = Describe("AppRepository", func() {
var (
vcapServiceSecretDataByte map[string][]byte
vcapServiceSecretData map[string]string
vcapServiceDataPresenter *env.VcapServicesPresenter
vcapServiceDataPresenter *env.VCAPServices
err error
)

Expand All @@ -1368,7 +1368,7 @@ var _ = Describe("AppRepository", func() {
vcapServiceSecretDataByte, err = generateVcapServiceSecretDataByte()
Expect(err).NotTo(HaveOccurred())
vcapServiceSecretData = asMapOfStrings(vcapServiceSecretDataByte)
vcapServiceDataPresenter = new(env.VcapServicesPresenter)
vcapServiceDataPresenter = new(env.VCAPServices)
err = json.Unmarshal(vcapServiceSecretDataByte["VCAP_SERVICES"], vcapServiceDataPresenter)
Expect(err).NotTo(HaveOccurred())

Expand Down Expand Up @@ -1592,7 +1592,7 @@ func generateVcapServiceSecretDataByte() (map[string][]byte, error) {
VolumeMounts: nil,
}

vcapServicesData, err := json.Marshal(env.VcapServicesPresenter{
vcapServicesData, err := json.Marshal(env.VCAPServices{
UserProvided: []env.ServiceDetails{
serviceDetails,
},
Expand Down
99 changes: 35 additions & 64 deletions controllers/controllers/workloads/cfapp_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,26 @@ const (
cfAppFinalizerName = "cfApp.korifi.cloudfoundry.org"
)

type VCAPEnvBuilder interface {
BuildVCAPServicesEnvValue(context.Context, *korifiv1alpha1.CFApp) (string, error)
BuildVCAPApplicationEnvValue(context.Context, *korifiv1alpha1.CFApp) (string, error)
type EnvValueBuilder interface {
BuildEnvValue(context.Context, *korifiv1alpha1.CFApp) (map[string]string, error)
}

// CFAppReconciler reconciles a CFApp object
type CFAppReconciler struct {
log logr.Logger
k8sClient client.Client
scheme *runtime.Scheme
vcapEnvBuilder VCAPEnvBuilder
log logr.Logger
k8sClient client.Client
scheme *runtime.Scheme
vcapServicesEnvBuilder EnvValueBuilder
vcapApplicationEnvBuilder EnvValueBuilder
}

func NewCFAppReconciler(k8sClient client.Client, scheme *runtime.Scheme, log logr.Logger, vcapServicesBuilder VCAPEnvBuilder) *k8s.PatchingReconciler[korifiv1alpha1.CFApp, *korifiv1alpha1.CFApp] {
func NewCFAppReconciler(k8sClient client.Client, scheme *runtime.Scheme, log logr.Logger, vcapServicesBuilder, vcapApplicationBuilder EnvValueBuilder) *k8s.PatchingReconciler[korifiv1alpha1.CFApp, *korifiv1alpha1.CFApp] {
appReconciler := CFAppReconciler{
log: log,
k8sClient: k8sClient,
scheme: scheme,
vcapEnvBuilder: vcapServicesBuilder,
log: log,
k8sClient: k8sClient,
scheme: scheme,
vcapServicesEnvBuilder: vcapServicesBuilder,
vcapApplicationEnvBuilder: vcapApplicationBuilder,
}
return k8s.NewPatchingReconciler[korifiv1alpha1.CFApp, *korifiv1alpha1.CFApp](log, k8sClient, &appReconciler)
}
Expand All @@ -72,17 +73,21 @@ func (r *CFAppReconciler) ReconcileResource(ctx context.Context, cfApp *korifiv1
return ctrl.Result{}, err
}

err = r.reconcileVCAPApplicationSecret(ctx, log, cfApp)
secretName := cfApp.Name + "-vcap-application"
err = r.reconcileVCAPSecret(ctx, log, cfApp, secretName, r.vcapApplicationEnvBuilder)
if err != nil {
log.Error(err, "unable to create CFApp VCAP Application secret")
return ctrl.Result{}, err
}
cfApp.Status.VCAPApplicationSecretName = secretName

err = r.reconcileVCAPServicesSecret(ctx, log, cfApp)
secretName = cfApp.Name + "-vcap-services"
err = r.reconcileVCAPSecret(ctx, log, cfApp, secretName, r.vcapServicesEnvBuilder)
if err != nil {
log.Error(err, "unable to create CFApp VCAP Services secret")
return ctrl.Result{}, err
}
cfApp.Status.VCAPServicesSecretName = secretName

if cfApp.Status.Conditions == nil {
cfApp.Status.Conditions = make([]metav1.Condition, 0)
Expand Down Expand Up @@ -382,71 +387,37 @@ func serviceBindingToApp(o client.Object) []reconcile.Request {
return result
}

func (r *CFAppReconciler) reconcileVCAPApplicationSecret(ctx context.Context, log logr.Logger, cfApp *korifiv1alpha1.CFApp) error {
vcapApplicationSecretName := cfApp.Name + "-vcap-application"
func (r *CFAppReconciler) reconcileVCAPSecret(
ctx context.Context,
log logr.Logger,
cfApp *korifiv1alpha1.CFApp,
secretName string,
envBuilder EnvValueBuilder,
) error {
log = log.WithName("reconcileVCAPSecret").WithValues("secretName", secretName)

log = log.WithName("reconcileVCAPApplicationSecret").WithValues("vcapApplicationSecretName", vcapApplicationSecretName)

vcapApplicationSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: vcapApplicationSecretName,
Namespace: cfApp.Namespace,
},
}

vcapApplicationValue, err := r.vcapEnvBuilder.BuildVCAPApplicationEnvValue(ctx, cfApp)
if err != nil {
log.Error(err, "failed to build 'VCAP_APPLICATION' value")
return err
}
_, err = controllerutil.CreateOrPatch(ctx, r.k8sClient, vcapApplicationSecret, func() error {
vcapApplicationSecret.StringData = map[string]string{
"VCAP_APPLICATION": vcapApplicationValue,
}

return controllerutil.SetOwnerReference(cfApp, vcapApplicationSecret, r.scheme)
})
if err != nil {
log.Error(err, "unable to create or patch 'VCAP_APPLICATION' Secret")
return err
}

cfApp.Status.VCAPApplicationSecretName = vcapApplicationSecretName

return nil
}

func (r *CFAppReconciler) reconcileVCAPServicesSecret(ctx context.Context, log logr.Logger, cfApp *korifiv1alpha1.CFApp) error {
vcapServicesSecretName := cfApp.Name + "-vcap-services"

log = log.WithName("reconcileVCAPServicesSecret").WithValues("vcapServicesSecretName", vcapServicesSecretName)

vcapServicesSecret := &corev1.Secret{
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: vcapServicesSecretName,
Name: secretName,
Namespace: cfApp.Namespace,
},
}

vcapServicesValue, err := r.vcapEnvBuilder.BuildVCAPServicesEnvValue(ctx, cfApp)
envValue, err := envBuilder.BuildEnvValue(ctx, cfApp)
if err != nil {
log.Error(err, "failed to build 'VCAP_SERVICES' value")
log.Error(err, "failed to build env value")
return err
}

_, err = controllerutil.CreateOrPatch(ctx, r.k8sClient, vcapServicesSecret, func() error {
vcapServicesSecret.StringData = map[string]string{
"VCAP_SERVICES": vcapServicesValue,
}
_, err = controllerutil.CreateOrPatch(ctx, r.k8sClient, secret, func() error {
secret.StringData = envValue

return controllerutil.SetOwnerReference(cfApp, vcapServicesSecret, r.scheme)
return controllerutil.SetOwnerReference(cfApp, secret, r.scheme)
})
if err != nil {
log.Error(err, "unable to create or patch 'VCAP_SERVICES' Secret")
log.Error(err, "unable to create or patch Secret")
return err
}

cfApp.Status.VCAPServicesSecretName = vcapServicesSecretName

return nil
}
170 changes: 5 additions & 165 deletions controllers/controllers/workloads/env/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ package env

import (
"context"
"encoding/json"
"fmt"

korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
"code.cloudfoundry.org/korifi/controllers/controllers/shared"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type VcapServicesPresenter struct {
type VCAPServices struct {
UserProvided []ServiceDetails `json:"user-provided,omitempty"`
}

Expand All @@ -30,15 +28,15 @@ type ServiceDetails struct {
VolumeMounts []string `json:"volume_mounts"`
}

type Builder struct {
type WorkloadEnvBuilder struct {
k8sClient client.Client
}

func NewBuilder(k8sClient client.Client) *Builder {
return &Builder{k8sClient: k8sClient}
func NewWorkloadEnvBuilder(k8sClient client.Client) *WorkloadEnvBuilder {
return &WorkloadEnvBuilder{k8sClient: k8sClient}
}

func (b *Builder) BuildEnv(ctx context.Context, cfApp *korifiv1alpha1.CFApp) ([]corev1.EnvVar, error) {
func (b *WorkloadEnvBuilder) BuildEnv(ctx context.Context, cfApp *korifiv1alpha1.CFApp) ([]corev1.EnvVar, error) {
var appEnvSecret, vcapServicesSecret, vcapApplicationSecret corev1.Secret

if cfApp.Spec.EnvSecretName != "" {
Expand Down Expand Up @@ -66,109 +64,6 @@ func (b *Builder) BuildEnv(ctx context.Context, cfApp *korifiv1alpha1.CFApp) ([]
return envVarsFromSecrets(appEnvSecret, vcapServicesSecret, vcapApplicationSecret), nil
}

func (b *Builder) getSpaceFromNamespace(ctx context.Context, ns string) (korifiv1alpha1.CFSpace, error) {
spaces := korifiv1alpha1.CFSpaceList{}
if err := b.k8sClient.List(ctx, &spaces, client.MatchingFields{
shared.IndexSpaceNamespaceName: ns,
}); err != nil {
return korifiv1alpha1.CFSpace{}, fmt.Errorf("error listing cfSpaces: %w", err)
}

if len(spaces.Items) != 1 {
return korifiv1alpha1.CFSpace{}, fmt.Errorf("expected a unique CFSpace for namespace %q, got %d", ns, len(spaces.Items))
}

return spaces.Items[0], nil
}

func (b *Builder) getOrgFromNamespace(ctx context.Context, ns string) (korifiv1alpha1.CFOrg, error) {
orgs := korifiv1alpha1.CFOrgList{}
if err := b.k8sClient.List(ctx, &orgs, client.MatchingFields{
shared.IndexOrgNamespaceName: ns,
}); err != nil {
return korifiv1alpha1.CFOrg{}, fmt.Errorf("error listing cfOrgs: %w", err)
}

if len(orgs.Items) != 1 {
return korifiv1alpha1.CFOrg{}, fmt.Errorf("expected a unique CFOrg for namespace %q, got %d", ns, len(orgs.Items))
}

return orgs.Items[0], nil
}

func (b *Builder) BuildVCAPApplicationEnvValue(ctx context.Context, cfApp *korifiv1alpha1.CFApp) (string, error) {
space, err := b.getSpaceFromNamespace(ctx, cfApp.Namespace)
if err != nil {
return "", fmt.Errorf("failed retrieving space for CFApp: %w", err)
}
org, err := b.getOrgFromNamespace(ctx, space.Namespace)
if err != nil {
return "", fmt.Errorf("failed retrieving org for CFSpace: %w", err)
}

vars := map[string]string{
"application_id": cfApp.Name,
"application_name": cfApp.Spec.DisplayName,
"cf_api": "",
"name": cfApp.Spec.DisplayName,
"organization_id": org.Name,
"organization_name": org.Spec.DisplayName,
"space_id": space.Name,
"space_name": space.Spec.DisplayName,
}

out, _ := json.Marshal(vars)
return string(out), nil
}

func (b *Builder) BuildVCAPServicesEnvValue(ctx context.Context, cfApp *korifiv1alpha1.CFApp) (string, error) {
serviceBindings := &korifiv1alpha1.CFServiceBindingList{}
err := b.k8sClient.List(ctx, serviceBindings,
client.InNamespace(cfApp.Namespace),
client.MatchingFields{shared.IndexServiceBindingAppGUID: cfApp.Name},
)
if err != nil {
return "", fmt.Errorf("error listing CFServiceBindings: %w", err)
}

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

serviceEnvs := []ServiceDetails{}
for _, currentServiceBinding := range serviceBindings.Items {
// If finalizing do not append
if !currentServiceBinding.DeletionTimestamp.IsZero() {
continue
}

var serviceEnv ServiceDetails
serviceEnv, err = buildSingleServiceEnv(ctx, b.k8sClient, currentServiceBinding)
if err != nil {
return "", err
}

serviceEnvs = append(serviceEnvs, serviceEnv)
}

toReturn, err := json.Marshal(VcapServicesPresenter{
UserProvided: serviceEnvs,
})
if err != nil {
return "", err
}

return string(toReturn), nil
}

func mapFromSecret(secret corev1.Secret) map[string]string {
convertedMap := make(map[string]string)
for k, v := range secret.Data {
convertedMap[k] = string(v)
}
return convertedMap
}

func envVarsFromSecrets(secrets ...corev1.Secret) []corev1.EnvVar {
var envVars []corev1.EnvVar
for _, secret := range secrets {
Expand All @@ -186,58 +81,3 @@ func envVarsFromSecrets(secrets ...corev1.Secret) []corev1.EnvVar {
}
return envVars
}

func fromServiceBinding(
serviceBinding korifiv1alpha1.CFServiceBinding,
serviceInstance korifiv1alpha1.CFServiceInstance,
serviceBindingSecret corev1.Secret,
) ServiceDetails {
var serviceName string
var bindingName *string

if serviceBinding.Spec.DisplayName != nil {
serviceName = *serviceBinding.Spec.DisplayName
bindingName = serviceBinding.Spec.DisplayName
} else {
serviceName = serviceInstance.Spec.DisplayName
bindingName = nil
}

tags := serviceInstance.Spec.Tags
if tags == nil {
tags = []string{}
}

return ServiceDetails{
Label: "user-provided",
Name: serviceName,
Tags: tags,
InstanceGUID: serviceInstance.Name,
InstanceName: serviceInstance.Spec.DisplayName,
BindingGUID: serviceBinding.Name,
BindingName: bindingName,
Credentials: mapFromSecret(serviceBindingSecret),
SyslogDrainURL: nil,
VolumeMounts: []string{},
}
}

func buildSingleServiceEnv(ctx context.Context, k8sClient client.Client, serviceBinding korifiv1alpha1.CFServiceBinding) (ServiceDetails, error) {
if serviceBinding.Status.Binding.Name == "" {
return ServiceDetails{}, fmt.Errorf("service binding secret name is empty")
}

serviceInstance := korifiv1alpha1.CFServiceInstance{}
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: serviceBinding.Namespace, Name: serviceBinding.Spec.Service.Name}, &serviceInstance)
if err != nil {
return ServiceDetails{}, fmt.Errorf("error fetching CFServiceInstance: %w", err)
}

secret := corev1.Secret{}
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: serviceBinding.Namespace, Name: serviceBinding.Status.Binding.Name}, &secret)
if err != nil {
return ServiceDetails{}, fmt.Errorf("error fetching CFServiceBinding Secret: %w", err)
}

return fromServiceBinding(serviceBinding, serviceInstance, secret), nil
}
Loading

0 comments on commit 93aa43f

Please sign in to comment.