v2/pkg/genruntime/admissions.go (92 lines of code) (raw):
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*/
package genruntime
import (
"context"
"github.com/rotisserie/eris"
"k8s.io/apimachinery/pkg/runtime"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
// Validator is similar to controller-runtime/pkg/webhook/admission Validator. Implementing this interface
// allows you to hook into the code generated validations and add custom handcrafted validations.
type Validator[T runtime.Object] interface {
// CreateValidations returns validation functions that should be run on create.
CreateValidations() []func(ctx context.Context, obj T) (admission.Warnings, error)
// UpdateValidations returns validation functions that should be run on update.
UpdateValidations() []func(ctx context.Context, oldObj T, newObj T) (admission.Warnings, error)
// DeleteValidations returns validation functions that should be run on delete.
DeleteValidations() []func(ctx context.Context, obj T) (admission.Warnings, error)
}
// Defaulter is similar to controller-runtime/pkg/webhook/admission Defaulter. Implementing this interface
// allows you to hook into the code generated defaults and add custom handcrafted defaults.
type Defaulter interface {
// CustomDefault performs custom defaults that are run in addition to the code generated defaults.
CustomDefault(ctx context.Context, obj runtime.Object) error
}
// ValidateWriteOnceProperties function validates the update on WriteOnce properties.
func ValidateWriteOnceProperties(oldObj ARMMetaObject, newObj ARMMetaObject) (admission.Warnings, error) {
var errs []error
if !IsResourceCreatedSuccessfully(newObj) {
return nil, nil
}
// Prohibit changing the AzureName,
// but allow it to be set if it's empty.
//
// https://github.com/Azure/azure-service-operator/issues/4306
oldName := oldObj.AzureName()
if oldName != "" && oldName != newObj.AzureName() {
err := eris.Errorf(
"updating 'spec.azureName' is not allowed for '%s : %s",
oldObj.GetObjectKind().GroupVersionKind(),
oldObj.GetName())
errs = append(errs, err)
}
// Ensure that owner has not been changed
oldOwner := oldObj.Owner()
newOwner := newObj.Owner()
bothHaveOwner := oldOwner != nil && newOwner != nil
ownerAdded := oldOwner == nil && newOwner != nil
ownerRemoved := oldOwner != nil && newOwner == nil
ownerNameChanged := bothHaveOwner && oldOwner.Name != newOwner.Name
ownerARMIDChanged := bothHaveOwner && oldOwner.ARMID != newOwner.ARMID
if ownerAdded {
// This error may not be possible to trigger in practice, as it requires an Azure resource that supports existing without an owner
// or with an owner. There aren't any resources that meet those criteria that we know of, so this check is primarily us being
// defensive.
errs = append(errs, eris.Errorf("adding an owner to an already created resource is not allowed for '%s : %s", oldObj.GetObjectKind().GroupVersionKind(), oldObj.GetName()))
} else if ownerNameChanged {
errs = append(errs, eris.Errorf("updating 'spec.owner.name' is not allowed for '%s : %s", oldObj.GetObjectKind().GroupVersionKind(), oldObj.GetName()))
} else if ownerARMIDChanged {
errs = append(errs, eris.Errorf("updating 'spec.owner.armId' is not allowed for '%s : %s", oldObj.GetObjectKind().GroupVersionKind(), oldObj.GetName()))
} else if ownerRemoved {
errs = append(errs, eris.Errorf("removing 'spec.owner' is not allowed for '%s : %s", oldObj.GetObjectKind().GroupVersionKind(), oldObj.GetName()))
}
return nil, kerrors.NewAggregate(errs)
}
func ValidateCreate[T runtime.Object](ctx context.Context, resource T, validations []func(ctx context.Context, resource T) (admission.Warnings, error)) (admission.Warnings, error) {
var errs []error
var warnings admission.Warnings
for _, validation := range validations {
warning, err := validation(ctx, resource)
if warning != nil {
warnings = append(warnings, warning...)
}
if err != nil {
errs = append(errs, err)
}
}
return warnings, kerrors.NewAggregate(errs)
}
func ValidateDelete[T runtime.Object](ctx context.Context, resource T, validations []func(ctx context.Context, resource T) (admission.Warnings, error)) (admission.Warnings, error) {
var errs []error
var warnings admission.Warnings
for _, validation := range validations {
warning, err := validation(ctx, resource)
if warning != nil {
warnings = append(warnings, warning...)
}
if err != nil {
errs = append(errs, err)
}
}
return warnings, kerrors.NewAggregate(errs)
}
func ValidateUpdate[T runtime.Object](ctx context.Context, old T, new T, validations []func(ctx context.Context, old T, new T) (admission.Warnings, error)) (admission.Warnings, error) {
var errs []error
var warnings admission.Warnings
for _, validation := range validations {
warning, err := validation(ctx, old, new)
if warning != nil {
warnings = append(warnings, warning...)
}
if err != nil {
errs = append(errs, err)
}
}
return warnings, kerrors.NewAggregate(errs)
}
func IsResourceCreatedSuccessfully(obj ARMMetaObject) bool {
return GetResourceIDOrDefault(obj) != ""
}