From 5256f8a931ebaf2cc9af8537eb0b573b874c61f2 Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Fri, 15 Nov 2019 07:58:02 -0800 Subject: [PATCH] Auto-update dependencies (#131) Produced via: `dep ensure -update knative.dev/test-infra knative.dev/pkg` /assign n3wscott --- Gopkg.lock | 4 +- vendor/knative.dev/pkg/apis/interfaces.go | 9 - .../pkg/injection/sharedmain/main.go | 4 +- vendor/knative.dev/pkg/test/e2e_flags.go | 55 +++- vendor/knative.dev/pkg/testing/resource.go | 8 +- .../{ => defaulting}/controller.go | 5 +- .../defaulting.go} | 58 +--- .../{ => defaulting}/user_info.go | 2 +- .../webhook/resourcesemantics/interface.go | 30 ++ .../validation/controller.go | 84 ++++++ .../validation/validation.go | 264 ++++++++++++++++++ vendor/knative.dev/pkg/webhook/webhook.go | 39 +-- 12 files changed, 471 insertions(+), 91 deletions(-) rename vendor/knative.dev/pkg/webhook/resourcesemantics/{ => defaulting}/controller.go (95%) rename vendor/knative.dev/pkg/webhook/resourcesemantics/{resourcesemantics.go => defaulting/defaulting.go} (85%) rename vendor/knative.dev/pkg/webhook/resourcesemantics/{ => defaulting}/user_info.go (98%) create mode 100644 vendor/knative.dev/pkg/webhook/resourcesemantics/interface.go create mode 100644 vendor/knative.dev/pkg/webhook/resourcesemantics/validation/controller.go create mode 100644 vendor/knative.dev/pkg/webhook/resourcesemantics/validation/validation.go diff --git a/Gopkg.lock b/Gopkg.lock index 12832293..5064b3d2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -933,7 +933,7 @@ [[projects]] branch = "master" - digest = "1:ff0b6b2483ef9341cc0c524f1e5bdbba37b56dde366791986e098a01de81dcac" + digest = "1:b1b3870d2746e5277761a3c2811d2b486db20a09763a1d98476cf82f2afbc0fc" name = "knative.dev/pkg" packages = [ "apis", @@ -952,7 +952,7 @@ "metrics/metricskey", ] pruneopts = "T" - revision = "d3841ea2c3cb7246076d771abd5cef8b94ef9031" + revision = "4836f680bbe5dc615e9c822f21c6685d97c6d553" [[projects]] branch = "master" diff --git a/vendor/knative.dev/pkg/apis/interfaces.go b/vendor/knative.dev/pkg/apis/interfaces.go index 6b6c772d..fef69d8b 100644 --- a/vendor/knative.dev/pkg/apis/interfaces.go +++ b/vendor/knative.dev/pkg/apis/interfaces.go @@ -44,15 +44,6 @@ type Convertible interface { ConvertDown(ctx context.Context, from Convertible) error } -// Immutable indicates that a particular type has fields that should -// not change after creation. -// DEPRECATED: Use WithinUpdate / GetBaseline from within Validatable instead. -type Immutable interface { - // CheckImmutableFields checks that the current instance's immutable - // fields haven't changed from the provided original. - CheckImmutableFields(ctx context.Context, original Immutable) *FieldError -} - // Listable indicates that a particular type can be returned via the returned // list type by the API server. type Listable interface { diff --git a/vendor/knative.dev/pkg/injection/sharedmain/main.go b/vendor/knative.dev/pkg/injection/sharedmain/main.go index 030d1853..bd379d9a 100644 --- a/vendor/knative.dev/pkg/injection/sharedmain/main.go +++ b/vendor/knative.dev/pkg/injection/sharedmain/main.go @@ -168,7 +168,7 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto metav1.GetOptions{}); err == nil { cmw.Watch(logging.ConfigMapName(), logging.UpdateLevelFromConfigMap(logger, atomicLevel, component)) } else if !apierrors.IsNotFound(err) { - logger.Fatalw("Error reading ConfigMap: " + logging.ConfigMapName(), zap.Error(err)) + logger.Fatalw("Error reading ConfigMap: "+logging.ConfigMapName(), zap.Error(err)) } // Watch the observability config map @@ -178,7 +178,7 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto metrics.UpdateExporterFromConfigMap(component, logger), profilingHandler.UpdateFromConfigMap) } else if !apierrors.IsNotFound(err) { - logger.Fatalw("Error reading ConfigMap: " + metrics.ConfigMapName(), zap.Error(err)) + logger.Fatalw("Error reading ConfigMap: "+metrics.ConfigMapName(), zap.Error(err)) } if err := cmw.Start(ctx.Done()); err != nil { diff --git a/vendor/knative.dev/pkg/test/e2e_flags.go b/vendor/knative.dev/pkg/test/e2e_flags.go index b5d911fd..978671f4 100644 --- a/vendor/knative.dev/pkg/test/e2e_flags.go +++ b/vendor/knative.dev/pkg/test/e2e_flags.go @@ -25,11 +25,27 @@ import ( "os" "os/user" "path" + "sync" + + "k8s.io/klog" + "knative.dev/pkg/test/logging" ) -// Flags holds the command line flags or defaults for settings in the user's environment. -// See EnvironmentFlags for a list of supported fields. -var Flags = initializeFlags() +const ( + // e2eMetricExporter is the name for the metrics exporter logger + e2eMetricExporter = "e2e-metrics" + + // The recommended default log level https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md + klogDefaultLogLevel = "2" +) + +var ( + flagsSetupOnce = &sync.Once{} + + // Flags holds the command line flags or defaults for settings in the user's environment. + // See EnvironmentFlags for a list of supported fields. + Flags = initializeFlags() +) // EnvironmentFlags define the flags that are needed to run the e2e tests. type EnvironmentFlags struct { @@ -73,9 +89,42 @@ func initializeFlags() *EnvironmentFlags { flag.StringVar(&f.Tag, "tag", "latest", "Provide the version tag for the test images.") + klog.InitFlags(nil) + flag.Set("v", klogDefaultLogLevel) + flag.Set("alsologtostderr", "true") + return &f } +func printFlags() { + fmt.Print("Test Flags: {") + flag.CommandLine.VisitAll(func(f *flag.Flag) { + fmt.Printf("'%s': '%s', ", f.Name, f.Value.String()) + }) + fmt.Println("}") +} + +// SetupLoggingFlags initializes the logging libraries at runtime +func SetupLoggingFlags() { + flagsSetupOnce.Do(func() { + if Flags.LogVerbose { + // If klog verbosity is not set to a non-default value (via "-args -v=X"), + if flag.CommandLine.Lookup("v").Value.String() == klogDefaultLogLevel { + // set up verbosity for klog so round_trippers.go prints: + // URL, request headers, response headers, and partial response body + // See levels in vendor/k8s.io/client-go/transport/round_trippers.go:DebugWrappers for other options + flag.Set("v", "8") + } + printFlags() + } + logging.InitializeLogger(Flags.LogVerbose) + + if Flags.EmitMetrics { + logging.InitializeMetricExporter(e2eMetricExporter) + } + }) +} + // ImagePath is a helper function to prefix image name with repo and suffix with tag func ImagePath(name string) string { return fmt.Sprintf("%s/%s:%s", Flags.DockerRepo, name, Flags.Tag) diff --git a/vendor/knative.dev/pkg/testing/resource.go b/vendor/knative.dev/pkg/testing/resource.go index aa1d6813..e44b2986 100644 --- a/vendor/knative.dev/pkg/testing/resource.go +++ b/vendor/knative.dev/pkg/testing/resource.go @@ -38,7 +38,6 @@ type Resource struct { // Check that Resource may be validated and defaulted. var _ apis.Validatable = (*Resource)(nil) var _ apis.Defaultable = (*Resource)(nil) -var _ apis.Immutable = (*Resource)(nil) var _ apis.Listable = (*Resource)(nil) // ResourceSpec represents test resource spec. @@ -105,12 +104,7 @@ func (cs *ResourceSpec) Validate(ctx context.Context) *apis.FieldError { return nil } -func (current *Resource) CheckImmutableFields(ctx context.Context, og apis.Immutable) *apis.FieldError { - original, ok := og.(*Resource) - if !ok { - return &apis.FieldError{Message: "The provided original was not a Resource"} - } - +func (current *Resource) CheckImmutableFields(ctx context.Context, original *Resource) *apis.FieldError { if original.Spec.FieldThatsImmutable != current.Spec.FieldThatsImmutable { return &apis.FieldError{ Message: "Immutable field changed", diff --git a/vendor/knative.dev/pkg/webhook/resourcesemantics/controller.go b/vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/controller.go similarity index 95% rename from vendor/knative.dev/pkg/webhook/resourcesemantics/controller.go rename to vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/controller.go index 38d1dd50..9f7135f6 100644 --- a/vendor/knative.dev/pkg/webhook/resourcesemantics/controller.go +++ b/vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/controller.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resourcesemantics +package defaulting import ( "context" @@ -30,13 +30,14 @@ import ( "knative.dev/pkg/logging" "knative.dev/pkg/system" "knative.dev/pkg/webhook" + "knative.dev/pkg/webhook/resourcesemantics" ) // NewAdmissionController constructs a reconciler func NewAdmissionController( ctx context.Context, name, path string, - handlers map[schema.GroupVersionKind]GenericCRD, + handlers map[schema.GroupVersionKind]resourcesemantics.GenericCRD, wc func(context.Context) context.Context, disallowUnknownFields bool, ) *controller.Impl { diff --git a/vendor/knative.dev/pkg/webhook/resourcesemantics/resourcesemantics.go b/vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/defaulting.go similarity index 85% rename from vendor/knative.dev/pkg/webhook/resourcesemantics/resourcesemantics.go rename to vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/defaulting.go index 14203a1c..bfd51ebd 100644 --- a/vendor/knative.dev/pkg/webhook/resourcesemantics/resourcesemantics.go +++ b/vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/defaulting.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resourcesemantics +package defaulting import ( "bytes" @@ -30,7 +30,6 @@ import ( "go.uber.org/zap" admissionv1beta1 "k8s.io/api/admission/v1beta1" admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes" admissionlisters "k8s.io/client-go/listers/admissionregistration/v1beta1" @@ -44,23 +43,16 @@ import ( "knative.dev/pkg/system" "knative.dev/pkg/webhook" certresources "knative.dev/pkg/webhook/certificates/resources" + "knative.dev/pkg/webhook/resourcesemantics" ) -// GenericCRD is the interface definition that allows us to perform the generic -// CRD actions like deciding whether to increment generation and so forth. -type GenericCRD interface { - apis.Defaultable - apis.Validatable - runtime.Object -} - var errMissingNewObject = errors.New("the new object may not be nil") // reconciler implements the AdmissionController for resources type reconciler struct { name string path string - handlers map[schema.GroupVersionKind]GenericCRD + handlers map[schema.GroupVersionKind]resourcesemantics.GenericCRD withContext func(context.Context) context.Context @@ -217,10 +209,10 @@ func (ac *reconciler) mutate(ctx context.Context, req *admissionv1beta1.Admissio } // nil values denote absence of `old` (create) or `new` (delete) objects. - var oldObj, newObj GenericCRD + var oldObj, newObj resourcesemantics.GenericCRD if len(newBytes) != 0 { - newObj = handler.DeepCopyObject().(GenericCRD) + newObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD) newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes)) if ac.disallowUnknownFields { newDecoder.DisallowUnknownFields() @@ -230,7 +222,7 @@ func (ac *reconciler) mutate(ctx context.Context, req *admissionv1beta1.Admissio } } if len(oldBytes) != 0 { - oldObj = handler.DeepCopyObject().(GenericCRD) + oldObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD) oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes)) if ac.disallowUnknownFields { oldDecoder.DisallowUnknownFields() @@ -258,7 +250,7 @@ func (ac *reconciler) mutate(ctx context.Context, req *admissionv1beta1.Admissio if oldObj != nil { // Copy the old object and set defaults so that we don't reject our own // defaulting done earlier in the webhook. - oldObj = oldObj.DeepCopyObject().(GenericCRD) + oldObj = oldObj.DeepCopyObject().(resourcesemantics.GenericCRD) oldObj.SetDefaults(ctx) s, ok := oldObj.(apis.HasSpec) @@ -293,17 +285,10 @@ func (ac *reconciler) mutate(ctx context.Context, req *admissionv1beta1.Admissio if newObj == nil { return nil, errMissingNewObject } - if err := validate(ctx, newObj); err != nil { - logger.Errorw("Failed the resource specific validation", zap.Error(err)) - // Return the error message as-is to give the validation callback - // discretion over (our portion of) the message that the user sees. - return nil, err - } - return json.Marshal(patches) } -func (ac *reconciler) setUserInfoAnnotations(ctx context.Context, patches duck.JSONPatch, new GenericCRD, groupName string) (duck.JSONPatch, error) { +func (ac *reconciler) setUserInfoAnnotations(ctx context.Context, patches duck.JSONPatch, new resourcesemantics.GenericCRD, groupName string) (duck.JSONPatch, error) { if new == nil { return patches, nil } @@ -341,33 +326,8 @@ func roundTripPatch(bytes []byte, unmarshalled interface{}) (duck.JSONPatch, err return jsonpatch.CreatePatch(bytes, marshaledBytes) } -// validate performs validation on the provided "new" CRD. -// For legacy purposes, this also does apis.Immutable validation, -// which is deprecated and will be removed in a future release. -func validate(ctx context.Context, new apis.Validatable) error { - if apis.IsInUpdate(ctx) { - old := apis.GetBaseline(ctx) - if immutableNew, ok := new.(apis.Immutable); ok { - immutableOld, ok := old.(apis.Immutable) - if !ok { - return fmt.Errorf("unexpected type mismatch %T vs. %T", old, new) - } - if err := immutableNew.CheckImmutableFields(ctx, immutableOld); err != nil { - return err - } - } - } - - // Can't just `return new.Validate()` because it doesn't properly nil-check. - if err := new.Validate(ctx); err != nil { - return err - } - - return nil -} - // setDefaults simply leverages apis.Defaultable to set defaults. -func setDefaults(ctx context.Context, patches duck.JSONPatch, crd GenericCRD) (duck.JSONPatch, error) { +func setDefaults(ctx context.Context, patches duck.JSONPatch, crd resourcesemantics.GenericCRD) (duck.JSONPatch, error) { before, after := crd.DeepCopyObject(), crd after.SetDefaults(ctx) diff --git a/vendor/knative.dev/pkg/webhook/resourcesemantics/user_info.go b/vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/user_info.go similarity index 98% rename from vendor/knative.dev/pkg/webhook/resourcesemantics/user_info.go rename to vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/user_info.go index ae92feb6..a1243b8e 100644 --- a/vendor/knative.dev/pkg/webhook/resourcesemantics/user_info.go +++ b/vendor/knative.dev/pkg/webhook/resourcesemantics/defaulting/user_info.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resourcesemantics +package defaulting import ( "context" diff --git a/vendor/knative.dev/pkg/webhook/resourcesemantics/interface.go b/vendor/knative.dev/pkg/webhook/resourcesemantics/interface.go new file mode 100644 index 00000000..7f216b10 --- /dev/null +++ b/vendor/knative.dev/pkg/webhook/resourcesemantics/interface.go @@ -0,0 +1,30 @@ +/* +Copyright 2019 The Knative Authors + +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 resourcesemantics + +import ( + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" +) + +// GenericCRD is the interface definition that allows us to perform the generic +// CRD actions like deciding whether to increment generation and so forth. +type GenericCRD interface { + apis.Defaultable + apis.Validatable + runtime.Object +} diff --git a/vendor/knative.dev/pkg/webhook/resourcesemantics/validation/controller.go b/vendor/knative.dev/pkg/webhook/resourcesemantics/validation/controller.go new file mode 100644 index 00000000..583784a4 --- /dev/null +++ b/vendor/knative.dev/pkg/webhook/resourcesemantics/validation/controller.go @@ -0,0 +1,84 @@ +/* +Copyright 2019 The Knative Authors + +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 validation + +import ( + "context" + + // Injection stuff + kubeclient "knative.dev/pkg/client/injection/kube/client" + vwhinformer "knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1beta1/validatingwebhookconfiguration" + secretinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/secret" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/cache" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" + "knative.dev/pkg/system" + "knative.dev/pkg/webhook" + "knative.dev/pkg/webhook/resourcesemantics" +) + +// NewAdmissionController constructs a reconciler +func NewAdmissionController( + ctx context.Context, + name, path string, + handlers map[schema.GroupVersionKind]resourcesemantics.GenericCRD, + wc func(context.Context) context.Context, + disallowUnknownFields bool, +) *controller.Impl { + + client := kubeclient.Get(ctx) + vwhInformer := vwhinformer.Get(ctx) + secretInformer := secretinformer.Get(ctx) + options := webhook.GetOptions(ctx) + + wh := &reconciler{ + name: name, + path: path, + handlers: handlers, + + withContext: wc, + disallowUnknownFields: disallowUnknownFields, + secretName: options.SecretName, + + client: client, + vwhlister: vwhInformer.Lister(), + secretlister: secretInformer.Lister(), + } + + logger := logging.FromContext(ctx) + c := controller.NewImpl(wh, logger, "ConfigMapWebhook") + + // Reconcile when the named ValidatingWebhookConfiguration changes. + vwhInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: controller.FilterWithName(name), + // It doesn't matter what we enqueue because we will always Reconcile + // the named VWH resource. + Handler: controller.HandleAll(c.Enqueue), + }) + + // Reconcile when the cert bundle changes. + secretInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{ + FilterFunc: controller.FilterWithNameAndNamespace(system.Namespace(), wh.secretName), + // It doesn't matter what we enqueue because we will always Reconcile + // the named VWH resource. + Handler: controller.HandleAll(c.Enqueue), + }) + + return c +} diff --git a/vendor/knative.dev/pkg/webhook/resourcesemantics/validation/validation.go b/vendor/knative.dev/pkg/webhook/resourcesemantics/validation/validation.go new file mode 100644 index 00000000..8dcd1dc3 --- /dev/null +++ b/vendor/knative.dev/pkg/webhook/resourcesemantics/validation/validation.go @@ -0,0 +1,264 @@ +/* +Copyright 2019 The Knative Authors + +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 validation + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "sort" + "strings" + + "github.com/markbates/inflect" + "go.uber.org/zap" + admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" + admissionlisters "k8s.io/client-go/listers/admissionregistration/v1beta1" + corelisters "k8s.io/client-go/listers/core/v1" + "knative.dev/pkg/apis" + "knative.dev/pkg/controller" + "knative.dev/pkg/kmp" + "knative.dev/pkg/logging" + "knative.dev/pkg/ptr" + "knative.dev/pkg/system" + "knative.dev/pkg/webhook" + certresources "knative.dev/pkg/webhook/certificates/resources" + "knative.dev/pkg/webhook/resourcesemantics" +) + +var errMissingNewObject = errors.New("the new object may not be nil") + +// reconciler implements the AdmissionController for resources +type reconciler struct { + name string + path string + handlers map[schema.GroupVersionKind]resourcesemantics.GenericCRD + + withContext func(context.Context) context.Context + + client kubernetes.Interface + vwhlister admissionlisters.ValidatingWebhookConfigurationLister + secretlister corelisters.SecretLister + + disallowUnknownFields bool + secretName string +} + +var _ controller.Reconciler = (*reconciler)(nil) +var _ webhook.AdmissionController = (*reconciler)(nil) + +// Reconcile implements controller.Reconciler +func (ac *reconciler) Reconcile(ctx context.Context, key string) error { + logger := logging.FromContext(ctx) + + // Look up the webhook secret, and fetch the CA cert bundle. + secret, err := ac.secretlister.Secrets(system.Namespace()).Get(ac.secretName) + if err != nil { + logger.Errorw("Error fetching secret", zap.Error(err)) + return err + } + caCert, ok := secret.Data[certresources.CACert] + if !ok { + return fmt.Errorf("secret %q is missing %q key", ac.secretName, certresources.CACert) + } + + // Reconcile the webhook configuration. + return ac.reconcileValidatingWebhook(ctx, caCert) +} + +// Path implements AdmissionController +func (ac *reconciler) Path() string { + return ac.path +} + +// Admit implements AdmissionController +func (ac *reconciler) Admit(ctx context.Context, request *admissionv1beta1.AdmissionRequest) *admissionv1beta1.AdmissionResponse { + if ac.withContext != nil { + ctx = ac.withContext(ctx) + } + + logger := logging.FromContext(ctx) + switch request.Operation { + case admissionv1beta1.Create, admissionv1beta1.Update: + default: + logger.Infof("Unhandled webhook operation, letting it through %v", request.Operation) + return &admissionv1beta1.AdmissionResponse{Allowed: true} + } + + if err := ac.validate(ctx, request); err != nil { + return webhook.MakeErrorStatus("validation failed: %v", err) + } + + return &admissionv1beta1.AdmissionResponse{Allowed: true} +} + +func (ac *reconciler) reconcileValidatingWebhook(ctx context.Context, caCert []byte) error { + logger := logging.FromContext(ctx) + + var rules []admissionregistrationv1beta1.RuleWithOperations + for gvk := range ac.handlers { + plural := strings.ToLower(inflect.Pluralize(gvk.Kind)) + + rules = append(rules, admissionregistrationv1beta1.RuleWithOperations{ + Operations: []admissionregistrationv1beta1.OperationType{ + admissionregistrationv1beta1.Create, + admissionregistrationv1beta1.Update, + }, + Rule: admissionregistrationv1beta1.Rule{ + APIGroups: []string{gvk.Group}, + APIVersions: []string{gvk.Version}, + Resources: []string{plural + "/*"}, + }, + }) + } + + // Sort the rules by Group, Version, Kind so that things are deterministically ordered. + sort.Slice(rules, func(i, j int) bool { + lhs, rhs := rules[i], rules[j] + if lhs.APIGroups[0] != rhs.APIGroups[0] { + return lhs.APIGroups[0] < rhs.APIGroups[0] + } + if lhs.APIVersions[0] != rhs.APIVersions[0] { + return lhs.APIVersions[0] < rhs.APIVersions[0] + } + return lhs.Resources[0] < rhs.Resources[0] + }) + + configuredWebhook, err := ac.vwhlister.Get(ac.name) + if err != nil { + return fmt.Errorf("error retrieving webhook: %v", err) + } + + webhook := configuredWebhook.DeepCopy() + + // Clear out any previous (bad) OwnerReferences. + // See: https://github.com/knative/serving/issues/5845 + webhook.OwnerReferences = nil + + for i, wh := range webhook.Webhooks { + if wh.Name != webhook.Name { + continue + } + webhook.Webhooks[i].Rules = rules + webhook.Webhooks[i].ClientConfig.CABundle = caCert + if webhook.Webhooks[i].ClientConfig.Service == nil { + return fmt.Errorf("missing service reference for webhook: %s", wh.Name) + } + webhook.Webhooks[i].ClientConfig.Service.Path = ptr.String(ac.Path()) + } + + if ok, err := kmp.SafeEqual(configuredWebhook, webhook); err != nil { + return fmt.Errorf("error diffing webhooks: %v", err) + } else if !ok { + logger.Info("Updating webhook") + vwhclient := ac.client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations() + if _, err := vwhclient.Update(webhook); err != nil { + return fmt.Errorf("failed to update webhook: %v", err) + } + } else { + logger.Info("Webhook is valid") + } + return nil +} + +func (ac *reconciler) validate(ctx context.Context, req *admissionv1beta1.AdmissionRequest) error { + kind := req.Kind + newBytes := req.Object.Raw + oldBytes := req.OldObject.Raw + // Why, oh why are these different types... + gvk := schema.GroupVersionKind{ + Group: kind.Group, + Version: kind.Version, + Kind: kind.Kind, + } + + logger := logging.FromContext(ctx) + handler, ok := ac.handlers[gvk] + if !ok { + logger.Errorf("Unhandled kind: %v", gvk) + return fmt.Errorf("unhandled kind: %v", gvk) + } + + // nil values denote absence of `old` (create) or `new` (delete) objects. + var oldObj, newObj resourcesemantics.GenericCRD + + if len(newBytes) != 0 { + newObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD) + newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes)) + if ac.disallowUnknownFields { + newDecoder.DisallowUnknownFields() + } + if err := newDecoder.Decode(&newObj); err != nil { + return fmt.Errorf("cannot decode incoming new object: %v", err) + } + } + if len(oldBytes) != 0 { + oldObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD) + oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes)) + if ac.disallowUnknownFields { + oldDecoder.DisallowUnknownFields() + } + if err := oldDecoder.Decode(&oldObj); err != nil { + return fmt.Errorf("cannot decode incoming old object: %v", err) + } + } + + // Set up the context for defaulting and validation + if oldObj != nil { + // TODO(mattmoor): Remove this after 0.11 cuts. + oldObj.SetDefaults(ctx) + if req.SubResource == "" { + ctx = apis.WithinUpdate(ctx, oldObj) + } else { + ctx = apis.WithinSubResourceUpdate(ctx, oldObj, req.SubResource) + } + } else { + ctx = apis.WithinCreate(ctx) + } + ctx = apis.WithUserInfo(ctx, &req.UserInfo) + + // None of the validators will accept a nil value for newObj. + if newObj == nil { + return errMissingNewObject + } + + // TODO(mattmoor): Remove this after 0.11 cuts. + newObj.SetDefaults(ctx) + + if err := validate(ctx, newObj); err != nil { + logger.Errorw("Failed the resource specific validation", zap.Error(err)) + // Return the error message as-is to give the validation callback + // discretion over (our portion of) the message that the user sees. + return err + } + + return nil +} + +// validate performs validation on the provided "new" CRD. +func validate(ctx context.Context, new apis.Validatable) error { + // Can't just `return new.Validate()` because it doesn't properly nil-check. + if err := new.Validate(ctx); err != nil { + return err + } + + return nil +} diff --git a/vendor/knative.dev/pkg/webhook/webhook.go b/vendor/knative.dev/pkg/webhook/webhook.go index b4efd796..7aaff193 100644 --- a/vendor/knative.dev/pkg/webhook/webhook.go +++ b/vendor/knative.dev/pkg/webhook/webhook.go @@ -80,7 +80,7 @@ type Webhook struct { Client kubernetes.Interface Options Options Logger *zap.SugaredLogger - admissionControllers map[string]AdmissionController + admissionControllers map[string][]AdmissionController secretlister corelisters.SecretLister } @@ -114,12 +114,9 @@ func New( } // Build up a map of paths to admission controllers for routing handlers. - acs := map[string]AdmissionController{} + acs := map[string][]AdmissionController{} for _, ac := range admissionControllers { - if _, ok := acs[ac.Path()]; ok { - return nil, fmt.Errorf("duplicate admission controller path %q", ac.Path()) - } - acs[ac.Path()] = ac + acs[ac.Path()] = append(acs[ac.Path()], ac) } return &Webhook{ @@ -212,23 +209,33 @@ func (ac *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { zap.String(logkey.UserInfo, fmt.Sprint(review.Request.UserInfo))) ctx := logging.WithLogger(r.Context(), logger) - c, ok := ac.admissionControllers[r.URL.Path] + cs, ok := ac.admissionControllers[r.URL.Path] if !ok { http.Error(w, fmt.Sprintf("no admission controller registered for: %s", r.URL.Path), http.StatusBadRequest) return } - // Where the magic happens. - reviewResponse := c.Admit(ctx, review.Request) - + // TODO(mattmoor): Remove support for multiple AdmissionControllers at + // the same path after 0.11 cuts. + // We only TEMPORARILY support multiple AdmissionControllers at the same path because of + // the issue described here: https://github.com/knative/serving/pull/5947 + // So we only support a single AdmissionController per path returning Patches. var response admissionv1beta1.AdmissionReview - if reviewResponse != nil { - response.Response = reviewResponse - response.Response.UID = review.Request.UID - } + for _, c := range cs { + reviewResponse := c.Admit(ctx, review.Request) + logger.Infof("AdmissionReview for %#v: %s/%s response=%#v", + review.Request.Kind, review.Request.Namespace, review.Request.Name, reviewResponse) - logger.Infof("AdmissionReview for %#v: %s/%s response=%#v", - review.Request.Kind, review.Request.Namespace, review.Request.Name, reviewResponse) + if !reviewResponse.Allowed { + response.Response = reviewResponse + break + } + + if reviewResponse.PatchType != nil || response.Response == nil { + response.Response = reviewResponse + } + } + response.Response.UID = review.Request.UID if err := json.NewEncoder(w).Encode(response); err != nil { http.Error(w, fmt.Sprintf("could encode response: %v", err), http.StatusInternalServerError)