From 7244050200c827067325a3eeb6a0d8d4a95c5cb8 Mon Sep 17 00:00:00 2001 From: Simon Beck Date: Tue, 6 Feb 2024 14:22:16 +0100 Subject: [PATCH 1/3] Fix nested SLI Prober --- .../vshnpostgresql_controller.go | 72 +++++++++++++------ .../vshnpostgresql_controller_test.go | 7 +- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/pkg/sliexporter/vshnpostgresql_controller/vshnpostgresql_controller.go b/pkg/sliexporter/vshnpostgresql_controller/vshnpostgresql_controller.go index cfe72e7eb..a90f845df 100644 --- a/pkg/sliexporter/vshnpostgresql_controller/vshnpostgresql_controller.go +++ b/pkg/sliexporter/vshnpostgresql_controller/vshnpostgresql_controller.go @@ -40,6 +40,7 @@ var ( vshnpostgresqlsServiceKey = "VSHNPostgreSQL" claimNamespaceLabel = "crossplane.io/claim-namespace" claimNameLabel = "crossplane.io/claim-name" + errNotReady = fmt.Errorf("Resource is not yet ready") ) // VSHNPostgreSQLReconciler reconciles a VSHNPostgreSQL object @@ -89,26 +90,30 @@ func (r *VSHNPostgreSQLReconciler) Reconcile(ctx context.Context, req ctrl.Reque return ctrl.Result{}, nil } - probe, err := r.fetchProberFor(ctx, inst) - if err != nil && !apierrors.IsNotFound(err) { - return ctrl.Result{}, err + if time.Since(inst.GetCreationTimestamp().Time) < r.StartupGracePeriod { + retry := r.StartupGracePeriod - time.Since(inst.GetCreationTimestamp().Time) + l.Info(fmt.Sprintf("Instance is starting up. Postpone probing until ready, retry in %s", retry.String())) + res.Requeue = true + res.RequeueAfter = retry + return res, nil } - if apierrors.IsNotFound(err) { + + probe, err := r.fetchProberFor(ctx, inst) + // By using the composite the credential secret is available instantly, but initially empty. + // TODO: we might want to rethink and generalize this whole reconciler logic in the future. + if err != nil && (apierrors.IsNotFound(err) || err == errNotReady) { l.WithValues("credentials", inst.Spec.WriteConnectionSecretToReference.Name, "error", err.Error()). Info("Failed to find credentials. Backing off") res.Requeue = true res.RequeueAfter = 30 * time.Second - if time.Since(inst.GetCreationTimestamp().Time) < r.StartupGracePeriod { - // Instance is starting up. Postpone probing until ready. - return res, nil - } - // Create a pobe that will always fail probe, err = probes.NewFailingProbe(vshnpostgresqlsServiceKey, inst.Name, inst.ObjectMeta.Labels[claimNamespaceLabel], err) if err != nil { return ctrl.Result{}, err } + } else if err != nil { + return ctrl.Result{}, err } l.Info("Starting Probe") @@ -117,24 +122,19 @@ func (r *VSHNPostgreSQLReconciler) Reconcile(ctx context.Context, req ctrl.Reque } func (r VSHNPostgreSQLReconciler) fetchProberFor(ctx context.Context, inst *vshnv1.XVSHNPostgreSQL) (probes.Prober, error) { - instance := &vshnv1.VSHNPostgreSQL{} + credSecret := corev1.Secret{} err := r.Get(ctx, types.NamespacedName{ - Name: inst.ObjectMeta.Labels[claimNameLabel], - Namespace: inst.ObjectMeta.Labels[claimNamespaceLabel], - }, instance) + Name: inst.Spec.WriteConnectionSecretToReference.Name, + Namespace: inst.Spec.WriteConnectionSecretToReference.Namespace, + }, &credSecret) if err != nil { return nil, err } - credSecret := corev1.Secret{} - err = r.Get(ctx, types.NamespacedName{ - Name: instance.Spec.WriteConnectionSecretToReference.Name, - Namespace: inst.ObjectMeta.Labels[claimNamespaceLabel], - }, &credSecret) - - if err != nil { - return nil, err + ready := r.areCredentialsAvailable(&credSecret) + if !ready { + return nil, errNotReady } ns := &corev1.Namespace{} @@ -176,3 +176,33 @@ func (r *VSHNPostgreSQLReconciler) SetupWithManager(mgr ctrl.Manager) error { For(&vshnv1.XVSHNPostgreSQL{}). Complete(r) } + +func (r *VSHNPostgreSQLReconciler) areCredentialsAvailable(secret *corev1.Secret) bool { + + _, ok := secret.Data["POSTGRESQL_USER"] + if !ok { + return false + } + _, ok = secret.Data["POSTGRESQL_PASSWORD"] + if !ok { + return false + } + _, ok = secret.Data["POSTGRESQL_HOST"] + if !ok { + return false + } + _, ok = secret.Data["POSTGRESQL_PORT"] + if !ok { + return false + } + _, ok = secret.Data["POSTGRESQL_DB"] + if !ok { + return false + } + _, ok = secret.Data["ca.crt"] + if !ok { + return false + } + + return ok +} diff --git a/pkg/sliexporter/vshnpostgresql_controller/vshnpostgresql_controller_test.go b/pkg/sliexporter/vshnpostgresql_controller/vshnpostgresql_controller_test.go index 93a5ad98a..4fa9a7a47 100644 --- a/pkg/sliexporter/vshnpostgresql_controller/vshnpostgresql_controller_test.go +++ b/pkg/sliexporter/vshnpostgresql_controller/vshnpostgresql_controller_test.go @@ -130,7 +130,8 @@ func TestVSHNPostgreSQL_Multi(t *testing.T) { func TestVSHNPostgreSQL_Startup_NoCreds_Dont_Probe(t *testing.T) { db := newTestVSHNPostgres("bar", "foo", "creds", 1) - db.SetCreationTimestamp(metav1.Now()) + cd := metav1.Now().Add(time.Minute * -6) + db.SetCreationTimestamp(metav1.Time{Time: cd}) r, manager, _ := setupVSHNPostgreTest(t, db, ) @@ -144,7 +145,9 @@ func TestVSHNPostgreSQL_Startup_NoCreds_Dont_Probe(t *testing.T) { assert.NoError(t, err) assert.Greater(t, res.RequeueAfter.Microseconds(), int64(0)) - assert.False(t, manager.probers[getFakeKey(pi)]) + // we now get a failing probe as the composite will always have + // connection details + assert.True(t, manager.probers[getFakeKey(pi)]) } func TestVSHNPostgreSQL_NoRef_Dont_Probe(t *testing.T) { From 4b3220a5048c84538176c21c5348fae5a3a04a9a Mon Sep 17 00:00:00 2001 From: Simon Beck Date: Wed, 7 Feb 2024 11:09:41 +0100 Subject: [PATCH 2/3] Generate CRDs --- crds/stackgres.io_sgpoolingconfigs.yaml | 4 ++-- crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml | 6 +++--- crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crds/stackgres.io_sgpoolingconfigs.yaml b/crds/stackgres.io_sgpoolingconfigs.yaml index 50a4c018b..e4ec75ef0 100644 --- a/crds/stackgres.io_sgpoolingconfigs.yaml +++ b/crds/stackgres.io_sgpoolingconfigs.yaml @@ -32,7 +32,7 @@ spec: metadata: type: object spec: - description: Spec defines the desired state of a VSHNPostgreSQL. + description: Spec contains the custom configurations for the pgbouncer. properties: pgBouncer: description: Connection pooling configuration based on PgBouncer. @@ -72,7 +72,7 @@ spec: type: object type: object status: - description: Status reflects the observed state of a VSHNPostgreSQL. + description: Status contains the default settings for the pgbouncer. properties: pgBouncer: description: Connection pooling configuration status based on PgBouncer. diff --git a/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml b/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml index 535977b98..1041f3c76 100644 --- a/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml +++ b/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml @@ -3707,10 +3707,10 @@ spec: - guaranteed type: string version: - default: "23" - description: Version contains supported version of keycloak. Multiple versions are supported. The latest version 22 is the default version. + default: 23.0.5-202402021353-44-af6cea11 + description: Version contains supported version of keycloak. Multiple versions are supported. The latest version 23 is the default version. enum: - - "23" + - 23.0.5-202402021353-44-af6cea11 type: string type: object default: {} diff --git a/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml b/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml index d19f842f5..cfbde02b4 100644 --- a/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml +++ b/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml @@ -5933,12 +5933,12 @@ spec: - guaranteed type: string version: - default: "23" + default: 23.0.5-202402021353-44-af6cea11 description: Version contains supported version of keycloak. - Multiple versions are supported. The latest version 22 is + Multiple versions are supported. The latest version 23 is the default version. enum: - - "23" + - 23.0.5-202402021353-44-af6cea11 type: string type: object size: From 4dee49aa462810e2234ea0ca8e1c61d7cf2784a4 Mon Sep 17 00:00:00 2001 From: Nicolas Bigler Date: Fri, 2 Feb 2024 16:37:12 +0100 Subject: [PATCH 3/3] Add support for custom themes and providers Signed-off-by: Nicolas Bigler --- apis/vshn/v1/dbaas_vshn_keycloak.go | 14 +++ apis/vshn/v1/zz_generated.deepcopy.go | 17 +++ crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml | 18 +++ crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml | 24 ++++ .../functions/vshnkeycloak/deploy.go | 118 ++++++++++++++++-- 5 files changed, 181 insertions(+), 10 deletions(-) diff --git a/apis/vshn/v1/dbaas_vshn_keycloak.go b/apis/vshn/v1/dbaas_vshn_keycloak.go index c3bf694ce..5b71e1263 100644 --- a/apis/vshn/v1/dbaas_vshn_keycloak.go +++ b/apis/vshn/v1/dbaas_vshn_keycloak.go @@ -5,6 +5,7 @@ import ( xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" v1 "github.com/vshn/appcat/v4/apis/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -100,6 +101,19 @@ type VSHNKeycloakServiceSpec struct { // PostgreSQLParameters can be used to set any supported setting in the // underlying PostgreSQL instance. PostgreSQLParameters *VSHNPostgreSQLParameters `json:"postgreSQLParameters,omitempty"` + + // CustomizationImage can be used to provide an image with custom themes and providers. + // The themes need to be be placed in the `/themes` directory of the custom image. + // the providers need to be placed in the `/providers` directory of the custom image. + CustomizationImage VSHNKeycloakCustomizationImage `json:"customizationImage,omitempty"` +} + +type VSHNKeycloakCustomizationImage struct { + // Path to a valid image + Image string `json:"image,omitempty"` + + // Reference to an imagePullSecret + ImagePullSecretRef corev1.SecretReference `json:"imagePullSecretRef,omitempty"` } // VSHNKeycloakSettings contains Keycloak specific settings. diff --git a/apis/vshn/v1/zz_generated.deepcopy.go b/apis/vshn/v1/zz_generated.deepcopy.go index 45e89c54a..b742ce4cf 100644 --- a/apis/vshn/v1/zz_generated.deepcopy.go +++ b/apis/vshn/v1/zz_generated.deepcopy.go @@ -171,6 +171,22 @@ func (in *VSHNKeycloak) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VSHNKeycloakCustomizationImage) DeepCopyInto(out *VSHNKeycloakCustomizationImage) { + *out = *in + out.ImagePullSecretRef = in.ImagePullSecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNKeycloakCustomizationImage. +func (in *VSHNKeycloakCustomizationImage) DeepCopy() *VSHNKeycloakCustomizationImage { + if in == nil { + return nil + } + out := new(VSHNKeycloakCustomizationImage) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VSHNKeycloakList) DeepCopyInto(out *VSHNKeycloakList) { *out = *in @@ -233,6 +249,7 @@ func (in *VSHNKeycloakServiceSpec) DeepCopyInto(out *VSHNKeycloakServiceSpec) { *out = new(VSHNPostgreSQLParameters) (*in).DeepCopyInto(*out) } + out.CustomizationImage = in.CustomizationImage } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VSHNKeycloakServiceSpec. diff --git a/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml b/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml index 1041f3c76..b137507ed 100644 --- a/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml +++ b/crds/vshn.appcat.vshn.io_vshnkeycloaks.yaml @@ -99,6 +99,24 @@ spec: service: description: Service contains keycloak DBaaS specific properties properties: + customizationImage: + description: CustomizationImage can be used to provide an image with custom themes and providers. The themes need to be be placed in the `/themes` directory of the custom image. the providers need to be placed in the `/providers` directory of the custom image. + properties: + image: + description: Path to a valid image + type: string + imagePullSecretRef: + description: Reference to an imagePullSecret + properties: + name: + description: name is unique within a namespace to reference a secret resource. + type: string + namespace: + description: namespace defines the space within which the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: object fqdn: description: FQDN contains the FQDN which will be used for the ingress. If it's not set, no ingress will be deployed. This also enables strict hostname checking for this FQDN. type: string diff --git a/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml b/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml index cfbde02b4..747a7c84d 100644 --- a/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml +++ b/crds/vshn.appcat.vshn.io_xvshnkeycloaks.yaml @@ -154,6 +154,30 @@ spec: service: description: Service contains keycloak DBaaS specific properties properties: + customizationImage: + description: CustomizationImage can be used to provide an + image with custom themes and providers. The themes need + to be be placed in the `/themes` directory of the custom + image. the providers need to be placed in the `/providers` + directory of the custom image. + properties: + image: + description: Path to a valid image + type: string + imagePullSecretRef: + description: Reference to an imagePullSecret + properties: + name: + description: name is unique within a namespace to + reference a secret resource. + type: string + namespace: + description: namespace defines the space within which + the secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: object fqdn: description: FQDN contains the FQDN which will be used for the ingress. If it's not set, no ingress will be deployed. diff --git a/pkg/comp-functions/functions/vshnkeycloak/deploy.go b/pkg/comp-functions/functions/vshnkeycloak/deploy.go index e6fc4707a..da5c53b3f 100644 --- a/pkg/comp-functions/functions/vshnkeycloak/deploy.go +++ b/pkg/comp-functions/functions/vshnkeycloak/deploy.go @@ -7,7 +7,9 @@ import ( "fmt" "dario.cat/mergo" + xkubev1 "github.com/crossplane-contrib/provider-kubernetes/apis/object/v1alpha1" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" xhelmv1 "github.com/vshn/appcat/v4/apis/helm/release/v1beta1" sgv1 "github.com/vshn/appcat/v4/apis/stackgres/v1" @@ -20,6 +22,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" "sigs.k8s.io/yaml" ) @@ -36,6 +39,7 @@ const ( registryURL = "docker-registry.inventage.com:10121/keycloak-competence-center/keycloak-managed" providerInitName = "copy-original-providers" realmInitName = "copy-original-realm-setup" + customImagePullsecretName = "customimagepullsecret" ) // DeployKeycloak deploys a keycloak instance via the codecentric Helm Chart. @@ -265,6 +269,10 @@ func newValues(ctx context.Context, svc *runtime.ServiceRuntime, comp *vshnv1.VS "name": "custom-providers", "emptyDir": nil, }, + { + "name": "custom-themes", + "emptyDir": nil, + }, { "name": "custom-setup", "emptyDir": nil, @@ -291,6 +299,10 @@ func newValues(ctx context.Context, svc *runtime.ServiceRuntime, comp *vshnv1.VS "name": "custom-providers", "mountPath": "/opt/keycloak/providers", }, + { + "name": "custom-themes", + "mountPath": "/opt/keycloak/themes", + }, { "name": "custom-setup", "mountPath": "/opt/keycloak/setup", @@ -332,14 +344,6 @@ func newValues(ctx context.Context, svc *runtime.ServiceRuntime, comp *vshnv1.VS "username": string(cd[vshnpostgres.PostgresqlUser]), "password": string(cd[vshnpostgres.PostgresqlPassword]), }, - "serviceAccount": map[string]any{ - "automountServiceAccountToken": "false", - "imagePullSecrets": []map[string]any{ - { - "name": pullsecretName, - }, - }, - }, "resources": map[string]any{ "requests": map[string]any{ "memory": res.ReqMem, @@ -395,7 +399,12 @@ func newRelease(ctx context.Context, svc *runtime.ServiceRuntime, comp *vshnv1.V return nil, fmt.Errorf("cannot set keycloak version for release: %w", err) } - err = addInitContainer(values, observedVersion) + err = addInitContainer(comp, svc, values, observedVersion) + if err != nil { + return nil, err + } + + err = addServiceAccount(comp, svc, values) if err != nil { return nil, err } @@ -412,6 +421,30 @@ func toYAML(obj any) (string, error) { return string(yamlBytes), err } +func copyCustomImagePullSecret(comp *vshnv1.VSHNKeycloak, svc *runtime.ServiceRuntime) error { + secretInstance := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: customImagePullsecretName, + Namespace: comp.GetInstanceNamespace(), + }, + Type: corev1.SecretTypeDockerConfigJson, + } + secretClaimRef := xkubev1.Reference{ + PatchesFrom: &xkubev1.PatchesFrom{ + DependsOn: xkubev1.DependsOn{ + APIVersion: "v1", + Kind: "Secret", + Namespace: comp.Spec.Parameters.Service.CustomizationImage.ImagePullSecretRef.DeepCopy().Namespace, + Name: comp.Spec.Parameters.Service.CustomizationImage.ImagePullSecretRef.Name, + }, + FieldPath: ptr.To("data"), + }, + ToFieldPath: ptr.To("data"), + } + + return svc.SetDesiredKubeObject(secretInstance, comp.GetName()+"-custom-image-pull-secret", runtime.KubeOptionAddRefs(secretClaimRef)) +} + func addPullSecret(comp *vshnv1.VSHNKeycloak, svc *runtime.ServiceRuntime) error { username := svc.Config.Data["registry_username"] @@ -449,7 +482,34 @@ func addPullSecret(comp *vshnv1.VSHNKeycloak, svc *runtime.ServiceRuntime) error return svc.SetDesiredKubeObject(secret, comp.GetName()+"-pull-secret") } -func addInitContainer(values map[string]any, version string) error { +func addServiceAccount(comp *vshnv1.VSHNKeycloak, svc *runtime.ServiceRuntime, values map[string]any) error { + + pullSecrets := []map[string]any{ + { + "name": pullsecretName, + }, + } + + if comp.Spec.Parameters.Service.CustomizationImage.ImagePullSecretRef.Name != "" { + err := copyCustomImagePullSecret(comp, svc) + if err != nil { + return err + } + customImagePullSecret := map[string]any{ + "name": customImagePullsecretName, + } + pullSecrets = append(pullSecrets, customImagePullSecret) + } + + values["serviceAccount"] = map[string]any{ + "automountServiceAccountToken": "false", + "imagePullSecrets": pullSecrets, + } + + return nil +} + +func addInitContainer(comp *vshnv1.VSHNKeycloak, svc *runtime.ServiceRuntime, values map[string]any, version string) error { extraInitContainersMap := []map[string]any{ { "name": providerInitName, @@ -492,6 +552,10 @@ ls -lh /custom-setup`, }, }, } + extraInitContainersMap, err := addCustomInitContainer(comp, svc, extraInitContainersMap) + if err != nil { + return err + } extraInitContainers, err := toYAML(extraInitContainersMap) if err != nil { @@ -502,3 +566,37 @@ ls -lh /custom-setup`, return nil } + +func addCustomInitContainer(comp *vshnv1.VSHNKeycloak, svc *runtime.ServiceRuntime, extraInitContainersMap []map[string]any) ([]map[string]any, error) { + if comp.Spec.Parameters.Service.CustomizationImage.Image != "" { + + extraInitContainersThemeProvidersMap := map[string]any{ + "name": "copy-custom-themes-providers", + "image": comp.Spec.Parameters.Service.CustomizationImage.Image, + "imagePullPolicy": "Always", + "command": []string{ + "sh", + }, + "args": []string{ + "-c", + `echo "Copying custom themes..." +cp -Rv /themes/* /custom-themes +echo "Copying custom providers..." +cp -Rv /providers/* /custom-providers +exit 0`, + }, + "volumeMounts": []map[string]any{ + { + "name": "custom-providers", + "mountPath": "/custom-providers", + }, + { + "name": "custom-themes", + "mountPath": "/custom-themes", + }, + }, + } + extraInitContainersMap = append(extraInitContainersMap, extraInitContainersThemeProvidersMap) + } + return extraInitContainersMap, nil +}