Auto-update dependencies (#195)

Produced via:
  `dep ensure -update knative.dev/test-infra knative.dev/pkg`
/assign n3wscott
/cc n3wscott
This commit is contained in:
Matt Moore 2020-02-04 08:14:30 -08:00 committed by GitHub
parent f07214572b
commit 5dcbd8b57e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1068 additions and 64 deletions

6
Gopkg.lock generated
View File

@ -966,7 +966,7 @@
[[projects]]
branch = "master"
digest = "1:107ee62154c011274e913c95b410fffde1c9a7ebb4e972a1428f4927a4f8afe6"
digest = "1:26325ee882afecdb033204b8f2d8e69f0d939f5bcc1a1b151233a7d78d74f7dc"
name = "knative.dev/pkg"
packages = [
"apis",
@ -985,7 +985,7 @@
"metrics/metricskey",
]
pruneopts = "T"
revision = "d5698e90e262503c568a6abc645a0f34dcec385f"
revision = "7ddd3b6a42f6c3db48561e5d9525c66b546bab01"
[[projects]]
branch = "master"
@ -996,7 +996,7 @@
"tools/dep-collector",
]
pruneopts = "UT"
revision = "25ea2a3bc5b89b01ebd564ee53bb737b2c4105e0"
revision = "7086222c769000dd2122c44de8f43bbb335ad81a"
[[projects]]
digest = "1:8730e0150dfb2b7e173890c8b9868e7a273082ef8e39f4940e3506a481cf895c"

2
vendor/knative.dev/pkg/Gopkg.lock generated vendored
View File

@ -1412,10 +1412,12 @@
"k8s.io/api/core/v1",
"k8s.io/api/extensions/v1beta1",
"k8s.io/api/rbac/v1",
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1",
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset",
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake",
"k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions",
"k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1",
"k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1",
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer",
"k8s.io/apimachinery/pkg/api/apitesting/roundtrip",
"k8s.io/apimachinery/pkg/api/equality",

View File

@ -59,7 +59,7 @@ aliases:
- vaikas
webhook-approvers:
- dprotaso
- mattmoor
- grantr
- tcnghia

View File

@ -26,7 +26,7 @@ import (
type Destination struct {
// Ref points to an Addressable.
// +optional
Ref *KnativeReference `json:"ref,omitempty"`
Ref *KReference `json:"ref,omitempty"`
// URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref.
// +optional
@ -61,9 +61,9 @@ func ValidateDestination(ctx context.Context, dest Destination) *apis.FieldError
return nil
}
// GetRef gets the KnativeReference from this Destination, if one is present. If no ref is present,
// GetRef gets the KReference from this Destination, if one is present. If no ref is present,
// then nil is returned.
func (dest *Destination) GetRef() *KnativeReference {
func (dest *Destination) GetRef() *KReference {
if dest == nil {
return nil
}

View File

@ -22,9 +22,9 @@ import (
"knative.dev/pkg/apis"
)
// KnativeReference contains enough information to refer to another object.
// KReference contains enough information to refer to another object.
// It's a trimmed down version of corev1.ObjectReference.
type KnativeReference struct {
type KReference struct {
// Kind of the referent.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
Kind string `json:"kind"`
@ -43,13 +43,12 @@ type KnativeReference struct {
APIVersion string `json:"apiVersion"`
}
func (kr *KnativeReference) Validate(ctx context.Context) *apis.FieldError {
func (kr *KReference) Validate(ctx context.Context) *apis.FieldError {
var errs *apis.FieldError
if kr == nil {
errs = errs.Also(apis.ErrMissingField("name"))
errs = errs.Also(apis.ErrMissingField("apiVersion"))
errs = errs.Also(apis.ErrMissingField("kind"))
return errs
return errs.Also(apis.ErrMissingField("name")).
Also(apis.ErrMissingField("apiVersion")).
Also(apis.ErrMissingField("kind"))
}
if kr.Name == "" {
errs = errs.Also(apis.ErrMissingField("name"))
@ -63,7 +62,7 @@ func (kr *KnativeReference) Validate(ctx context.Context) *apis.FieldError {
return errs
}
func (kr *KnativeReference) SetDefaults(ctx context.Context) {
func (kr *KReference) SetDefaults(ctx context.Context) {
if kr.Namespace == "" {
kr.Namespace = apis.ParentMeta(ctx).Namespace
}

View File

@ -177,7 +177,7 @@ func (in *Destination) DeepCopyInto(out *Destination) {
*out = *in
if in.Ref != nil {
in, out := &in.Ref, &out.Ref
*out = new(KnativeReference)
*out = new(KReference)
**out = **in
}
if in.URI != nil {
@ -198,6 +198,22 @@ func (in *Destination) DeepCopy() *Destination {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KReference) DeepCopyInto(out *KReference) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KReference.
func (in *KReference) DeepCopy() *KReference {
if in == nil {
return nil
}
out := new(KReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KResource) DeepCopyInto(out *KResource) {
*out = *in
@ -258,22 +274,6 @@ func (in *KResourceList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KnativeReference) DeepCopyInto(out *KnativeReference) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KnativeReference.
func (in *KnativeReference) DeepCopy() *KnativeReference {
if in == nil {
return nil
}
out := new(KnativeReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodSpecable) DeepCopyInto(out *PodSpecable) {
*out = *in

View File

@ -244,7 +244,7 @@ func (r *reporter) ReportReconcile(duration time.Duration, key, success string)
return err
}
metrics.Record(ctx, reconcileCountStat.M(1))
metrics.Record(ctx, reconcileLatencyStat.M(int64(duration/time.Millisecond)))
metrics.RecordBatch(ctx, reconcileCountStat.M(1),
reconcileLatencyStat.M(duration.Milliseconds()))
return nil
}

View File

@ -60,7 +60,15 @@ ${CODEGEN_PKG}/generate-groups.sh "deepcopy" \
# Depends on generate-groups.sh to install bin/deepcopy-gen
${GOPATH}/bin/deepcopy-gen --input-dirs \
knative.dev/pkg/apis,knative.dev/pkg/tracker,knative.dev/pkg/logging,knative.dev/pkg/metrics,knative.dev/pkg/testing,knative.dev/pkg/testing/duck \
$(echo \
knative.dev/pkg/apis \
knative.dev/pkg/tracker \
knative.dev/pkg/logging \
knative.dev/pkg/metrics \
knative.dev/pkg/testing \
knative.dev/pkg/testing/duck \
knative.dev/pkg/webhook/resourcesemantics/conversion/internal \
| sed "s/ /,/g") \
-O zz_generated.deepcopy \
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt

View File

@ -164,14 +164,15 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto
// Based on the reconcilers we have linked, build up the set of controllers to run.
controllers := make([]*controller.Impl, 0, len(ctors))
webhooks := make([]webhook.AdmissionController, 0)
webhooks := make([]interface{}, 0)
for _, cf := range ctors {
ctrl := cf(ctx, cmw)
controllers = append(controllers, ctrl)
// Build a list of any reconcilers that implement webhook.AdmissionController
if ac, ok := ctrl.Reconciler.(webhook.AdmissionController); ok {
webhooks = append(webhooks, ac)
switch c := ctrl.Reconciler.(type) {
case webhook.AdmissionController, webhook.ConversionController:
webhooks = append(webhooks, c)
}
}
@ -222,7 +223,7 @@ func MainWithConfig(ctx context.Context, component string, cfg *rest.Config, cto
wh, err := webhook.New(ctx, webhooks)
if err != nil {
logger.Fatalw("Failed to create admission controller", zap.Error(err))
logger.Fatalw("Failed to create webhook", zap.Error(err))
}
eg.Go(func() error {
return wh.Run(ctx.Done())

View File

@ -94,7 +94,7 @@ func CheckDistributionData(t test.T, name string, wantTags map[string]string, ex
// reported are tagged with the tags in wantTags and that wantValue matches reported last value.
func CheckLastValueData(t test.T, name string, wantTags map[string]string, wantValue float64) {
t.Helper()
if row := checkExactlyOneRow(t, name); row != nil {
if row := lastRow(t, name); row != nil {
checkRowTags(t, row, name, wantTags)
if s, ok := row.Data.(*view.LastValueData); !ok {
@ -135,6 +135,21 @@ func Unregister(names ...string) {
}
}
func lastRow(t test.T, name string) *view.Row {
t.Helper()
d, err := view.RetrieveData(name)
if err != nil {
t.Error("Reporter.Report() error", "metric", name, "error", err)
return nil
}
if len(d) < 1 {
t.Error("Reporter.Report() wrong length", "metric", name, "got", len(d), "want at least", 1)
return nil
}
return d[len(d)-1]
}
func checkExactlyOneRow(t test.T, name string) *view.Row {
t.Helper()
d, err := view.RetrieveData(name)

View File

@ -106,7 +106,7 @@ func (r *URIResolver) URIFromDestination(dest duckv1beta1.Destination, parent in
// URIFromDestinationV1 resolves a v1.Destination into a URL.
func (r *URIResolver) URIFromDestinationV1(dest duckv1.Destination, parent interface{}) (*apis.URL, error) {
if dest.Ref != nil {
url, err := r.URIFromKnativeReference(dest.Ref, parent)
url, err := r.URIFromKReference(dest.Ref, parent)
if err != nil {
return nil, err
}
@ -130,7 +130,7 @@ func (r *URIResolver) URIFromDestinationV1(dest duckv1.Destination, parent inter
return nil, errors.New("destination missing Ref and URI, expected at least one")
}
func (r *URIResolver) URIFromKnativeReference(ref *duckv1.KnativeReference, parent interface{}) (*apis.URL, error) {
func (r *URIResolver) URIFromKReference(ref *duckv1.KReference, parent interface{}) (*apis.URL, error) {
return r.URIFromObjectReference(&corev1.ObjectReference{Name: ref.Name, Namespace: ref.Namespace, APIVersion: ref.APIVersion, Kind: ref.Kind}, parent)
}

View File

@ -58,9 +58,11 @@ type Request struct {
// Addons: cluster addons to be added to cluster, such as istio
Addons []string
// EnableWorkloadIdentity: whether to enable Workload Identity -
// https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity or not
// EnableWorkloadIdentity: whether to enable Workload Identity for this cluster
EnableWorkloadIdentity bool
// ServiceAccount: service account that will be used on this cluster
ServiceAccount string
}
// DeepCopy will make a deepcopy of the request struct.
@ -77,6 +79,7 @@ func (r *Request) DeepCopy() *Request {
Zone: r.Zone,
Addons: r.Addons,
EnableWorkloadIdentity: r.EnableWorkloadIdentity,
ServiceAccount: r.ServiceAccount,
}
}
@ -146,6 +149,12 @@ func NewCreateClusterRequest(request *Request) (*container.CreateClusterRequest,
IdentityNamespace: request.Project + ".svc.id.goog",
}
}
if request.ServiceAccount != "" {
// The Google Cloud Platform Service Account to be used by the node VMs.
// If a service account is specified, the cloud-platform and userinfo.email scopes are used.
// If no Service Account is specified, the project default service account is used.
ccr.Cluster.NodePools[0].Config.ServiceAccount = request.ServiceAccount
}
// Manage the GKE cluster version. Only one of initial cluster version or release channel can be specified.
if request.ReleaseChannel != "" {

View File

@ -22,7 +22,9 @@ import (
"log"
"strings"
"github.com/davecgh/go-spew/spew"
container "google.golang.org/api/container/v1beta1"
"knative.dev/pkg/test/gke"
"knative.dev/pkg/testutils/clustermanager/e2e-tests/boskos"
"knative.dev/pkg/testutils/clustermanager/e2e-tests/common"
@ -36,7 +38,6 @@ const (
DefaultGKEZone = ""
regionEnv = "E2E_CLUSTER_REGION"
backupRegionEnv = "E2E_CLUSTER_BACKUP_REGIONS"
defaultGKEVersion = "latest"
DefaultResourceType = boskos.GKEProjectResource
ClusterRunning = "RUNNING"
@ -231,7 +232,7 @@ func (gc *GKECluster) Acquire() error {
return nil
}
// Creating cluster
log.Printf("Creating cluster %q in region %q zone %q with:\n%+v", clusterName, region, request.Zone, gc.Request)
log.Printf("Creating cluster %q in region %q zone %q with:\n%+v", clusterName, region, request.Zone, spew.Sdump(rb))
err = gc.operations.CreateCluster(gc.Project, region, request.Zone, rb)
if err == nil {
cluster, err = gc.operations.GetCluster(gc.Project, region, request.Zone, rb.Cluster.Name)

View File

@ -17,6 +17,7 @@ limitations under the License.
package pkg
import (
"flag"
"fmt"
"log"
"strings"
@ -41,6 +42,12 @@ const (
statusStopping = "STOPPING"
)
// Extra configurations we want to support for cluster creation request.
var (
enableWorkloadIdentity = flag.Bool("enable-workload-identity", false, "whether to enable Workload Identity")
serviceAccount = flag.String("service-account", "", "service account that will be used on this cluster")
)
type gkeClient struct {
ops gke.SDKOperations
}
@ -237,12 +244,14 @@ func (gc *gkeClient) createClusterWithRetries(gcpProject, name string, config Cl
addons = strings.Split(config.Addons, ",")
}
req := &gke.Request{
Project: gcpProject,
ClusterName: name,
MinNodes: config.NodeCount,
MaxNodes: config.NodeCount,
NodeType: config.NodeType,
Addons: addons,
Project: gcpProject,
ClusterName: name,
MinNodes: config.NodeCount,
MaxNodes: config.NodeCount,
NodeType: config.NodeType,
Addons: addons,
EnableWorkloadIdentity: *enableWorkloadIdentity,
ServiceAccount: *serviceAccount,
}
creq, err := gke.NewCreateClusterRequest(req)
if err != nil {

View File

@ -51,7 +51,7 @@ func main() {
case get:
_, err = actions.Get(o)
default:
err = errors.New("Must pass one of --create, --delete, --get")
err = errors.New("must pass one of --create, --delete, --get")
}
if err != nil {

View File

@ -0,0 +1,71 @@
/*
Copyright 2020 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 webhook
import (
"context"
"encoding/json"
"fmt"
"net/http"
"go.uber.org/zap"
apixv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"knative.dev/pkg/logging"
)
// ConversionController provides the interface for different conversion controllers
type ConversionController interface {
// Path returns the path that this particular conversion controller serves on.
Path() string
// Convert is the callback which is invoked when an HTTPS request comes in on Path().
Convert(context.Context, *apixv1beta1.ConversionRequest) *apixv1beta1.ConversionResponse
}
func conversionHandler(rootLogger *zap.SugaredLogger, stats StatsReporter, c ConversionController) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
logger := rootLogger
logger.Infof("Webhook ServeHTTP request=%#v", r)
var review apixv1beta1.ConversionReview
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
http.Error(w, fmt.Sprintf("could not decode body: %v", err), http.StatusBadRequest)
return
}
logger = logger.With(
zap.String("uid", string(review.Request.UID)),
zap.String("desiredAPIVersion", review.Request.DesiredAPIVersion),
)
ctx := logging.WithLogger(r.Context(), logger)
response := apixv1beta1.ConversionReview{
Response: c.Convert(ctx, review.Request),
}
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, fmt.Sprintf("could encode response: %v", err), http.StatusInternalServerError)
return
}
// TODO(dprotaso) - figure out what metrics we want reported
// if stats != nil {
// // Only report valid requests
// stats.ReportRequest(review.Request, response.Response, time.Since(ttStart))
// }
}
}

View File

@ -0,0 +1,130 @@
/*
Copyright 2020 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 conversion
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/cache"
"knative.dev/pkg/apis"
apixclient "knative.dev/pkg/client/injection/apiextensions/client"
crdinformer "knative.dev/pkg/client/injection/apiextensions/informers/apiextensions/v1beta1/customresourcedefinition"
secretinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/secret"
"knative.dev/pkg/controller"
"knative.dev/pkg/logging"
"knative.dev/pkg/system"
"knative.dev/pkg/webhook"
)
// ConvertibleObject defines the functionality our API types
// are required to implement in order to be convertible from
// one version to another
//
// Optionally if the object implements apis.Defaultable the
// ConversionController will apply defaults before returning
// the response
type ConvertibleObject interface {
// ConvertUp(ctx)
// ConvertDown(ctx)
apis.Convertible
// DeepCopyObject()
// GetObjectKind() => SetGroupVersionKind(gvk)
runtime.Object
}
// GroupKindConversion specifies how a specific Kind for a given
// group should be converted
type GroupKindConversion struct {
// DefinitionName specifies the CustomResourceDefinition that should
// be reconciled with by the controller.
//
// The conversion webhook configuration will be updated
// when the CA bundle changes
DefinitionName string
// HubVersion specifies which version of the CustomResource supports
// convertions to and from all types
//
// It is expected that the Zygotes map contains an entry for the
// specified HubVersion
HubVersion string
// Zygotes contains a map of version strings (ie. v1, v2) to empty
// ConvertibleObject objects
//
// During a conversion request these zygotes will be deep copied
// and manipulated using the apis.Convertible interface
Zygotes map[string]ConvertibleObject
}
// NewConversionController returns a K8s controller that will
// will reconcile CustomResourceDefinitions and update their
// conversion webhook attributes such as path & CA bundle.
//
// Additionally the controller's Reconciler implements
// webhook.ConversionController for the purposes of converting
// resources between different versions
func NewConversionController(
ctx context.Context,
path string,
kinds map[schema.GroupKind]GroupKindConversion,
withContext func(context.Context) context.Context,
) *controller.Impl {
logger := logging.FromContext(ctx)
secretInformer := secretinformer.Get(ctx)
crdInformer := crdinformer.Get(ctx)
client := apixclient.Get(ctx)
options := webhook.GetOptions(ctx)
r := &reconciler{
kinds: kinds,
path: path,
secretName: options.SecretName,
withContext: withContext,
client: client,
secretLister: secretInformer.Lister(),
crdLister: crdInformer.Lister(),
}
c := controller.NewImpl(r, logger, "ConversionWebhook")
// Reconciler when the named CRDs change.
for _, gkc := range kinds {
name := gkc.DefinitionName
crdInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
FilterFunc: controller.FilterWithName(name),
Handler: controller.HandleAll(c.Enqueue),
})
sentinel := c.EnqueueSentinel(types.NamespacedName{Name: name})
// Reconcile when the cert bundle changes.
secretInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
FilterFunc: controller.FilterWithNameAndNamespace(system.Namespace(), options.SecretName),
Handler: controller.HandleAll(sentinel),
})
}
return c
}

View File

@ -0,0 +1,206 @@
/*
Copyright 2020 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 conversion
import (
"context"
"encoding/json"
"fmt"
"go.uber.org/zap"
apixv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"knative.dev/pkg/apis"
"knative.dev/pkg/logging"
)
// Convert implements webhook.ConversionController
func (r *reconciler) Convert(
ctx context.Context,
req *apixv1beta1.ConversionRequest,
) *apixv1beta1.ConversionResponse {
if r.withContext != nil {
ctx = r.withContext(ctx)
}
res := &apixv1beta1.ConversionResponse{
UID: req.UID,
Result: metav1.Status{
Status: metav1.StatusSuccess,
},
}
result := make([]runtime.RawExtension, 0, len(req.Objects))
for _, obj := range req.Objects {
converted, err := r.convert(ctx, obj, req.DesiredAPIVersion)
if err != nil {
res.Result.Status = metav1.StatusFailure
res.Result.Message = err.Error()
break
}
result = append(result, converted)
}
res.ConvertedObjects = result
return res
}
func (r *reconciler) convert(
ctx context.Context,
inRaw runtime.RawExtension,
targetVersion string,
) (outRaw runtime.RawExtension, err error) {
logger := logging.FromContext(ctx)
defer func() {
if err != nil {
logger.Errorf("Conversion failed: %s", err)
}
}()
inGVK, err := parseGVK(inRaw)
if err != nil {
return
}
logger.Infof("Converting %s to version %s", formatGVK(inGVK), targetVersion)
inGK := inGVK.GroupKind()
conv, ok := r.kinds[inGK]
if !ok {
err = fmt.Errorf("no conversion support for type %s", formatGK(inGVK.GroupKind()))
return
}
outGVK, err := parseAPIVersion(targetVersion, inGK.Kind)
if err != nil {
return
}
inZygote, ok := conv.Zygotes[inGVK.Version]
if !ok {
err = fmt.Errorf("conversion not supported for type %s", formatGVK(inGVK))
return
}
outZygote, ok := conv.Zygotes[outGVK.Version]
if !ok {
err = fmt.Errorf("conversion not supported for type %s", formatGVK(outGVK))
return
}
hubZygote, ok := conv.Zygotes[conv.HubVersion]
if !ok {
inGK := inGVK.GroupKind()
err = fmt.Errorf("conversion not supported for type %s", formatGK(inGK))
return
}
in := inZygote.DeepCopyObject().(ConvertibleObject)
hub := hubZygote.DeepCopyObject().(ConvertibleObject)
out := outZygote.DeepCopyObject().(ConvertibleObject)
hubGVK := inGVK.GroupKind().WithVersion(conv.HubVersion)
logger = logger.With(
zap.String("inputType", formatGVK(inGVK)),
zap.String("outputType", formatGVK(outGVK)),
zap.String("hubType", formatGVK(hubGVK)),
)
// TODO(dprotaso) - potentially error on unknown fields
if err = json.Unmarshal(inRaw.Raw, &in); err != nil {
err = fmt.Errorf("unable to unmarshal input: %s", err)
return
}
if inGVK.Version == conv.HubVersion {
hub = in
} else if err = hub.ConvertDown(ctx, in); err != nil {
err = fmt.Errorf("conversion failed to version %s for type %s - %s", outGVK.Version, formatGVK(inGVK), err)
return
}
if outGVK.Version == conv.HubVersion {
out = hub
} else if err = hub.ConvertUp(ctx, out); err != nil {
err = fmt.Errorf("conversion failed to version %s for type %s - %s", outGVK.Version, formatGVK(inGVK), err)
return
}
out.GetObjectKind().SetGroupVersionKind(outGVK)
if defaultable, ok := out.(apis.Defaultable); ok {
defaultable.SetDefaults(ctx)
}
if outRaw.Raw, err = json.Marshal(out); err != nil {
err = fmt.Errorf("unable to marshal output: %s", err)
return
}
return
}
func parseGVK(in runtime.RawExtension) (gvk schema.GroupVersionKind, err error) {
var typeMeta metav1.TypeMeta
if err = json.Unmarshal(in.Raw, &typeMeta); err != nil {
err = fmt.Errorf("error parsing type meta %q - %s", string(in.Raw), err)
return
}
gv, err := schema.ParseGroupVersion(typeMeta.APIVersion)
gvk = gv.WithKind(typeMeta.Kind)
if gvk.Group == "" || gvk.Version == "" || gvk.Kind == "" {
err = fmt.Errorf("invalid GroupVersionKind %v", gvk)
return
}
return
}
func parseAPIVersion(apiVersion string, kind string) (schema.GroupVersionKind, error) {
gv, err := schema.ParseGroupVersion(apiVersion)
if err != nil {
err = fmt.Errorf("desired API version %q is not valid", apiVersion)
return schema.GroupVersionKind{}, err
}
if !isValidGV(gv) {
err = fmt.Errorf("desired API version %q is not valid", apiVersion)
return schema.GroupVersionKind{}, err
}
return gv.WithKind(kind), nil
}
func formatGVK(gvk schema.GroupVersionKind) string {
return fmt.Sprintf("[kind=%s group=%s version=%s]", gvk.Kind, gvk.Group, gvk.Version)
}
func formatGK(gk schema.GroupKind) string {
return fmt.Sprintf("[kind=%s group=%s]", gk.Kind, gk.Group)
}
func isValidGV(gk schema.GroupVersion) bool {
return gk.Group != "" && gk.Version != ""
}

View File

@ -0,0 +1,278 @@
/*
Copyright 2020 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 internal
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/pkg/apis"
)
const (
// Group specifies the group of the test resource
Group = "webhook.pkg.knative.dev"
// Kind specifies the kind of the test resource
Kind = "Resource"
// ErrorMarshal when assigned to the Spec.Property of the ErrorResource
// will cause json marshalling of the resource to fail
ErrorMarshal = "marshal"
// ErrorUnmarshal when assigned to the Spec.Property of the ErrorResource
// will cause json unmarshalling of the resource to fail
ErrorUnmarshal = "unmarshal"
// ErrorConvertUp when assigned to the Spec.Property of the ErrorResource
// will cause ConvertUp to fail
ErrorConvertUp = "convertUp"
// ErrorConvertDown when assigned to the Spec.Property of the ErrorResource
// will cause ConvertDown to fail
ErrorConvertDown = "convertDown"
)
type (
// V1Resource will never has a prefix or suffix on Spec.Property
// This type is used for testing conversion webhooks
//
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
V1Resource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec Spec `json:"spec"`
}
// V2Resource will always have a 'prefix/' in front of it's property
// This type is used for testing conversion webhooks
//
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
V2Resource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec Spec `json:"spec"`
}
// V3Resource will always have a '/suffix' in front of it's property
// This type is used for testing conversion webhooks
//
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
V3Resource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SpecWithDefault `json:"spec"`
}
// ErrorResource explodes in various settings depending on the property
// set. Use the Error* constants
//
//This type is used for testing conversion webhooks
//
// +k8s:deepcopy-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
ErrorResource struct {
// We embed the V1Resource as an easy way to still marshal & unmarshal
// this type without infinite loops - since we override the methods
// in order to induce failures
V1Resource `json:",inline"`
}
// Spec holds our fancy string property
Spec struct {
Property string `json:"prop"`
}
// SpecWithDefault holds two fancy string properties
SpecWithDefault struct {
Property string `json:"prop"`
NewProperty string `json:"defaulted_prop"`
}
)
var (
_ apis.Convertible = (*V1Resource)(nil)
_ apis.Convertible = (*V2Resource)(nil)
_ apis.Convertible = (*V3Resource)(nil)
_ apis.Convertible = (*ErrorResource)(nil)
_ apis.Defaultable = (*V3Resource)(nil)
)
// NewV1 returns a V1Resource with Spec.Property set
// to prop
func NewV1(prop string) *V1Resource {
return &V1Resource{
TypeMeta: metav1.TypeMeta{
Kind: Kind,
APIVersion: Group + "/v1",
},
Spec: Spec{
Property: prop,
},
}
}
// NewV2 returns a V2Resource with Spec.Property set
// to 'prefix/' + prop
func NewV2(prop string) *V2Resource {
return &V2Resource{
TypeMeta: metav1.TypeMeta{
Kind: Kind,
APIVersion: Group + "/v2",
},
Spec: Spec{
Property: fmt.Sprintf("prefix/%s", prop),
},
}
}
// NewV3 returns a V3Resource with Spec.Property set
// to prop + '/suffix'
func NewV3(prop string) *V3Resource {
v3 := &V3Resource{
TypeMeta: metav1.TypeMeta{
Kind: Kind,
APIVersion: Group + "/v3",
},
Spec: SpecWithDefault{
Property: fmt.Sprintf("%s/suffix", prop),
},
}
v3.SetDefaults(context.Background())
return v3
}
// NewErrorResource returns an ErrorResource with Spec.Property set
// to failure
func NewErrorResource(failure string) *ErrorResource {
return &ErrorResource{
V1Resource: V1Resource{
TypeMeta: metav1.TypeMeta{
Kind: Kind,
APIVersion: Group + "/error",
},
Spec: Spec{
Property: failure,
},
},
}
}
// ConvertUp implements apis.Convertible
func (r *V1Resource) ConvertUp(ctx context.Context, to apis.Convertible) error {
switch sink := to.(type) {
case *V2Resource:
sink.Spec.Property = "prefix/" + r.Spec.Property
case *V3Resource:
sink.Spec.Property = r.Spec.Property + "/suffix"
case *ErrorResource:
sink.Spec.Property = r.Spec.Property
case *V1Resource:
sink.Spec.Property = r.Spec.Property
default:
return fmt.Errorf("unsupported type %T", sink)
}
return nil
}
// ConvertDown implements apis.Convertible
func (r *V1Resource) ConvertDown(ctx context.Context, from apis.Convertible) error {
switch source := from.(type) {
case *V2Resource:
r.Spec.Property = strings.TrimPrefix(source.Spec.Property, "prefix/")
case *V3Resource:
r.Spec.Property = strings.TrimSuffix(source.Spec.Property, "/suffix")
case *ErrorResource:
r.Spec.Property = source.Spec.Property
case *V1Resource:
r.Spec.Property = source.Spec.Property
default:
return fmt.Errorf("unsupported type %T", source)
}
return nil
}
// SetDefaults implements apis.Defaultable
func (r *V3Resource) SetDefaults(ctx context.Context) {
if r.Spec.NewProperty == "" {
r.Spec.NewProperty = "defaulted"
}
}
// ConvertUp implements apis.Convertible
func (*V2Resource) ConvertUp(ctx context.Context, to apis.Convertible) error {
panic("unimplemented")
}
// ConvertDown implements apis.Convertible
func (*V2Resource) ConvertDown(ctx context.Context, from apis.Convertible) error {
panic("unimplemented")
}
// ConvertUp implements apis.Convertible
func (*V3Resource) ConvertUp(ctx context.Context, to apis.Convertible) error {
panic("unimplemented")
}
// ConvertDown implements apis.Convertible
func (*V3Resource) ConvertDown(ctx context.Context, from apis.Convertible) error {
panic("unimplemented")
}
// ConvertUp implements apis.Convertible
func (e *ErrorResource) ConvertUp(ctx context.Context, to apis.Convertible) error {
if e.Spec.Property == ErrorConvertUp {
return errors.New("boooom - convert up")
}
return e.V1Resource.ConvertUp(ctx, to)
}
// ConvertDown implements apis.Convertible
func (e *ErrorResource) ConvertDown(ctx context.Context, from apis.Convertible) error {
err := e.V1Resource.ConvertDown(ctx, from)
if err == nil && e.Spec.Property == ErrorConvertDown {
err = errors.New("boooom - convert down")
}
return err
}
// UnmarshalJSON implements json.Unmarshaler
func (e *ErrorResource) UnmarshalJSON(data []byte) (err error) {
err = json.Unmarshal(data, &e.V1Resource)
if err == nil && e.Spec.Property == ErrorUnmarshal {
err = errors.New("boooom - unmarshal json")
}
return
}
// MarshalJSON implements json.Marshaler
func (e *ErrorResource) MarshalJSON() ([]byte, error) {
if e.Spec.Property == ErrorMarshal {
return nil, errors.New("boooom - marshal json")
}
return json.Marshal(e.V1Resource)
}

View File

@ -0,0 +1,131 @@
// +build !ignore_autogenerated
/*
Copyright 2020 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package internal
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ErrorResource) DeepCopyInto(out *ErrorResource) {
*out = *in
in.V1Resource.DeepCopyInto(&out.V1Resource)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorResource.
func (in *ErrorResource) DeepCopy() *ErrorResource {
if in == nil {
return nil
}
out := new(ErrorResource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ErrorResource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *V1Resource) DeepCopyInto(out *V1Resource) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new V1Resource.
func (in *V1Resource) DeepCopy() *V1Resource {
if in == nil {
return nil
}
out := new(V1Resource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *V1Resource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *V2Resource) DeepCopyInto(out *V2Resource) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new V2Resource.
func (in *V2Resource) DeepCopy() *V2Resource {
if in == nil {
return nil
}
out := new(V2Resource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *V2Resource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *V3Resource) DeepCopyInto(out *V3Resource) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new V3Resource.
func (in *V3Resource) DeepCopy() *V3Resource {
if in == nil {
return nil
}
out := new(V3Resource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *V3Resource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,108 @@
/*
Copyright 2020 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 conversion
import (
"context"
"fmt"
apixv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apixclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apixlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
corelisters "k8s.io/client-go/listers/core/v1"
"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"
)
type reconciler struct {
kinds map[schema.GroupKind]GroupKindConversion
path string
secretName string
withContext func(context.Context) context.Context
secretLister corelisters.SecretLister
crdLister apixlisters.CustomResourceDefinitionLister
client apixclient.Interface
}
var _ webhook.ConversionController = (*reconciler)(nil)
var _ controller.Reconciler = (*reconciler)(nil)
// Path implements webhook.ConversionController
func (r *reconciler) Path() string {
return r.path
}
// Reconciler implements controller.Reconciler
func (r *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 := r.secretLister.Secrets(system.Namespace()).Get(r.secretName)
if err != nil {
logger.Errorf("Error fetching secret: %v", err)
return err
}
cacert, ok := secret.Data[certresources.CACert]
if !ok {
return fmt.Errorf("secret %q is missing %q key", r.secretName, certresources.CACert)
}
return r.reconcileCRD(ctx, cacert, key)
}
func (r *reconciler) reconcileCRD(ctx context.Context, cacert []byte, key string) error {
logger := logging.FromContext(ctx)
configuredCRD, err := r.crdLister.Get(key)
if err != nil {
return fmt.Errorf("error retrieving crd: %v", err)
}
crd := configuredCRD.DeepCopy()
if crd.Spec.Conversion == nil ||
crd.Spec.Conversion.Strategy != apixv1beta1.WebhookConverter ||
crd.Spec.Conversion.WebhookClientConfig == nil ||
crd.Spec.Conversion.WebhookClientConfig.Service == nil {
return fmt.Errorf("custom resource %q isn't configured for webhook conversion", key)
}
crd.Spec.Conversion.WebhookClientConfig.CABundle = cacert
crd.Spec.Conversion.WebhookClientConfig.Service.Path = ptr.String(r.path)
if ok, err := kmp.SafeEqual(configuredCRD, crd); err != nil {
return fmt.Errorf("error diffing custom resource definitions: %v", err)
} else if !ok {
logger.Infof("updating CRD")
crdClient := r.client.ApiextensionsV1beta1().CustomResourceDefinitions()
if _, err := crdClient.Update(crd); err != nil {
return fmt.Errorf("failed to update webhook: %v", err)
}
} else {
logger.Info("CRD is up to date")
}
return nil
}

View File

@ -101,9 +101,9 @@ func (r *reporter) ReportRequest(req *admissionv1beta1.AdmissionRequest, resp *a
return err
}
metrics.Record(ctx, requestCountM.M(1))
// Convert time.Duration in nanoseconds to milliseconds
metrics.Record(ctx, responseTimeInMsecM.M(float64(d/time.Millisecond)))
metrics.RecordBatch(ctx, requestCountM.M(1),
// Convert time.Duration in nanoseconds to milliseconds
responseTimeInMsecM.M(float64(d.Milliseconds())))
return nil
}

View File

@ -21,6 +21,7 @@ import (
"encoding/json"
"testing"
fakeapixclient "knative.dev/pkg/client/injection/apiextensions/client/fake"
fakekubeclient "knative.dev/pkg/client/injection/kube/client/fake"
fakedynamicclient "knative.dev/pkg/injection/clients/dynamicclient/fake"
@ -61,6 +62,7 @@ func MakeFactory(ctor Ctor) rtesting.Factory {
ctx = logging.WithLogger(ctx, logger)
ctx, kubeClient := fakekubeclient.With(ctx, ls.GetKubeObjects()...)
ctx, apixClient := fakeapixclient.With(ctx, ls.GetApiExtensionsObjects()...)
ctx, dynamicClient := fakedynamicclient.With(ctx,
ls.NewScheme(), ToUnstructured(t, ls.NewScheme(), r.Objects)...)
@ -85,6 +87,13 @@ func MakeFactory(ctor Ctor) rtesting.Factory {
return false, nil, nil
},
)
apixClient.PrependReactor("create", "*",
func(action ktesting.Action) (bool, runtime.Object, error) {
ca := action.(ktesting.CreateAction)
ls.IndexerFor(ca.GetObject()).Add(ca.GetObject())
return false, nil, nil
},
)
// Set up our Controller from the fakes.
c := ctor(ctx, &ls, configmap.NewStaticWatcher())
@ -93,10 +102,11 @@ func MakeFactory(ctor Ctor) rtesting.Factory {
for _, reactor := range r.WithReactors {
kubeClient.PrependReactor("*", "*", reactor)
apixClient.PrependReactor("*", "*", reactor)
dynamicClient.PrependReactor("*", "*", reactor)
}
actionRecorderList := rtesting.ActionRecorderList{dynamicClient, kubeClient}
actionRecorderList := rtesting.ActionRecorderList{dynamicClient, kubeClient, apixClient}
eventList := rtesting.EventList{Recorder: eventRecorder}
return c, actionRecorderList, eventList, statsReporter

View File

@ -21,9 +21,13 @@ import (
appsv1 "k8s.io/api/apps/v1"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
corev1 "k8s.io/api/core/v1"
apixv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apixlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
fakekubeclientset "k8s.io/client-go/kubernetes/fake"
admissionlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
fakeapix "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
appsv1listers "k8s.io/client-go/listers/apps/v1"
autoscalingv2beta1listers "k8s.io/client-go/listers/autoscaling/v2beta1"
corev1listers "k8s.io/client-go/listers/core/v1"
@ -38,6 +42,7 @@ var clientSetSchemes = []func(*runtime.Scheme) error{
autoscalingv2beta1.AddToScheme,
pkgtesting.AddToScheme,
pkgducktesting.AddToScheme,
fakeapix.AddToScheme,
}
// Listers is used to synthesize informer-style Listers from fixed lists of resources in tests.
@ -93,6 +98,11 @@ func (l *Listers) GetDuckObjects() []runtime.Object {
return l.sorter.ObjectsForSchemeFunc(pkgducktesting.AddToScheme)
}
// GetApiExtensionsObjects filters the Listers initial list of objects to types definite in k8s.io/apiextensions
func (l *Listers) GetApiExtensionsObjects() []runtime.Object {
return l.sorter.ObjectsForSchemeFunc(fakeapix.AddToScheme)
}
// GetHorizontalPodAutoscalerLister gets lister for HorizontalPodAutoscaler resources.
func (l *Listers) GetHorizontalPodAutoscalerLister() autoscalingv2beta1listers.HorizontalPodAutoscalerLister {
return autoscalingv2beta1listers.NewHorizontalPodAutoscalerLister(l.IndexerFor(&autoscalingv2beta1.HorizontalPodAutoscaler{}))
@ -137,3 +147,7 @@ func (l *Listers) GetMutatingWebhookConfigurationLister() admissionlisters.Mutat
func (l *Listers) GetValidatingWebhookConfigurationLister() admissionlisters.ValidatingWebhookConfigurationLister {
return admissionlisters.NewValidatingWebhookConfigurationLister(l.IndexerFor(&admissionregistrationv1beta1.ValidatingWebhookConfiguration{}))
}
func (l *Listers) GetCustomResourceDefinitionLister() apixlisters.CustomResourceDefinitionLister {
return apixlisters.NewCustomResourceDefinitionLister(l.IndexerFor(&apixv1beta1.CustomResourceDefinition{}))
}

View File

@ -72,7 +72,7 @@ type Webhook struct {
// New constructs a Webhook
func New(
ctx context.Context,
controllers []AdmissionController,
controllers []interface{},
) (webhook *Webhook, err error) {
// ServeMux.Handle panics on duplicate paths
@ -113,15 +113,27 @@ func New(
}
webhook.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, fmt.Sprintf("no admission controller registered for: %s", r.URL.Path), http.StatusBadRequest)
http.Error(w, fmt.Sprintf("no controller registered for: %s", r.URL.Path), http.StatusBadRequest)
})
for _, c := range controllers {
webhook.mux.Handle(
c.Path(),
admissionHandler(logger, opts.StatsReporter, c),
)
for _, controller := range controllers {
var handler http.Handler
var path string
switch c := controller.(type) {
case AdmissionController:
handler = admissionHandler(logger, opts.StatsReporter, c)
path = c.Path()
case ConversionController:
handler = conversionHandler(logger, opts.StatsReporter, c)
path = c.Path()
default:
return nil, fmt.Errorf("unknown webhook controller type: %T", controller)
}
webhook.mux.Handle(path, handler)
}
return
}