diff --git a/pkg/webhook/register.go b/pkg/webhook/register.go index 3d02adafea..0401c276b5 100644 --- a/pkg/webhook/register.go +++ b/pkg/webhook/register.go @@ -180,6 +180,18 @@ func GetCommonWebhookConfigs() ([]Config, error) { ), SideEffects: admissionregistration.SideEffectClassNone, }, + { + Name: "state-into-spec-validation.cnrm.cloud.google.com", + Path: "/state-into-spec-validation", + Type: Validating, + HandlerFunc: NewRequestLoggingHandler(NewStateIntoSpecAnnotationValidatorHandler(), "state-into-spec validation"), + FailurePolicy: admissionregistration.Fail, + Rules: getRulesForOperationTypes(allResourcesRules, + admissionregistration.Create, + admissionregistration.Update, + ), + SideEffects: admissionregistration.SideEffectClassNone, + }, } return whCfgs, nil } diff --git a/pkg/webhook/state_into_spec_annotation_validator.go b/pkg/webhook/state_into_spec_annotation_validator.go new file mode 100644 index 0000000000..5f7c51c783 --- /dev/null +++ b/pkg/webhook/state_into_spec_annotation_validator.go @@ -0,0 +1,63 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webhook + +import ( + "context" + "fmt" + "net/http" + + "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +type stateIntoSpecAnnotationValidator struct { + client client.Client +} + +// NewStateIntoSpecAnnotationValidatorHandler creates an instance of +// stateIntoSpecAnnotationValidator to handle state-into-spec annotation +// validation. +func NewStateIntoSpecAnnotationValidatorHandler() HandlerFunc { + return func(mgr manager.Manager) admission.Handler { + return &stateIntoSpecAnnotationValidator{client: mgr.GetClient()} + } +} + +func (a *stateIntoSpecAnnotationValidator) Handle(ctx context.Context, req admission.Request) admission.Response { + deserializer := codecs.UniversalDeserializer() + obj := &unstructured.Unstructured{} + if _, _, err := deserializer.Decode(req.AdmissionRequest.Object.Raw, nil, obj); err != nil { + klog.Error(err) + return admission.Errored(http.StatusBadRequest, + fmt.Errorf("error decoding object: %w", err)) + } + + value, ok := k8s.GetAnnotation(k8s.StateIntoSpecAnnotation, obj) + if ok && value == k8s.StateMergeIntoSpec { + return allowedResponse.WithWarnings( + fmt.Sprintf("'%v: %v' is unsupported for CRDs added in "+ + "1.114.0 and later. Use '%v' instead. More details can be "+ + "found at https://cloud.google.com/config-connector/docs/concepts/ignore-unspecified-fields.", + k8s.StateIntoSpecAnnotation, k8s.StateMergeIntoSpec, k8s.StateAbsentInSpec)) + } + // TODO: Verify if the state-into-spec annotation will be defaulted to 'merge'. + return allowedResponse +}