mirror of https://github.com/knative/caching.git
Auto-update dependencies (#131)
Produced via: `dep ensure -update knative.dev/test-infra knative.dev/pkg` /assign n3wscott
This commit is contained in:
parent
4276980149
commit
5256f8a931
|
|
@ -933,7 +933,7 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:ff0b6b2483ef9341cc0c524f1e5bdbba37b56dde366791986e098a01de81dcac"
|
digest = "1:b1b3870d2746e5277761a3c2811d2b486db20a09763a1d98476cf82f2afbc0fc"
|
||||||
name = "knative.dev/pkg"
|
name = "knative.dev/pkg"
|
||||||
packages = [
|
packages = [
|
||||||
"apis",
|
"apis",
|
||||||
|
|
@ -952,7 +952,7 @@
|
||||||
"metrics/metricskey",
|
"metrics/metricskey",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "d3841ea2c3cb7246076d771abd5cef8b94ef9031"
|
revision = "4836f680bbe5dc615e9c822f21c6685d97c6d553"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,6 @@ type Convertible interface {
|
||||||
ConvertDown(ctx context.Context, from Convertible) error
|
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
|
// Listable indicates that a particular type can be returned via the returned
|
||||||
// list type by the API server.
|
// list type by the API server.
|
||||||
type Listable interface {
|
type Listable interface {
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto
|
||||||
metav1.GetOptions{}); err == nil {
|
metav1.GetOptions{}); err == nil {
|
||||||
cmw.Watch(logging.ConfigMapName(), logging.UpdateLevelFromConfigMap(logger, atomicLevel, component))
|
cmw.Watch(logging.ConfigMapName(), logging.UpdateLevelFromConfigMap(logger, atomicLevel, component))
|
||||||
} else if !apierrors.IsNotFound(err) {
|
} 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
|
// Watch the observability config map
|
||||||
|
|
@ -178,7 +178,7 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto
|
||||||
metrics.UpdateExporterFromConfigMap(component, logger),
|
metrics.UpdateExporterFromConfigMap(component, logger),
|
||||||
profilingHandler.UpdateFromConfigMap)
|
profilingHandler.UpdateFromConfigMap)
|
||||||
} else if !apierrors.IsNotFound(err) {
|
} 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 {
|
if err := cmw.Start(ctx.Done()); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,27 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"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.
|
const (
|
||||||
// See EnvironmentFlags for a list of supported fields.
|
// e2eMetricExporter is the name for the metrics exporter logger
|
||||||
var Flags = initializeFlags()
|
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.
|
// EnvironmentFlags define the flags that are needed to run the e2e tests.
|
||||||
type EnvironmentFlags struct {
|
type EnvironmentFlags struct {
|
||||||
|
|
@ -73,9 +89,42 @@ func initializeFlags() *EnvironmentFlags {
|
||||||
|
|
||||||
flag.StringVar(&f.Tag, "tag", "latest", "Provide the version tag for the test images.")
|
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
|
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
|
// ImagePath is a helper function to prefix image name with repo and suffix with tag
|
||||||
func ImagePath(name string) string {
|
func ImagePath(name string) string {
|
||||||
return fmt.Sprintf("%s/%s:%s", Flags.DockerRepo, name, Flags.Tag)
|
return fmt.Sprintf("%s/%s:%s", Flags.DockerRepo, name, Flags.Tag)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ type Resource struct {
|
||||||
// Check that Resource may be validated and defaulted.
|
// Check that Resource may be validated and defaulted.
|
||||||
var _ apis.Validatable = (*Resource)(nil)
|
var _ apis.Validatable = (*Resource)(nil)
|
||||||
var _ apis.Defaultable = (*Resource)(nil)
|
var _ apis.Defaultable = (*Resource)(nil)
|
||||||
var _ apis.Immutable = (*Resource)(nil)
|
|
||||||
var _ apis.Listable = (*Resource)(nil)
|
var _ apis.Listable = (*Resource)(nil)
|
||||||
|
|
||||||
// ResourceSpec represents test resource spec.
|
// ResourceSpec represents test resource spec.
|
||||||
|
|
@ -105,12 +104,7 @@ func (cs *ResourceSpec) Validate(ctx context.Context) *apis.FieldError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (current *Resource) CheckImmutableFields(ctx context.Context, og apis.Immutable) *apis.FieldError {
|
func (current *Resource) CheckImmutableFields(ctx context.Context, original *Resource) *apis.FieldError {
|
||||||
original, ok := og.(*Resource)
|
|
||||||
if !ok {
|
|
||||||
return &apis.FieldError{Message: "The provided original was not a Resource"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if original.Spec.FieldThatsImmutable != current.Spec.FieldThatsImmutable {
|
if original.Spec.FieldThatsImmutable != current.Spec.FieldThatsImmutable {
|
||||||
return &apis.FieldError{
|
return &apis.FieldError{
|
||||||
Message: "Immutable field changed",
|
Message: "Immutable field changed",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package resourcesemantics
|
package defaulting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -30,13 +30,14 @@ import (
|
||||||
"knative.dev/pkg/logging"
|
"knative.dev/pkg/logging"
|
||||||
"knative.dev/pkg/system"
|
"knative.dev/pkg/system"
|
||||||
"knative.dev/pkg/webhook"
|
"knative.dev/pkg/webhook"
|
||||||
|
"knative.dev/pkg/webhook/resourcesemantics"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewAdmissionController constructs a reconciler
|
// NewAdmissionController constructs a reconciler
|
||||||
func NewAdmissionController(
|
func NewAdmissionController(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
name, path string,
|
name, path string,
|
||||||
handlers map[schema.GroupVersionKind]GenericCRD,
|
handlers map[schema.GroupVersionKind]resourcesemantics.GenericCRD,
|
||||||
wc func(context.Context) context.Context,
|
wc func(context.Context) context.Context,
|
||||||
disallowUnknownFields bool,
|
disallowUnknownFields bool,
|
||||||
) *controller.Impl {
|
) *controller.Impl {
|
||||||
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package resourcesemantics
|
package defaulting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
@ -30,7 +30,6 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
admissionlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
|
admissionlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
|
||||||
|
|
@ -44,23 +43,16 @@ import (
|
||||||
"knative.dev/pkg/system"
|
"knative.dev/pkg/system"
|
||||||
"knative.dev/pkg/webhook"
|
"knative.dev/pkg/webhook"
|
||||||
certresources "knative.dev/pkg/webhook/certificates/resources"
|
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")
|
var errMissingNewObject = errors.New("the new object may not be nil")
|
||||||
|
|
||||||
// reconciler implements the AdmissionController for resources
|
// reconciler implements the AdmissionController for resources
|
||||||
type reconciler struct {
|
type reconciler struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
handlers map[schema.GroupVersionKind]GenericCRD
|
handlers map[schema.GroupVersionKind]resourcesemantics.GenericCRD
|
||||||
|
|
||||||
withContext func(context.Context) context.Context
|
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.
|
// nil values denote absence of `old` (create) or `new` (delete) objects.
|
||||||
var oldObj, newObj GenericCRD
|
var oldObj, newObj resourcesemantics.GenericCRD
|
||||||
|
|
||||||
if len(newBytes) != 0 {
|
if len(newBytes) != 0 {
|
||||||
newObj = handler.DeepCopyObject().(GenericCRD)
|
newObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD)
|
||||||
newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes))
|
newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes))
|
||||||
if ac.disallowUnknownFields {
|
if ac.disallowUnknownFields {
|
||||||
newDecoder.DisallowUnknownFields()
|
newDecoder.DisallowUnknownFields()
|
||||||
|
|
@ -230,7 +222,7 @@ func (ac *reconciler) mutate(ctx context.Context, req *admissionv1beta1.Admissio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(oldBytes) != 0 {
|
if len(oldBytes) != 0 {
|
||||||
oldObj = handler.DeepCopyObject().(GenericCRD)
|
oldObj = handler.DeepCopyObject().(resourcesemantics.GenericCRD)
|
||||||
oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes))
|
oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes))
|
||||||
if ac.disallowUnknownFields {
|
if ac.disallowUnknownFields {
|
||||||
oldDecoder.DisallowUnknownFields()
|
oldDecoder.DisallowUnknownFields()
|
||||||
|
|
@ -258,7 +250,7 @@ func (ac *reconciler) mutate(ctx context.Context, req *admissionv1beta1.Admissio
|
||||||
if oldObj != nil {
|
if oldObj != nil {
|
||||||
// Copy the old object and set defaults so that we don't reject our own
|
// Copy the old object and set defaults so that we don't reject our own
|
||||||
// defaulting done earlier in the webhook.
|
// defaulting done earlier in the webhook.
|
||||||
oldObj = oldObj.DeepCopyObject().(GenericCRD)
|
oldObj = oldObj.DeepCopyObject().(resourcesemantics.GenericCRD)
|
||||||
oldObj.SetDefaults(ctx)
|
oldObj.SetDefaults(ctx)
|
||||||
|
|
||||||
s, ok := oldObj.(apis.HasSpec)
|
s, ok := oldObj.(apis.HasSpec)
|
||||||
|
|
@ -293,17 +285,10 @@ func (ac *reconciler) mutate(ctx context.Context, req *admissionv1beta1.Admissio
|
||||||
if newObj == nil {
|
if newObj == nil {
|
||||||
return nil, errMissingNewObject
|
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)
|
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 {
|
if new == nil {
|
||||||
return patches, nil
|
return patches, nil
|
||||||
}
|
}
|
||||||
|
|
@ -341,33 +326,8 @@ func roundTripPatch(bytes []byte, unmarshalled interface{}) (duck.JSONPatch, err
|
||||||
return jsonpatch.CreatePatch(bytes, marshaledBytes)
|
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.
|
// 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
|
before, after := crd.DeepCopyObject(), crd
|
||||||
after.SetDefaults(ctx)
|
after.SetDefaults(ctx)
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package resourcesemantics
|
package defaulting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -80,7 +80,7 @@ type Webhook struct {
|
||||||
Client kubernetes.Interface
|
Client kubernetes.Interface
|
||||||
Options Options
|
Options Options
|
||||||
Logger *zap.SugaredLogger
|
Logger *zap.SugaredLogger
|
||||||
admissionControllers map[string]AdmissionController
|
admissionControllers map[string][]AdmissionController
|
||||||
secretlister corelisters.SecretLister
|
secretlister corelisters.SecretLister
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,12 +114,9 @@ func New(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build up a map of paths to admission controllers for routing handlers.
|
// Build up a map of paths to admission controllers for routing handlers.
|
||||||
acs := map[string]AdmissionController{}
|
acs := map[string][]AdmissionController{}
|
||||||
for _, ac := range admissionControllers {
|
for _, ac := range admissionControllers {
|
||||||
if _, ok := acs[ac.Path()]; ok {
|
acs[ac.Path()] = append(acs[ac.Path()], ac)
|
||||||
return nil, fmt.Errorf("duplicate admission controller path %q", ac.Path())
|
|
||||||
}
|
|
||||||
acs[ac.Path()] = ac
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Webhook{
|
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)))
|
zap.String(logkey.UserInfo, fmt.Sprint(review.Request.UserInfo)))
|
||||||
ctx := logging.WithLogger(r.Context(), logger)
|
ctx := logging.WithLogger(r.Context(), logger)
|
||||||
|
|
||||||
c, ok := ac.admissionControllers[r.URL.Path]
|
cs, ok := ac.admissionControllers[r.URL.Path]
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, fmt.Sprintf("no admission controller registered for: %s", r.URL.Path), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("no admission controller registered for: %s", r.URL.Path), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where the magic happens.
|
// TODO(mattmoor): Remove support for multiple AdmissionControllers at
|
||||||
reviewResponse := c.Admit(ctx, review.Request)
|
// 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
|
var response admissionv1beta1.AdmissionReview
|
||||||
if reviewResponse != nil {
|
for _, c := range cs {
|
||||||
response.Response = reviewResponse
|
reviewResponse := c.Admit(ctx, review.Request)
|
||||||
response.Response.UID = review.Request.UID
|
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",
|
if !reviewResponse.Allowed {
|
||||||
review.Request.Kind, review.Request.Namespace, review.Request.Name, reviewResponse)
|
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 {
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||||
http.Error(w, fmt.Sprintf("could encode response: %v", err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("could encode response: %v", err), http.StatusInternalServerError)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue