Repository: vshn/appcat
Pull request #352: Fix Nextcloud default PG settings
Author: login: mdnix
Labels: name: minor
Created: 2025-05-07T13:48:12Z | Merged: 2025-05-12T12:35:18Z

## Summary

We discovered that the disk for the embedded PostgreSQL will be set to the same size as the Nextcloud disk.
This PR improves the default configuration and reconciliation behavior for Nextcloud's PostgreSQL dependency.

* Default disk size for the PostgreSQL dependency is set to `10GiB` if not specified by the user.  
* Existing instances will not be downsized if they already use more than `10GiB`.  
* If the current disk size is below `10GiB` and no user-defined value is set, it will be updated to the new default.  
* Adds and enforces `max_connections: "1000"` in PostgreSQL settings.  
* Adds and enforces `max_client_conn: "1000"` in pgBouncer settings.  
* These settings will be reconciled on existing instances unless explicitly overridden by the user.

## Checklist

- [ ] Categorize the PR by setting a good title and adding one of the labels:
      `bug`, `enhancement`, `documentation`, `change`, `breaking`, `dependency`
      as they show up in the changelog
- [ ] Update tests.
- [ ] Link this PR to related issues.

<!--
Remove items that do not apply. For completed items, change [ ] to [x].

NOTE: these things are not required to open a PR and can be done afterwards,
while the PR is open.
-->



Diff:
diff --git a/pkg/comp-functions/functions/common/postgresql.go b/pkg/comp-functions/functions/common/postgresql.go
index ef6c5ea845..01e17f0dfe 100644
--- a/pkg/comp-functions/functions/common/postgresql.go
+++ b/pkg/comp-functions/functions/common/postgresql.go
@@ -23,6 +23,7 @@ type PostgreSQLDependencyBuilder struct {
 	comp                 InfoGetter
 	psqlParams           *vshnv1.VSHNPostgreSQLParameters
 	pgBouncerConfig      map[string]string
+	pgSettings           map[string]string
 	timeOfDayMaintenance vshnv1.TimeOfDay
 }
 
@@ -33,6 +34,14 @@ func NewPostgreSQLDependencyBuilder(svc *runtime.ServiceRuntime, comp InfoGetter
 	}
 }
 
+func (a *PostgreSQLDependencyBuilder) SetDiskSize(size string) *PostgreSQLDependencyBuilder {
+	if a.psqlParams == nil {
+		a.psqlParams = &vshnv1.VSHNPostgreSQLParameters{}
+	}
+	a.psqlParams.Size.Disk = size
+	return a
+}
+
 func (a *PostgreSQLDependencyBuilder) AddParameters(psqlParams *vshnv1.VSHNPostgreSQLParameters) *PostgreSQLDependencyBuilder {
 	a.psqlParams = psqlParams
 	return a
@@ -43,6 +52,11 @@ func (a *PostgreSQLDependencyBuilder) AddPGBouncerConfig(pgBouncerConfig map[str
 	return a
 }
 
+func (a *PostgreSQLDependencyBuilder) AddPGSettings(pgSettings map[string]string) *PostgreSQLDependencyBuilder {
+	a.pgSettings = pgSettings
+	return a
+}
+
 func (a *PostgreSQLDependencyBuilder) SetCustomMaintenanceSchedule(timeOfDayMaintenance vshnv1.TimeOfDay) *PostgreSQLDependencyBuilder {
 	a.timeOfDayMaintenance = timeOfDayMaintenance
 	return a
@@ -60,15 +74,25 @@ func (a *PostgreSQLDependencyBuilder) CreateDependency() (string, error) {
 
 	pgBouncerRaw := k8sruntime.RawExtension{}
 	if a.pgBouncerConfig != nil {
-		if a.pgBouncerConfig != nil {
-
-			pgBouncerConfigBytes, err := json.Marshal(a.pgBouncerConfig)
-			if err != nil {
-				return "", err
-			}
-			pgBouncerRaw = k8sruntime.RawExtension{
-				Raw: pgBouncerConfigBytes,
-			}
+
+		pgBouncerConfigBytes, err := json.Marshal(a.pgBouncerConfig)
+		if err != nil {
+			return "", err
+		}
+		pgBouncerRaw = k8sruntime.RawExtension{
+			Raw: pgBouncerConfigBytes,
+		}
+	}
+
+	pgSettingsRaw := k8sruntime.RawExtension{}
+	if a.pgSettings != nil {
+
+		pgSettingsBytes, err := json.Marshal(a.pgSettings)
+		if err != nil {
+			return "", err
+		}
+		pgSettingsRaw = k8sruntime.RawExtension{
+			Raw: pgSettingsBytes,
 		}
 	}
 
@@ -85,6 +109,7 @@ func (a *PostgreSQLDependencyBuilder) CreateDependency() (string, error) {
 			PgBouncerSettings: &sgv1.SGPoolingConfigSpecPgBouncerPgbouncerIni{
 				Pgbouncer: pgBouncerRaw,
 			},
+			PostgreSQLSettings: pgSettingsRaw,
 		},
 		Monitoring: a.comp.GetMonitoring(),
 	}
diff --git a/pkg/comp-functions/functions/vshnnextcloud/deploy.go b/pkg/comp-functions/functions/vshnnextcloud/deploy.go
index 52cefa158f..6c0451d492 100644
--- a/pkg/comp-functions/functions/vshnnextcloud/deploy.go
+++ b/pkg/comp-functions/functions/vshnnextcloud/deploy.go
@@ -3,12 +3,14 @@ package vshnnextcloud
 import (
 	"context"
 	_ "embed"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"strconv"
 	"strings"
 	"time"
 
+	"dario.cat/mergo"
 	xfnproto "github.com/crossplane/function-sdk-go/proto/v1"
 	xhelmv1 "github.com/vshn/appcat/v4/apis/helm/release/v1beta1"
 	vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1"
@@ -18,7 +20,7 @@ import (
 	"github.com/vshn/appcat/v4/pkg/comp-functions/functions/vshnpostgres"
 	"github.com/vshn/appcat/v4/pkg/comp-functions/runtime"
 	corev1 "k8s.io/api/core/v1"
-	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
@@ -67,13 +69,26 @@ func DeployNextcloud(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runti
 	var pgTime vshnv1.TimeOfDay
 	pgTime.SetTime(comp.GetMaintenanceTimeOfDay().GetTime().Add(20 * time.Minute))
 
+	pgBouncerConfig, pgSettings, pgDiskSize, err := getObservedPostgresSettings(svc, comp)
+	if err != nil {
+		return runtime.NewWarningResult(fmt.Sprintf("cannot get observed postgres settings: %s", err))
+	}
+
 	pgSecret := ""
 	if comp.Spec.Parameters.Service.UseExternalPostgreSQL {
 		svc.Log.Info("Adding postgresql instance")
-		pgSecret, err = common.NewPostgreSQLDependencyBuilder(svc, comp).
+
+		pgBuilder := common.NewPostgreSQLDependencyBuilder(svc, comp).
 			AddParameters(comp.Spec.Parameters.Service.PostgreSQLParameters).
-			SetCustomMaintenanceSchedule(pgTime).
-			CreateDependency()
+			AddPGBouncerConfig(pgBouncerConfig).
+			AddPGSettings(pgSettings).
+			SetCustomMaintenanceSchedule(pgTime)
+
+		if pgDiskSize != "" {
+			pgBuilder.SetDiskSize(pgDiskSize)
+		}
+
+		pgSecret, err = pgBuilder.CreateDependency()
 		if err != nil {
 			return runtime.NewWarningResult(fmt.Sprintf("cannot create postgresql instance: %s", err))
 		}
@@ -143,6 +158,122 @@ func DeployNextcloud(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runti
 	return nil
 }
 
+func getObservedPostgresSettings(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNNextcloud) (map[string]string, map[string]string, string, error) {
+	pgBouncerConfig := map[string]string{
+		"max_client_conn": "1000",
+	}
+	pgSettings := map[string]string{
+		"max_connections": "1000",
+	}
+	pgDiskSize := ""
+
+	if !comp.Spec.Parameters.Service.UseExternalPostgreSQL {
+		return pgBouncerConfig, pgSettings, pgDiskSize, nil
+	}
+
+	existing := &vshnv1.XVSHNPostgreSQL{}
+	err := svc.GetObservedComposedResource(existing, comp.GetName()+pgInstanceNameSuffix)
+
+	switch {
+	case err == nil:
+		if existing.Spec.Parameters.Size.Disk != "" {
+			pgDiskSize = existing.Spec.Parameters.Size.Disk
+		}
+
+		if existing.Spec.Parameters.Service.PgBouncerSettings != nil {
+			if bouncer := existing.Spec.Parameters.Service.PgBouncerSettings.Pgbouncer.Raw; len(bouncer) > 0 {
+				var rawBouncer map[string]interface{}
+				if err := json.Unmarshal(bouncer, &rawBouncer); err != nil {
+					return nil, nil, "", fmt.Errorf("cannot unmarshal existing pgbouncersettings: %w", err)
+				}
+
+				bouncerSettings := make(map[string]string)
+				for k, v := range rawBouncer {
+					bouncerSettings[k] = fmt.Sprint(v)
+				}
+
+				if err := mergo.Merge(&pgBouncerConfig, bouncerSettings, mergo.WithOverride); err != nil {
+					return nil, nil, "", fmt.Errorf("cannot merge existing pgbouncerconfig: %w", err)
+				}
+			}
+		}
+
+		if settings := existing.Spec.Parameters.Service.PostgreSQLSettings.Raw; len(settings) > 0 {
+			var rawSettings map[string]interface{}
+			if err := json.Unmarshal(settings, &rawSettings); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot unmarshal existing pgsettings: %w", err)
+			}
+
+			pgSettingsMap := make(map[string]string)
+			for k, v := range rawSettings {
+				pgSettingsMap[k] = fmt.Sprint(v)
+			}
+
+			if err := mergo.Merge(&pgSettings, pgSettingsMap, mergo.WithOverride); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot merge existing pgsettings: %w", err)
+			}
+		}
+
+	case err == runtime.ErrNotFound:
+		pgDiskSize = "10Gi"
+
+	default:
+		return nil, nil, "", fmt.Errorf("cannot get observed postgres instance: %w", err)
+	}
+
+	if params := comp.Spec.Parameters.Service.PostgreSQLParameters; params != nil {
+		if params.Size.Disk != "" {
+			desiredDiskSize, err := resource.ParseQuantity(params.Size.Disk)
+			if err != nil {
+				return nil, nil, "", fmt.Errorf("cannot parse desired disk size %q: %w", params.Size.Disk, err)
+			}
+
+			currentDiskSize, err := resource.ParseQuantity(pgDiskSize)
+			if err != nil {
+				return nil, nil, "", fmt.Errorf("cannot parse current disk size %q: %w", pgDiskSize, err)
+			}
+
+			if desiredDiskSize.Cmp(currentDiskSize) >= 0 {
+				pgDiskSize = params.Size.Disk
+			}
+		}
+
+		if params.Service.PgBouncerSettings != nil && len(params.Service.PgBouncerSettings.Pgbouncer.Raw) > 0 {
+			var rawBouncer map[string]interface{}
+			if err := json.Unmarshal(params.Service.PgBouncerSettings.Pgbouncer.Raw, &rawBouncer); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot unmarshal user pgbouncersettings: %w", err)
+			}
+
+			bouncerSettings := make(map[string]string)
+			for k, v := range rawBouncer {
+				bouncerSettings[k] = fmt.Sprint(v)
+			}
+
+			if err := mergo.Merge(&pgBouncerConfig, bouncerSettings, mergo.WithOverride); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot merge user pgbouncerconfig: %w", err)
+			}
+		}
+
+		if len(params.Service.PostgreSQLSettings.Raw) > 0 {
+			var rawSettings map[string]interface{}
+			if err := json.Unmarshal(params.Service.PostgreSQLSettings.Raw, &rawSettings); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot unmarshal user pgsettings: %w", err)
+			}
+
+			pgSettingsMap := make(map[string]string)
+			for k, v := range rawSettings {
+				pgSettingsMap[k] = fmt.Sprint(v)
+			}
+
+			if err := mergo.Merge(&pgSettings, pgSettingsMap, mergo.WithOverride); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot merge user pgsettings: %w", err)
+			}
+		}
+	}
+
+	return pgBouncerConfig, pgSettings, pgDiskSize, nil
+}
+
 func createInstallCollaboraConfigMap(comp *vshnv1.VSHNNextcloud, svc *runtime.ServiceRuntime) error {
 	cm := &corev1.ConfigMap{
 		ObjectMeta: metav1.ObjectMeta{
@@ -480,7 +611,7 @@ func configureCronSidecar(values map[string]interface{}, version string, svc *ru
 
 func addApacheConfig(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNNextcloud) error {
 
-	cm := &v1.ConfigMap{
+	cm := &corev1.ConfigMap{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "apache-config",
 			Namespace: comp.GetInstanceNamespace(),
@@ -500,7 +631,7 @@ func addApacheConfig(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNNextcloud) er
 
 func addNextcloudHooks(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNNextcloud) error {
 
-	cm := &v1.ConfigMap{
+	cm := &corev1.ConfigMap{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "nextcloud-hooks",
 			Namespace: comp.GetInstanceNamespace(),


Reviews:
user: login: wejdross
body: Some small things, please also make sure that description is correct and explain what happens in the PR, it's then placed in our Release Notes
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T09:40:11Z
user: login: mdnix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T14:51:24Z
user: login: mdnix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T14:59:31Z
user: login: mdnix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T15:00:09Z
user: login: Kidswiss
body: Found one bug, that could bite us in the butt :)
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T06:43:46Z
user: login: Kidswiss
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T08:29:41Z
user: login: wejdross
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T09:13:55Z

Review comments:
diff_hunk: @@ -33,6 +34,14 @@ func NewPostgreSQLDependencyBuilder(svc *runtime.ServiceRuntime, comp InfoGetter
 	}
 }
 
+func (a *PostgreSQLDependencyBuilder) SetSize(size string) *PostgreSQLDependencyBuilder {
path: pkg/comp-functions/functions/common/postgresql.go
user: login: wejdross
body: Maybe rename to `setDiskSize`? 
created_at: 2025-05-08T09:30:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,6 +86,20 @@ func (a *PostgreSQLDependencyBuilder) CreateDependency() (string, error) {
 		}
 	}
 
+	pgSettingsRaw := k8sruntime.RawExtension{}
+	if a.pgSettings != nil {
+		if a.pgSettings != nil {
path: pkg/comp-functions/functions/common/postgresql.go
user: login: wejdross
body: do I see double here? :D 
can You elaborate what this code does?
created_at: 2025-05-08T09:31:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -143,6 +157,104 @@ func DeployNextcloud(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runti
 	return nil
 }
 
+func getObservedPostgresSettings(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNNextcloud) (map[string]string, map[string]string, string, error) {
+	pgBouncerConfig := map[string]string{
+		"max_client_conn": "1000",
+	}
+	pgSettings := map[string]string{
+		"max_connections": "1000",
+	}
+	pgDiskSize := ""
+
+	if !comp.Spec.Parameters.Service.UseExternalPostgreSQL {
+		return pgBouncerConfig, pgSettings, pgDiskSize, nil
+	}
+
+	existing := &vshnv1.XVSHNPostgreSQL{}
+	err := svc.GetObservedComposedResource(existing, comp.GetName()+pgInstanceNameSuffix)
+	if err == nil {
+		if existing.Spec.Parameters.Size.Disk != "" {
+			pgDiskSize = existing.Spec.Parameters.Size.Disk
+		}
+
+		if existing.Spec.Parameters.Service.PgBouncerSettings != nil {
+			if bouncer := existing.Spec.Parameters.Service.PgBouncerSettings.Pgbouncer.Raw; len(bouncer) > 0 {
+				var rawBouncer map[string]interface{}
+				if err := json.Unmarshal(bouncer, &rawBouncer); err != nil {
+					return nil, nil, "", fmt.Errorf("cannot unmarshal existing pgbouncersettings: %w", err)
+				}
+
+				bouncerSettings := make(map[string]string)
+				for k, v := range rawBouncer {
+					bouncerSettings[k] = fmt.Sprint(v)
+				}
+
+				if err := mergo.Merge(&pgBouncerConfig, bouncerSettings, mergo.WithOverride); err != nil {
+					return nil, nil, "", fmt.Errorf("cannot merge existing pgbouncerconfig: %w", err)
+				}
+			}
+		}
+
+		if settings := existing.Spec.Parameters.Service.PostgreSQLSettings.Raw; len(settings) > 0 {
+			var rawSettings map[string]interface{}
+			if err := json.Unmarshal(settings, &rawSettings); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot unmarshal existing pgsettings: %w", err)
+			}
+
+			pgSettingsMap := make(map[string]string)
+			for k, v := range rawSettings {
+				pgSettingsMap[k] = fmt.Sprint(v)
+			}
+
+			if err := mergo.Merge(&pgSettings, pgSettingsMap, mergo.WithOverride); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot merge existing pgsettings: %w", err)
+			}
+		}
+	} else if err == runtime.ErrNotFound {
+		pgDiskSize = "10Gi"
+	}
path: pkg/comp-functions/functions/vshnnextcloud/deploy.go
user: login: wejdross
body: I'd still add some logic if err !=nil, because we miss it now
created_at: 2025-05-08T09:37:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,6 +34,14 @@ func NewPostgreSQLDependencyBuilder(svc *runtime.ServiceRuntime, comp InfoGetter
 	}
 }
 
+func (a *PostgreSQLDependencyBuilder) SetSize(size string) *PostgreSQLDependencyBuilder {
path: pkg/comp-functions/functions/common/postgresql.go
user: login: mdnix
body: Yes, I like that better as it's clearer
created_at: 2025-05-08T14:51:24Z
author_association: MEMBER
diff_hunk: @@ -72,6 +86,20 @@ func (a *PostgreSQLDependencyBuilder) CreateDependency() (string, error) {
 		}
 	}
 
+	pgSettingsRaw := k8sruntime.RawExtension{}
+	if a.pgSettings != nil {
+		if a.pgSettings != nil {
path: pkg/comp-functions/functions/common/postgresql.go
user: login: mdnix
body: You do, thanks for pointing that out. :D
I've removed that totally unnecessary check, also for the pgBouncerConfig above.

Basically, what I do here is take the settings I pass through the method `AddPGSettings` and marshal them so I can use them as a byte slice below when actually setting them in `vshnv1.VSHNPostgreSQLServiceSpec`. The field `PostgreSQLSettings` where I set this is of type `runtime.RawExtension`. Same goes for the pgBouncer we had before...
created_at: 2025-05-08T14:59:31Z
author_association: MEMBER
diff_hunk: @@ -143,6 +157,104 @@ func DeployNextcloud(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runti
 	return nil
 }
 
+func getObservedPostgresSettings(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNNextcloud) (map[string]string, map[string]string, string, error) {
+	pgBouncerConfig := map[string]string{
+		"max_client_conn": "1000",
+	}
+	pgSettings := map[string]string{
+		"max_connections": "1000",
+	}
+	pgDiskSize := ""
+
+	if !comp.Spec.Parameters.Service.UseExternalPostgreSQL {
+		return pgBouncerConfig, pgSettings, pgDiskSize, nil
+	}
+
+	existing := &vshnv1.XVSHNPostgreSQL{}
+	err := svc.GetObservedComposedResource(existing, comp.GetName()+pgInstanceNameSuffix)
+	if err == nil {
+		if existing.Spec.Parameters.Size.Disk != "" {
+			pgDiskSize = existing.Spec.Parameters.Size.Disk
+		}
+
+		if existing.Spec.Parameters.Service.PgBouncerSettings != nil {
+			if bouncer := existing.Spec.Parameters.Service.PgBouncerSettings.Pgbouncer.Raw; len(bouncer) > 0 {
+				var rawBouncer map[string]interface{}
+				if err := json.Unmarshal(bouncer, &rawBouncer); err != nil {
+					return nil, nil, "", fmt.Errorf("cannot unmarshal existing pgbouncersettings: %w", err)
+				}
+
+				bouncerSettings := make(map[string]string)
+				for k, v := range rawBouncer {
+					bouncerSettings[k] = fmt.Sprint(v)
+				}
+
+				if err := mergo.Merge(&pgBouncerConfig, bouncerSettings, mergo.WithOverride); err != nil {
+					return nil, nil, "", fmt.Errorf("cannot merge existing pgbouncerconfig: %w", err)
+				}
+			}
+		}
+
+		if settings := existing.Spec.Parameters.Service.PostgreSQLSettings.Raw; len(settings) > 0 {
+			var rawSettings map[string]interface{}
+			if err := json.Unmarshal(settings, &rawSettings); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot unmarshal existing pgsettings: %w", err)
+			}
+
+			pgSettingsMap := make(map[string]string)
+			for k, v := range rawSettings {
+				pgSettingsMap[k] = fmt.Sprint(v)
+			}
+
+			if err := mergo.Merge(&pgSettings, pgSettingsMap, mergo.WithOverride); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot merge existing pgsettings: %w", err)
+			}
+		}
+	} else if err == runtime.ErrNotFound {
+		pgDiskSize = "10Gi"
+	}
path: pkg/comp-functions/functions/vshnnextcloud/deploy.go
user: login: mdnix
body: Fair, I have extended the error handling
created_at: 2025-05-08T15:00:09Z
author_association: MEMBER
diff_hunk: @@ -143,6 +157,110 @@ func DeployNextcloud(ctx context.Context, comp *vshnv1.VSHNNextcloud, svc *runti
 	return nil
 }
 
+func getObservedPostgresSettings(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNNextcloud) (map[string]string, map[string]string, string, error) {
+	pgBouncerConfig := map[string]string{
+		"max_client_conn": "1000",
+	}
+	pgSettings := map[string]string{
+		"max_connections": "1000",
+	}
+	pgDiskSize := ""
+
+	if !comp.Spec.Parameters.Service.UseExternalPostgreSQL {
+		return pgBouncerConfig, pgSettings, pgDiskSize, nil
+	}
+
+	existing := &vshnv1.XVSHNPostgreSQL{}
+	err := svc.GetObservedComposedResource(existing, comp.GetName()+pgInstanceNameSuffix)
+
+	switch {
+	case err == nil:
+		if existing.Spec.Parameters.Size.Disk != "" {
+			pgDiskSize = existing.Spec.Parameters.Size.Disk
+		}
+
+		if existing.Spec.Parameters.Service.PgBouncerSettings != nil {
+			if bouncer := existing.Spec.Parameters.Service.PgBouncerSettings.Pgbouncer.Raw; len(bouncer) > 0 {
+				var rawBouncer map[string]interface{}
+				if err := json.Unmarshal(bouncer, &rawBouncer); err != nil {
+					return nil, nil, "", fmt.Errorf("cannot unmarshal existing pgbouncersettings: %w", err)
+				}
+
+				bouncerSettings := make(map[string]string)
+				for k, v := range rawBouncer {
+					bouncerSettings[k] = fmt.Sprint(v)
+				}
+
+				if err := mergo.Merge(&pgBouncerConfig, bouncerSettings, mergo.WithOverride); err != nil {
+					return nil, nil, "", fmt.Errorf("cannot merge existing pgbouncerconfig: %w", err)
+				}
+			}
+		}
+
+		if settings := existing.Spec.Parameters.Service.PostgreSQLSettings.Raw; len(settings) > 0 {
+			var rawSettings map[string]interface{}
+			if err := json.Unmarshal(settings, &rawSettings); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot unmarshal existing pgsettings: %w", err)
+			}
+
+			pgSettingsMap := make(map[string]string)
+			for k, v := range rawSettings {
+				pgSettingsMap[k] = fmt.Sprint(v)
+			}
+
+			if err := mergo.Merge(&pgSettings, pgSettingsMap, mergo.WithOverride); err != nil {
+				return nil, nil, "", fmt.Errorf("cannot merge existing pgsettings: %w", err)
+			}
+		}
+
+	case err == runtime.ErrNotFound:
+		pgDiskSize = "10Gi"
+
+	default:
+		return nil, nil, "", fmt.Errorf("cannot get observed postgres instance: %w", err)
+	}
+
+	if params := comp.Spec.Parameters.Service.PostgreSQLParameters; params != nil {
+		if params.Size.Disk != "" && !(pgDiskSize == "10Gi" && params.Size.Disk < pgDiskSize) {
path: pkg/comp-functions/functions/vshnnextcloud/deploy.go
user: login: Kidswiss
body: We should probably convert the strings to int for the comparison. See: https://go.dev/play/p/0Kubqu2OQ-D

For strings with the less than and greater than operator golang will look at the underlying bytes, which may or may not match the actual value.

You can use `k8s.io/apimachinery/pkg/api/resource` to parse the resource strings and compare them. See `quotas.go` for how to use it.
created_at: 2025-05-09T06:38:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-07T13:54:52Z
body: - [ ] Check out Your new Comp-Functions diff at: https://git.vshn.net/appcat/comp-func-pipelines/-/pipelines/571736
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-08T14:55:23Z
body: - [ ] Check out Your new Comp-Functions diff at: https://git.vshn.net/appcat/comp-func-pipelines/-/pipelines/572302
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-09T08:35:46Z
body: - [ ] Check out Your new Comp-Functions diff at: https://git.vshn.net/appcat/comp-func-pipelines/-/pipelines/572490
author_association: CONTRIBUTOR