Skip to content

Commit 1896854

Browse files
committed
Ensure composition for nested postgres is not changed
this ensures that the existing vshnkeycloak instances will not switch the composition used by the depending postgres instance and thus breaking the instance.
1 parent 07b8a2d commit 1896854

File tree

4 files changed

+96
-6
lines changed

4 files changed

+96
-6
lines changed

cmd/controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ func setupWebhooks(mgr manager.Manager, withQuota bool, withAppcatWebhooks bool,
196196
if err != nil {
197197
return err
198198
}
199+
err = webhooks.SetupXVSHNPostgreSQLWebhookHandlerWithManager(mgr)
200+
if err != nil {
201+
return err
202+
}
199203
err = webhooks.SetupRedisWebhookHandlerWithManager(mgr, withQuota)
200204
if err != nil {
201205
return err

config/controller/webhooks.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,22 @@ webhooks:
324324
resources:
325325
- xobjectbuckets
326326
sideEffects: None
327+
- admissionReviewVersions:
328+
- v1
329+
clientConfig:
330+
service:
331+
name: webhook-service
332+
namespace: system
333+
path: /validate-vshn-appcat-vshn-io-v1-xvshnpostgresql
334+
failurePolicy: Fail
335+
name: xvshnpostgresql.vshn.appcat.vshn.io
336+
rules:
337+
- apiGroups:
338+
- vshn.appcat.vshn.io
339+
apiVersions:
340+
- v1
341+
operations:
342+
- UPDATE
343+
resources:
344+
- xvshnpostgresqls
345+
sideEffects: None

pkg/comp-functions/functions/common/postgresql.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,21 @@ func (a *PostgreSQLDependencyBuilder) CreateDependency() (string, error) {
162162
// and would therefore override any value we set before the merge.
163163
params.Instances = a.comp.GetInstances()
164164

165+
// Preserve the compositionRef of an already-existing nested PostgreSQL composite.
166+
// If we always wrote defaultPGComposition, switching that default (e.g. Stackgres → CNPG)
167+
// would silently migrate live instances on the next reconcile, which is destructive.
168+
// If the observed composite exists but has no compositionRef we return an error rather than
169+
// falling back to the default — an empty ref on an existing instance is unexpected and
170+
// silently overwriting it could trigger an unintended backend migration.
171+
compositionName := a.svc.Config.Data["defaultPGComposition"]
172+
observedPg := &vshnv1.XVSHNPostgreSQL{}
173+
if err := a.svc.GetObservedComposedResource(observedPg, a.comp.GetName()+PgInstanceNameSuffix); err == nil {
174+
if observedPg.Spec.CompositionRef.Name == "" {
175+
return "", fmt.Errorf("observed nested postgresql composite %q has no compositionRef, refusing to overwrite with default to prevent unintended backend migration", observedPg.GetName())
176+
}
177+
compositionName = observedPg.Spec.CompositionRef.Name
178+
}
179+
165180
// We have to ignore the providerconfig on the composite itself.
166181
pg := &vshnv1.XVSHNPostgreSQL{
167182
ObjectMeta: metav1.ObjectMeta{
@@ -173,7 +188,7 @@ func (a *PostgreSQLDependencyBuilder) CreateDependency() (string, error) {
173188
Spec: vshnv1.XVSHNPostgreSQLSpec{
174189
Parameters: *params,
175190
CompositionRef: v1.CompositionReference{
176-
Name: a.svc.Config.Data["defaultPGComposition"],
191+
Name: compositionName,
177192
},
178193
ResourceSpec: xpv1.ResourceSpec{
179194
WriteConnectionSecretToReference: &xpv1.SecretReference{

pkg/controller/webhooks/postgresql.go

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import (
2424
// See https://book.kubebuilder.io/reference/markers/webhook for docs
2525
//+kubebuilder:webhook:verbs=create;update;delete,path=/validate-vshn-appcat-vshn-io-v1-vshnpostgresql,mutating=false,failurePolicy=fail,groups=vshn.appcat.vshn.io,resources=vshnpostgresqls,versions=v1,name=postgresql.vshn.appcat.vshn.io,sideEffects=None,admissionReviewVersions=v1
2626

27+
// Protect the nested XVSHNPostgreSQL composite from having its compositionRef changed once set.
28+
// This prevents accidental backend migrations (e.g. StackGres → CNPG) on live instances.
29+
//+kubebuilder:webhook:verbs=update,path=/validate-vshn-appcat-vshn-io-v1-xvshnpostgresql,mutating=false,failurePolicy=fail,groups=vshn.appcat.vshn.io,resources=xvshnpostgresqls,versions=v1,name=xvshnpostgresql.vshn.appcat.vshn.io,sideEffects=None,admissionReviewVersions=v1
30+
2731
//RBAC
2832
//+kubebuilder:rbac:groups=vshn.appcat.vshn.io,resources=xvshnpostgresqls,verbs=get;list;watch;patch;update
2933
//+kubebuilder:rbac:groups=vshn.appcat.vshn.io,resources=xvshnpostgresqls/status,verbs=get;list;watch;patch;update
@@ -38,10 +42,12 @@ const (
3842
)
3943

4044
var (
41-
pgGK = schema.GroupKind{Group: "vshn.appcat.vshn.io", Kind: "VSHNPostgreSQL"}
42-
pgGR = schema.GroupResource{Group: pgGK.Group, Resource: "vshnpostgresqls"}
45+
pgGK = schema.GroupKind{Group: "vshn.appcat.vshn.io", Kind: "VSHNPostgreSQL"}
46+
pgGR = schema.GroupResource{Group: pgGK.Group, Resource: "vshnpostgresqls"}
47+
xpgGK = schema.GroupKind{Group: "vshn.appcat.vshn.io", Kind: "XVSHNPostgreSQL"}
4348

4449
_ webhook.CustomValidator = &PostgreSQLWebhookHandler{}
50+
_ webhook.CustomValidator = &XVSHNPostgreSQLWebhookHandler{}
4551

4652
blocklist = map[string]string{
4753
"listen_addresses": "",
@@ -84,6 +90,46 @@ func SetupPostgreSQLWebhookHandlerWithManager(mgr ctrl.Manager, withQuota bool)
8490
Complete()
8591
}
8692

93+
// XVSHNPostgreSQLWebhookHandler validates the nested XVSHNPostgreSQL composite resource.
94+
// Its sole purpose is to make compositionRef immutable once set, preventing accidental
95+
// backend migrations on live nested instances (e.g. those created by VSHNKeycloak).
96+
type XVSHNPostgreSQLWebhookHandler struct{}
97+
98+
// SetupXVSHNPostgreSQLWebhookHandlerWithManager registers the XVSHNPostgreSQL validation webhook.
99+
func SetupXVSHNPostgreSQLWebhookHandlerWithManager(mgr ctrl.Manager) error {
100+
return ctrl.NewWebhookManagedBy(mgr).
101+
For(&vshnv1.XVSHNPostgreSQL{}).
102+
WithValidator(&XVSHNPostgreSQLWebhookHandler{}).
103+
Complete()
104+
}
105+
106+
func (x *XVSHNPostgreSQLWebhookHandler) ValidateCreate(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
107+
return nil, nil
108+
}
109+
110+
func (x *XVSHNPostgreSQLWebhookHandler) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
111+
oldPg, ok := oldObj.(*vshnv1.XVSHNPostgreSQL)
112+
if !ok {
113+
return nil, fmt.Errorf("provided manifest is not a valid XVSHNPostgreSQL object")
114+
}
115+
newPg, ok := newObj.(*vshnv1.XVSHNPostgreSQL)
116+
if !ok {
117+
return nil, fmt.Errorf("provided manifest is not a valid XVSHNPostgreSQL object")
118+
}
119+
120+
if newPg.DeletionTimestamp != nil {
121+
return nil, nil
122+
}
123+
124+
allErrs := newFielErrors(newPg.Name, xpgGK)
125+
allErrs.Add(validateCompositionRefImmutability(oldPg.Spec.CompositionRef.Name, newPg.Spec.CompositionRef.Name))
126+
return nil, allErrs.Get()
127+
}
128+
129+
func (x *XVSHNPostgreSQLWebhookHandler) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
130+
return nil, nil
131+
}
132+
87133
func (p *PostgreSQLWebhookHandler) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
88134
return p.validatePostgreSQL(ctx, obj, nil, true)
89135
}
@@ -151,9 +197,7 @@ func (p *PostgreSQLWebhookHandler) validatePostgreSQL(ctx context.Context, newOb
151197

152198
// Do not allow changing compositionRef if it has been set previously.
153199
// When creating a new VSHNPostgresQL, crossplane will automatically set this field if unset.
154-
if oldPg.Spec.CompositionRef.Name != "" && newPg.Spec.CompositionRef.Name != oldPg.Spec.CompositionRef.Name {
155-
allErrs.Add(field.Forbidden(field.NewPath("spec", "compositionRef"), "compositionRef is immutable"))
156-
}
200+
allErrs.Add(validateCompositionRefImmutability(oldPg.Spec.CompositionRef.Name, newPg.Spec.CompositionRef.Name))
157201

158202
// Check for disk downsizing
159203
if diskErr := p.DefaultWebhookHandler.ValidateDiskDownsizing(ctx, oldPg, newPg, p.gk.Kind); diskErr != nil {
@@ -389,6 +433,14 @@ func validatePostgreSQLEncryptionChanges(newEncryption, oldEncryption *vshnv1.VS
389433
return nil
390434
}
391435

436+
// validateCompositionRefImmutability returns a Forbidden error if the compositionRef name has changed after being set
437+
func validateCompositionRefImmutability(oldRef, newRef string) *field.Error {
438+
if oldRef != "" && newRef != oldRef {
439+
return field.Forbidden(field.NewPath("spec", "compositionRef"), "compositionRef is immutable")
440+
}
441+
return nil
442+
}
443+
392444
// validatePinImageTag validates that pinImageTag's major version matches the specified majorVersion
393445
func validatePinImageTag(pinImageTag, majorVersion string) *field.Error {
394446
if pinImageTag == "" {

0 commit comments

Comments
 (0)