mirror of https://github.com/knative/caching.git
Auto-update dependencies (#83)
Produced via: `dep ensure -update knative.dev/test-infra knative.dev/pkg` /assign mattmoor
This commit is contained in:
parent
4362288203
commit
7fb04b4264
|
@ -927,7 +927,7 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:655572a72adfa087e24c0d8ca498a1f517ecddee1acf9768e9e89c8c3178a9e5"
|
digest = "1:bb5bc945cb10dbe8d011d5b6477576bd72ef6420a1329c5f12e1f5de1db32877"
|
||||||
name = "knative.dev/pkg"
|
name = "knative.dev/pkg"
|
||||||
packages = [
|
packages = [
|
||||||
"apis",
|
"apis",
|
||||||
|
@ -946,7 +946,7 @@
|
||||||
"metrics/metricskey",
|
"metrics/metricskey",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "73863941e2242e1c0e47bb70021c915707995b9f"
|
revision = "9118872a32f620bbf4798b4e6103094f6b7bce6c"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -957,7 +957,7 @@
|
||||||
"tools/dep-collector",
|
"tools/dep-collector",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "b639ab478b1e4882090abb51a043ba2dc9aaafcb"
|
revision = "5ce07d0fc6603e4cf576f91cdd85dcd74c4d629c"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
|
|
|
@ -240,21 +240,21 @@
|
||||||
revision = "c3068f13fcc3961fd05f96f13c8250e350db4209"
|
revision = "c3068f13fcc3961fd05f96f13c8250e350db4209"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:3efb665a5beaa0266ff287cdb58dff8a966631000e9f8ad8d832a288b90ca247"
|
digest = "1:d7736b4372fa7b6d447118aca5a9a6d90a61d4e0d467316ac631e4ba58458ae4"
|
||||||
name = "github.com/google/mako"
|
name = "github.com/google/mako"
|
||||||
packages = [
|
packages = [
|
||||||
"clients/proto/analyzers/threshold_analyzer_go_proto",
|
"clients/proto/analyzers/threshold_analyzer_go_proto",
|
||||||
"clients/proto/analyzers/utest_analyzer_go_proto",
|
"clients/proto/analyzers/utest_analyzer_go_proto",
|
||||||
"clients/proto/analyzers/window_deviation_go_proto",
|
"clients/proto/analyzers/window_deviation_go_proto",
|
||||||
"helpers/go/quickstore",
|
"go/quickstore",
|
||||||
"helpers/proto/quickstore/quickstore_go_proto",
|
|
||||||
"internal/go/common",
|
"internal/go/common",
|
||||||
"internal/quickstore_microservice/proto/quickstore_go_proto",
|
"internal/quickstore_microservice/proto/quickstore_go_proto",
|
||||||
|
"proto/quickstore/quickstore_go_proto",
|
||||||
"spec/proto/mako_go_proto",
|
"spec/proto/mako_go_proto",
|
||||||
]
|
]
|
||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
revision = "d56a6e811df75f8e4d6e7c256d25acf19ef16d03"
|
revision = "122f8dcef9e3906310e7dba05849cedb5be43b24"
|
||||||
version = "v0.0.0-rc.4"
|
version = "0.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:ab3ec1fe3e39bac4b3ab63390767766622be35b7cab03f47f787f9ec60522a53"
|
digest = "1:ab3ec1fe3e39bac4b3ab63390767766622be35b7cab03f47f787f9ec60522a53"
|
||||||
|
@ -1280,8 +1280,8 @@
|
||||||
"github.com/google/go-cmp/cmp/cmpopts",
|
"github.com/google/go-cmp/cmp/cmpopts",
|
||||||
"github.com/google/go-github/github",
|
"github.com/google/go-github/github",
|
||||||
"github.com/google/mako/clients/proto/analyzers/threshold_analyzer_go_proto",
|
"github.com/google/mako/clients/proto/analyzers/threshold_analyzer_go_proto",
|
||||||
"github.com/google/mako/helpers/go/quickstore",
|
"github.com/google/mako/go/quickstore",
|
||||||
"github.com/google/mako/helpers/proto/quickstore/quickstore_go_proto",
|
"github.com/google/mako/proto/quickstore/quickstore_go_proto",
|
||||||
"github.com/google/mako/spec/proto/mako_go_proto",
|
"github.com/google/mako/spec/proto/mako_go_proto",
|
||||||
"github.com/google/uuid",
|
"github.com/google/uuid",
|
||||||
"github.com/gorilla/websocket",
|
"github.com/gorilla/websocket",
|
||||||
|
@ -1356,7 +1356,6 @@
|
||||||
"k8s.io/client-go/kubernetes",
|
"k8s.io/client-go/kubernetes",
|
||||||
"k8s.io/client-go/kubernetes/fake",
|
"k8s.io/client-go/kubernetes/fake",
|
||||||
"k8s.io/client-go/kubernetes/scheme",
|
"k8s.io/client-go/kubernetes/scheme",
|
||||||
"k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1",
|
|
||||||
"k8s.io/client-go/kubernetes/typed/core/v1",
|
"k8s.io/client-go/kubernetes/typed/core/v1",
|
||||||
"k8s.io/client-go/plugin/pkg/client/auth/gcp",
|
"k8s.io/client-go/plugin/pkg/client/auth/gcp",
|
||||||
"k8s.io/client-go/rest",
|
"k8s.io/client-go/rest",
|
||||||
|
|
|
@ -58,6 +58,10 @@ required = [
|
||||||
name = "contrib.go.opencensus.io/exporter/stackdriver"
|
name = "contrib.go.opencensus.io/exporter/stackdriver"
|
||||||
version = "v0.12.2"
|
version = "v0.12.2"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/google/mako"
|
||||||
|
version = "v0.1.0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "knative.dev/test-infra"
|
name = "knative.dev/test-infra"
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
|
|
@ -125,6 +125,25 @@ type DestinationRuleSpec struct {
|
||||||
// One or more named sets that represent individual versions of a
|
// One or more named sets that represent individual versions of a
|
||||||
// service. Traffic policies can be overridden at subset level.
|
// service. Traffic policies can be overridden at subset level.
|
||||||
Subsets []Subset `json:"subsets,omitempty"`
|
Subsets []Subset `json:"subsets,omitempty"`
|
||||||
|
|
||||||
|
// A list of namespaces to which this destination rule is exported.
|
||||||
|
// The resolution of a destination rule to apply to a service occurs in the
|
||||||
|
// context of a hierarchy of namespaces. Exporting a destination rule allows
|
||||||
|
// it to be included in the resolution hierarchy for services in
|
||||||
|
// other namespaces. This feature provides a mechanism for service owners
|
||||||
|
// and mesh administrators to control the visibility of destination rules
|
||||||
|
// across namespace boundaries.
|
||||||
|
//
|
||||||
|
// If no namespaces are specified then the destination rule is exported to all
|
||||||
|
// namespaces by default.
|
||||||
|
//
|
||||||
|
// The value "." is reserved and defines an export to the same namespace that
|
||||||
|
// the destination rule is declared in. Similarly, the value "*" is reserved and
|
||||||
|
// defines an export to all namespaces.
|
||||||
|
//
|
||||||
|
// NOTE: in the current release, the `exportTo` value is restricted to
|
||||||
|
// "." or "*" (i.e., the current namespace or all namespaces).
|
||||||
|
ExportTo []string `json:"exportTo,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traffic policies to apply for a specific destination, across all
|
// Traffic policies to apply for a specific destination, across all
|
||||||
|
|
|
@ -141,6 +141,25 @@ type VirtualServiceSpec struct {
|
||||||
TCP []TCPRoute `json:"tcp,omitempty"`
|
TCP []TCPRoute `json:"tcp,omitempty"`
|
||||||
|
|
||||||
TLS []TLSRoute `json:"tls,omitempty"`
|
TLS []TLSRoute `json:"tls,omitempty"`
|
||||||
|
|
||||||
|
// A list of namespaces to which this destination rule is exported.
|
||||||
|
// The resolution of a destination rule to apply to a service occurs in the
|
||||||
|
// context of a hierarchy of namespaces. Exporting a destination rule allows
|
||||||
|
// it to be included in the resolution hierarchy for services in
|
||||||
|
// other namespaces. This feature provides a mechanism for service owners
|
||||||
|
// and mesh administrators to control the visibility of destination rules
|
||||||
|
// across namespace boundaries.
|
||||||
|
//
|
||||||
|
// If no namespaces are specified then the destination rule is exported to all
|
||||||
|
// namespaces by default.
|
||||||
|
//
|
||||||
|
// The value "." is reserved and defines an export to the same namespace that
|
||||||
|
// the destination rule is declared in. Similarly, the value "*" is reserved and
|
||||||
|
// defines an export to all namespaces.
|
||||||
|
//
|
||||||
|
// NOTE: in the current release, the `exportTo` value is restricted to
|
||||||
|
// "." or "*" (i.e., the current namespace or all namespaces).
|
||||||
|
ExportTo []string `json:"exportTo,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describes match conditions and actions for routing HTTP/1.1, HTTP2, and
|
// Describes match conditions and actions for routing HTTP/1.1, HTTP2, and
|
||||||
|
|
|
@ -200,6 +200,11 @@ func (in *DestinationRuleSpec) DeepCopyInto(out *DestinationRuleSpec) {
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if in.ExportTo != nil {
|
||||||
|
in, out := &in.ExportTo, &out.ExportTo
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1145,6 +1150,11 @@ func (in *VirtualServiceSpec) DeepCopyInto(out *VirtualServiceSpec) {
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if in.ExportTo != nil {
|
||||||
|
in, out := &in.ExportTo, &out.ExportTo
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,11 +70,11 @@ func (r *URIResolver) URIFromDestination(dest apisv1alpha1.Destination, parent i
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return extendPath(url, dest.Path).String(), nil
|
return extendPath(url.DeepCopy(), dest.Path).String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if dest.URI != nil {
|
if dest.URI != nil {
|
||||||
return extendPath(dest.URI, dest.Path).String(), nil
|
return extendPath(dest.URI.DeepCopy(), dest.Path).String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("destination missing ObjectReference and URI, expected exactly one")
|
return "", fmt.Errorf("destination missing ObjectReference and URI, expected exactly one")
|
||||||
|
|
|
@ -51,6 +51,7 @@ func init() {
|
||||||
contents, err := ioutil.ReadFile(configFile)
|
contents, err := ioutil.ReadFile(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to load the config file: %v", err)
|
log.Printf("Failed to load the config file: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
config := &config{}
|
config := &config{}
|
||||||
if err = yaml.Unmarshal(contents, &config); err != nil {
|
if err = yaml.Unmarshal(contents, &config); err != nil {
|
||||||
|
|
|
@ -18,6 +18,7 @@ package mako
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -25,8 +26,9 @@ import (
|
||||||
"cloud.google.com/go/compute/metadata"
|
"cloud.google.com/go/compute/metadata"
|
||||||
"knative.dev/pkg/injection/clients/kubeclient"
|
"knative.dev/pkg/injection/clients/kubeclient"
|
||||||
|
|
||||||
"github.com/google/mako/helpers/go/quickstore"
|
"github.com/google/mako/go/quickstore"
|
||||||
qpb "github.com/google/mako/helpers/proto/quickstore/quickstore_go_proto"
|
qpb "github.com/google/mako/proto/quickstore/quickstore_go_proto"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"knative.dev/pkg/changeset"
|
"knative.dev/pkg/changeset"
|
||||||
"knative.dev/pkg/controller"
|
"knative.dev/pkg/controller"
|
||||||
|
@ -69,17 +71,35 @@ func Setup(ctx context.Context, extraTags ...string) (context.Context, *quicksto
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the Kubernetes version from the API server.
|
// Get the Kubernetes version from the API server.
|
||||||
version, err := kubeclient.Get(ctx).Discovery().ServerVersion()
|
kc := kubeclient.Get(ctx)
|
||||||
|
version, err := kc.Discovery().ServerVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get GCP project ID as a tag.
|
// Determine the number of Kubernetes nodes through the kubernetes client.
|
||||||
|
nodes, err := kc.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
tags = append(tags, "nodes="+fmt.Sprintf("%d", len(nodes.Items)))
|
||||||
|
|
||||||
|
// Decorate GCP metadata as tags (when we're running on GCP).
|
||||||
if projectID, err := metadata.ProjectID(); err != nil {
|
if projectID, err := metadata.ProjectID(); err != nil {
|
||||||
log.Printf("GCP project ID is not available: %v", err)
|
log.Printf("GCP project ID is not available: %v", err)
|
||||||
} else {
|
} else {
|
||||||
tags = append(tags, "project-id="+EscapeTag(projectID))
|
tags = append(tags, "project-id="+EscapeTag(projectID))
|
||||||
}
|
}
|
||||||
|
if zone, err := metadata.Zone(); err != nil {
|
||||||
|
log.Printf("GCP zone is not available: %v", err)
|
||||||
|
} else {
|
||||||
|
tags = append(tags, "zone="+EscapeTag(zone))
|
||||||
|
}
|
||||||
|
if machineType, err := metadata.Get("instance/machine-type"); err != nil {
|
||||||
|
log.Printf("GCP machine type is not available: %v", err)
|
||||||
|
} else if parts := strings.Split(machineType, "/"); len(parts) != 4 {
|
||||||
|
tags = append(tags, "instanceType="+EscapeTag(parts[3]))
|
||||||
|
}
|
||||||
|
|
||||||
qs, qclose, err := quickstore.NewAtAddress(ctx, &qpb.QuickstoreInput{
|
qs, qclose, err := quickstore.NewAtAddress(ctx, &qpb.QuickstoreInput{
|
||||||
BenchmarkKey: MustGetBenchmark(),
|
BenchmarkKey: MustGetBenchmark(),
|
||||||
|
|
|
@ -162,7 +162,7 @@ func (a *APICoverageRecorder) GetResourceCoverage(w http.ResponseWriter, r *http
|
||||||
var ignoredFields coveragecalculator.IgnoredFields
|
var ignoredFields coveragecalculator.IgnoredFields
|
||||||
ignoredFieldsFilePath := os.Getenv("KO_DATA_PATH") + "/ignoredfields.yaml"
|
ignoredFieldsFilePath := os.Getenv("KO_DATA_PATH") + "/ignoredfields.yaml"
|
||||||
if err := ignoredFields.ReadFromFile(ignoredFieldsFilePath); err != nil {
|
if err := ignoredFields.ReadFromFile(ignoredFieldsFilePath); err != nil {
|
||||||
fmt.Fprintf(w, "Error reading file: %s", ignoredFieldsFilePath)
|
a.Logger.Errorf("Error reading file %s: %v", ignoredFieldsFilePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tree := a.ResourceForest.TopLevelTrees[resource]
|
tree := a.ResourceForest.TopLevelTrees[resource]
|
||||||
|
@ -184,7 +184,7 @@ func (a *APICoverageRecorder) GetTotalCoverage(w http.ResponseWriter, r *http.Re
|
||||||
|
|
||||||
ignoredFieldsFilePath := os.Getenv("KO_DATA_PATH") + "/ignoredfields.yaml"
|
ignoredFieldsFilePath := os.Getenv("KO_DATA_PATH") + "/ignoredfields.yaml"
|
||||||
if err = ignoredFields.ReadFromFile(ignoredFieldsFilePath); err != nil {
|
if err = ignoredFields.ReadFromFile(ignoredFieldsFilePath); err != nil {
|
||||||
fmt.Fprintf(w, "error reading file: %s error: %v", ignoredFieldsFilePath, err)
|
a.Logger.Errorf("Error reading file %s: %v", ignoredFieldsFilePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
totalCoverage := coveragecalculator.CoverageValues{}
|
totalCoverage := coveragecalculator.CoverageValues{}
|
||||||
|
@ -200,6 +200,7 @@ func (a *APICoverageRecorder) GetTotalCoverage(w http.ResponseWriter, r *http.Re
|
||||||
var body []byte
|
var body []byte
|
||||||
if body, err = json.Marshal(totalCoverage); err != nil {
|
if body, err = json.Marshal(totalCoverage); err != nil {
|
||||||
fmt.Fprintf(w, "error marshalling total coverage response: %v", err)
|
fmt.Fprintf(w, "error marshalling total coverage response: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = w.Write(body); err != nil {
|
if _, err = w.Write(body); err != nil {
|
||||||
|
|
|
@ -237,7 +237,7 @@ func BuildWebhookConfiguration(componentCommonName string, webhookName string, n
|
||||||
FailurePolicy: admissionregistrationv1beta1.Fail,
|
FailurePolicy: admissionregistrationv1beta1.Fail,
|
||||||
ClientAuth: tls.NoClientCert,
|
ClientAuth: tls.NoClientCert,
|
||||||
RegistrationDelay: time.Second * 2,
|
RegistrationDelay: time.Second * 2,
|
||||||
Port: 443,
|
Port: 8443, // Using port greater than 1024 as webhook runs as non-root user.
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
DeploymentName: componentCommonName,
|
DeploymentName: componentCommonName,
|
||||||
ServiceName: componentCommonName,
|
ServiceName: componentCommonName,
|
||||||
|
|
|
@ -0,0 +1,325 @@
|
||||||
|
/*
|
||||||
|
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 webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/markbates/inflect"
|
||||||
|
"github.com/mattbaird/jsonpatch"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
|
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
"knative.dev/pkg/apis/duck"
|
||||||
|
"knative.dev/pkg/kmp"
|
||||||
|
"knative.dev/pkg/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResourceCallback defines a signature for resource specific (Route, Configuration, etc.)
|
||||||
|
// handlers that can validate and mutate an object. If non-nil error is returned, object mutation
|
||||||
|
// is denied. Mutations should be appended to the patches operations.
|
||||||
|
type ResourceCallback func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error
|
||||||
|
|
||||||
|
// ResourceDefaulter defines a signature for resource specific (Route, Configuration, etc.)
|
||||||
|
// handlers that can set defaults on an object. If non-nil error is returned, object mutation
|
||||||
|
// is denied. Mutations should be appended to the patches operations.
|
||||||
|
type ResourceDefaulter func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceAdmissionController struct {
|
||||||
|
Handlers map[schema.GroupVersionKind]GenericCRD
|
||||||
|
Options ControllerOptions
|
||||||
|
|
||||||
|
DisallowUnknownFields bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ResourceAdmissionController) Admit(ctx context.Context, request *admissionv1beta1.AdmissionRequest) *admissionv1beta1.AdmissionResponse {
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
|
||||||
|
patchBytes, err := ac.mutate(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return makeErrorStatus("mutation failed: %v", err)
|
||||||
|
}
|
||||||
|
logger.Infof("Kind: %q PatchBytes: %v", request.Kind, string(patchBytes))
|
||||||
|
|
||||||
|
return &admissionv1beta1.AdmissionResponse{
|
||||||
|
Patch: patchBytes,
|
||||||
|
Allowed: true,
|
||||||
|
PatchType: func() *admissionv1beta1.PatchType {
|
||||||
|
pt := admissionv1beta1.PatchTypeJSONPatch
|
||||||
|
return &pt
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ResourceAdmissionController) Register(ctx context.Context, kubeClient kubernetes.Interface, caCert []byte) error {
|
||||||
|
client := kubeClient.AdmissionregistrationV1beta1().MutatingWebhookConfigurations()
|
||||||
|
logger := logging.FromContext(ctx)
|
||||||
|
failurePolicy := admissionregistrationv1beta1.Fail
|
||||||
|
|
||||||
|
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]
|
||||||
|
})
|
||||||
|
|
||||||
|
webhook := &admissionregistrationv1beta1.MutatingWebhookConfiguration{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: ac.Options.WebhookName,
|
||||||
|
},
|
||||||
|
Webhooks: []admissionregistrationv1beta1.Webhook{{
|
||||||
|
Name: ac.Options.WebhookName,
|
||||||
|
Rules: rules,
|
||||||
|
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
|
||||||
|
Service: &admissionregistrationv1beta1.ServiceReference{
|
||||||
|
Namespace: ac.Options.Namespace,
|
||||||
|
Name: ac.Options.ServiceName,
|
||||||
|
},
|
||||||
|
CABundle: caCert,
|
||||||
|
},
|
||||||
|
FailurePolicy: &failurePolicy,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the owner to our deployment.
|
||||||
|
deployment, err := kubeClient.Apps().Deployments(ac.Options.Namespace).Get(ac.Options.DeploymentName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch our deployment: %v", err)
|
||||||
|
}
|
||||||
|
deploymentRef := metav1.NewControllerRef(deployment, deploymentKind)
|
||||||
|
webhook.OwnerReferences = append(webhook.OwnerReferences, *deploymentRef)
|
||||||
|
|
||||||
|
// Try to create the webhook and if it already exists validate webhook rules.
|
||||||
|
_, err = client.Create(webhook)
|
||||||
|
if err != nil {
|
||||||
|
if !apierrors.IsAlreadyExists(err) {
|
||||||
|
return fmt.Errorf("failed to create a webhook: %v", err)
|
||||||
|
}
|
||||||
|
logger.Info("Webhook already exists")
|
||||||
|
configuredWebhook, err := client.Get(ac.Options.WebhookName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error retrieving webhook: %v", err)
|
||||||
|
}
|
||||||
|
if ok, err := kmp.SafeEqual(configuredWebhook.Webhooks, webhook.Webhooks); err != nil {
|
||||||
|
return fmt.Errorf("error diffing webhooks: %v", err)
|
||||||
|
} else if !ok {
|
||||||
|
logger.Info("Updating webhook")
|
||||||
|
// Set the ResourceVersion as required by update.
|
||||||
|
webhook.ObjectMeta.ResourceVersion = configuredWebhook.ObjectMeta.ResourceVersion
|
||||||
|
if _, err := client.Update(webhook); err != nil {
|
||||||
|
return fmt.Errorf("failed to update webhook: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Info("Webhook is already valid")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Info("Created a webhook")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ResourceAdmissionController) mutate(ctx context.Context, req *admissionv1beta1.AdmissionRequest) ([]byte, 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 nil, fmt.Errorf("unhandled kind: %v", gvk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nil values denote absence of `old` (create) or `new` (delete) objects.
|
||||||
|
var oldObj, newObj GenericCRD
|
||||||
|
|
||||||
|
if len(newBytes) != 0 {
|
||||||
|
newObj = handler.DeepCopyObject().(GenericCRD)
|
||||||
|
newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes))
|
||||||
|
if ac.DisallowUnknownFields {
|
||||||
|
newDecoder.DisallowUnknownFields()
|
||||||
|
}
|
||||||
|
if err := newDecoder.Decode(&newObj); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot decode incoming new object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(oldBytes) != 0 {
|
||||||
|
oldObj = handler.DeepCopyObject().(GenericCRD)
|
||||||
|
oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes))
|
||||||
|
if ac.DisallowUnknownFields {
|
||||||
|
oldDecoder.DisallowUnknownFields()
|
||||||
|
}
|
||||||
|
if err := oldDecoder.Decode(&oldObj); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot decode incoming old object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var patches duck.JSONPatch
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// Skip this step if the type we're dealing with is a duck type, since it is inherently
|
||||||
|
// incomplete and this will patch away all of the unspecified fields.
|
||||||
|
if _, ok := newObj.(duck.Populatable); !ok {
|
||||||
|
// Add these before defaulting fields, otherwise defaulting may cause an illegal patch
|
||||||
|
// because it expects the round tripped through Golang fields to be present already.
|
||||||
|
rtp, err := roundTripPatch(newBytes, newObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot create patch for round tripped newBytes: %v", err)
|
||||||
|
}
|
||||||
|
patches = append(patches, rtp...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the context for defaulting and validation
|
||||||
|
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.SetDefaults(ctx)
|
||||||
|
|
||||||
|
s, ok := oldObj.(apis.HasSpec)
|
||||||
|
if ok {
|
||||||
|
SetUserInfoAnnotations(s, ctx, req.Resource.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Default the new object.
|
||||||
|
if patches, err = setDefaults(ctx, patches, newObj); err != nil {
|
||||||
|
logger.Errorw("Failed the resource specific defaulter", zap.Error(err))
|
||||||
|
// Return the error message as-is to give the defaulter callback
|
||||||
|
// discretion over (our portion of) the message that the user sees.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if patches, err = ac.setUserInfoAnnotations(ctx, patches, newObj, req.Resource.Group); err != nil {
|
||||||
|
logger.Errorw("Failed the resource user info annotator", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// None of the validators will accept a nil value for newObj.
|
||||||
|
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 *ResourceAdmissionController) setUserInfoAnnotations(ctx context.Context, patches duck.JSONPatch, new GenericCRD, groupName string) (duck.JSONPatch, error) {
|
||||||
|
if new == nil {
|
||||||
|
return patches, nil
|
||||||
|
}
|
||||||
|
nh, ok := new.(apis.HasSpec)
|
||||||
|
if !ok {
|
||||||
|
return patches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, a := new.DeepCopyObject().(apis.HasSpec), nh
|
||||||
|
|
||||||
|
SetUserInfoAnnotations(nh, ctx, groupName)
|
||||||
|
|
||||||
|
patch, err := duck.CreatePatch(b, a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return append(patches, patch...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// roundTripPatch generates the JSONPatch that corresponds to round tripping the given bytes through
|
||||||
|
// the Golang type (JSON -> Golang type -> JSON). Because it is not always true that
|
||||||
|
// bytes == json.Marshal(json.Unmarshal(bytes)).
|
||||||
|
//
|
||||||
|
// For example, if bytes did not contain a 'spec' field and the Golang type specifies its 'spec'
|
||||||
|
// field without omitempty, then by round tripping through the Golang type, we would have added
|
||||||
|
// `'spec': {}`.
|
||||||
|
func roundTripPatch(bytes []byte, unmarshalled interface{}) (duck.JSONPatch, error) {
|
||||||
|
if unmarshalled == nil {
|
||||||
|
return duck.JSONPatch{}, nil
|
||||||
|
}
|
||||||
|
marshaledBytes, err := json.Marshal(unmarshalled)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot marshal interface: %v", err)
|
||||||
|
}
|
||||||
|
return jsonpatch.CreatePatch(bytes, marshaledBytes)
|
||||||
|
}
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
package webhook
|
package webhook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -25,30 +24,22 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"knative.dev/pkg/apis"
|
"knative.dev/pkg/apis"
|
||||||
"knative.dev/pkg/apis/duck"
|
"knative.dev/pkg/apis/duck"
|
||||||
"knative.dev/pkg/kmp"
|
|
||||||
"knative.dev/pkg/logging"
|
"knative.dev/pkg/logging"
|
||||||
"knative.dev/pkg/logging/logkey"
|
"knative.dev/pkg/logging/logkey"
|
||||||
|
|
||||||
"github.com/markbates/inflect"
|
|
||||||
"github.com/mattbaird/jsonpatch"
|
|
||||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"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"
|
||||||
clientadmissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -105,34 +96,15 @@ type ControllerOptions struct {
|
||||||
StatsReporter StatsReporter
|
StatsReporter StatsReporter
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceCallback defines a signature for resource specific (Route, Configuration, etc.)
|
|
||||||
// handlers that can validate and mutate an object. If non-nil error is returned, object creation
|
|
||||||
// is denied. Mutations should be appended to the patches operations.
|
|
||||||
type ResourceCallback func(patches *[]jsonpatch.JsonPatchOperation, old GenericCRD, new GenericCRD) error
|
|
||||||
|
|
||||||
// ResourceDefaulter defines a signature for resource specific (Route, Configuration, etc.)
|
|
||||||
// handlers that can set defaults on an object. If non-nil error is returned, object creation
|
|
||||||
// is denied. Mutations should be appended to the patches operations.
|
|
||||||
type ResourceDefaulter func(patches *[]jsonpatch.JsonPatchOperation, crd GenericCRD) error
|
|
||||||
|
|
||||||
// AdmissionController implements the external admission webhook for validation of
|
// AdmissionController implements the external admission webhook for validation of
|
||||||
// pilot configuration.
|
// pilot configuration.
|
||||||
type AdmissionController struct {
|
type AdmissionController struct {
|
||||||
Client kubernetes.Interface
|
Client kubernetes.Interface
|
||||||
Options ControllerOptions
|
Options ControllerOptions
|
||||||
Handlers map[schema.GroupVersionKind]GenericCRD
|
Logger *zap.SugaredLogger
|
||||||
Logger *zap.SugaredLogger
|
resourceAdmissionController ResourceAdmissionController
|
||||||
|
|
||||||
WithContext func(context.Context) context.Context
|
WithContext func(context.Context) context.Context
|
||||||
DisallowUnknownFields bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdmissionController constructs an AdmissionController
|
// NewAdmissionController constructs an AdmissionController
|
||||||
|
@ -153,12 +125,15 @@ func NewAdmissionController(
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AdmissionController{
|
return &AdmissionController{
|
||||||
Client: client,
|
Client: client,
|
||||||
Options: opts,
|
Options: opts,
|
||||||
Handlers: handlers,
|
resourceAdmissionController: ResourceAdmissionController{
|
||||||
Logger: logger,
|
Handlers: handlers,
|
||||||
WithContext: ctx,
|
Options: opts,
|
||||||
DisallowUnknownFields: disallowUnknownFields,
|
DisallowUnknownFields: disallowUnknownFields,
|
||||||
|
},
|
||||||
|
Logger: logger,
|
||||||
|
WithContext: ctx,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +213,7 @@ func getOrGenerateKeyCertsFromSecret(ctx context.Context, client kubernetes.Inte
|
||||||
// validate performs validation on the provided "new" CRD.
|
// validate performs validation on the provided "new" CRD.
|
||||||
// For legacy purposes, this also does apis.Immutable validation,
|
// For legacy purposes, this also does apis.Immutable validation,
|
||||||
// which is deprecated and will be removed in a future release.
|
// which is deprecated and will be removed in a future release.
|
||||||
func validate(ctx context.Context, new GenericCRD) error {
|
func validate(ctx context.Context, new apis.Validatable) error {
|
||||||
if apis.IsInUpdate(ctx) {
|
if apis.IsInUpdate(ctx) {
|
||||||
old := apis.GetBaseline(ctx)
|
old := apis.GetBaseline(ctx)
|
||||||
if immutableNew, ok := new.(apis.Immutable); ok {
|
if immutableNew, ok := new.(apis.Immutable); ok {
|
||||||
|
@ -317,8 +292,7 @@ func (ac *AdmissionController) Run(stop <-chan struct{}) error {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(ac.Options.RegistrationDelay):
|
case <-time.After(ac.Options.RegistrationDelay):
|
||||||
cl := ac.Client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations()
|
if err := ac.resourceAdmissionController.Register(ctx, ac.Client, caCert); err != nil {
|
||||||
if err := ac.register(ctx, cl, caCert); err != nil {
|
|
||||||
logger.Errorw("failed to register webhook", zap.Error(err))
|
logger.Errorw("failed to register webhook", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -343,97 +317,6 @@ func (ac *AdmissionController) Run(stop <-chan struct{}) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register registers the external admission webhook for pilot
|
|
||||||
// configuration types.
|
|
||||||
func (ac *AdmissionController) register(
|
|
||||||
ctx context.Context, client clientadmissionregistrationv1beta1.MutatingWebhookConfigurationInterface, caCert []byte) error { // nolint: lll
|
|
||||||
logger := logging.FromContext(ctx)
|
|
||||||
failurePolicy := admissionregistrationv1beta1.Fail
|
|
||||||
|
|
||||||
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]
|
|
||||||
})
|
|
||||||
|
|
||||||
webhook := &admissionregistrationv1beta1.MutatingWebhookConfiguration{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: ac.Options.WebhookName,
|
|
||||||
},
|
|
||||||
Webhooks: []admissionregistrationv1beta1.Webhook{{
|
|
||||||
Name: ac.Options.WebhookName,
|
|
||||||
Rules: rules,
|
|
||||||
ClientConfig: admissionregistrationv1beta1.WebhookClientConfig{
|
|
||||||
Service: &admissionregistrationv1beta1.ServiceReference{
|
|
||||||
Namespace: ac.Options.Namespace,
|
|
||||||
Name: ac.Options.ServiceName,
|
|
||||||
},
|
|
||||||
CABundle: caCert,
|
|
||||||
},
|
|
||||||
FailurePolicy: &failurePolicy,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the owner to our deployment.
|
|
||||||
deployment, err := ac.Client.Apps().Deployments(ac.Options.Namespace).Get(ac.Options.DeploymentName, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to fetch our deployment: %v", err)
|
|
||||||
}
|
|
||||||
deploymentRef := metav1.NewControllerRef(deployment, deploymentKind)
|
|
||||||
webhook.OwnerReferences = append(webhook.OwnerReferences, *deploymentRef)
|
|
||||||
|
|
||||||
// Try to create the webhook and if it already exists validate webhook rules.
|
|
||||||
_, err = client.Create(webhook)
|
|
||||||
if err != nil {
|
|
||||||
if !apierrors.IsAlreadyExists(err) {
|
|
||||||
return fmt.Errorf("failed to create a webhook: %v", err)
|
|
||||||
}
|
|
||||||
logger.Info("Webhook already exists")
|
|
||||||
configuredWebhook, err := client.Get(ac.Options.WebhookName, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error retrieving webhook: %v", err)
|
|
||||||
}
|
|
||||||
if ok, err := kmp.SafeEqual(configuredWebhook.Webhooks, webhook.Webhooks); err != nil {
|
|
||||||
return fmt.Errorf("error diffing webhooks: %v", err)
|
|
||||||
} else if !ok {
|
|
||||||
logger.Info("Updating webhook")
|
|
||||||
// Set the ResourceVersion as required by update.
|
|
||||||
webhook.ObjectMeta.ResourceVersion = configuredWebhook.ObjectMeta.ResourceVersion
|
|
||||||
if _, err := client.Update(webhook); err != nil {
|
|
||||||
return fmt.Errorf("failed to update webhook: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Info("Webhook is already valid")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Info("Created a webhook")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP implements the external admission webhook for mutating
|
// ServeHTTP implements the external admission webhook for mutating
|
||||||
// serving resources.
|
// serving resources.
|
||||||
func (ac *AdmissionController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (ac *AdmissionController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -468,7 +351,7 @@ func (ac *AdmissionController) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||||
ctx = ac.WithContext(ctx)
|
ctx = ac.WithContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewResponse := ac.admit(ctx, review.Request)
|
reviewResponse := ac.resourceAdmissionController.Admit(ctx, review.Request)
|
||||||
var response admissionv1beta1.AdmissionReview
|
var response admissionv1beta1.AdmissionReview
|
||||||
if reviewResponse != nil {
|
if reviewResponse != nil {
|
||||||
response.Response = reviewResponse
|
response.Response = reviewResponse
|
||||||
|
@ -497,174 +380,6 @@ func makeErrorStatus(reason string, args ...interface{}) *admissionv1beta1.Admis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AdmissionController) admit(ctx context.Context, request *admissionv1beta1.AdmissionRequest) *admissionv1beta1.AdmissionResponse {
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
|
|
||||||
patchBytes, err := ac.mutate(ctx, request)
|
|
||||||
if err != nil {
|
|
||||||
return makeErrorStatus("mutation failed: %v", err)
|
|
||||||
}
|
|
||||||
logger.Infof("Kind: %q PatchBytes: %v", request.Kind, string(patchBytes))
|
|
||||||
|
|
||||||
return &admissionv1beta1.AdmissionResponse{
|
|
||||||
Patch: patchBytes,
|
|
||||||
Allowed: true,
|
|
||||||
PatchType: func() *admissionv1beta1.PatchType {
|
|
||||||
pt := admissionv1beta1.PatchTypeJSONPatch
|
|
||||||
return &pt
|
|
||||||
}(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ac *AdmissionController) mutate(ctx context.Context, req *admissionv1beta1.AdmissionRequest) ([]byte, 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 nil, fmt.Errorf("unhandled kind: %v", gvk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nil values denote absence of `old` (create) or `new` (delete) objects.
|
|
||||||
var oldObj, newObj GenericCRD
|
|
||||||
|
|
||||||
if len(newBytes) != 0 {
|
|
||||||
newObj = handler.DeepCopyObject().(GenericCRD)
|
|
||||||
newDecoder := json.NewDecoder(bytes.NewBuffer(newBytes))
|
|
||||||
if ac.DisallowUnknownFields {
|
|
||||||
newDecoder.DisallowUnknownFields()
|
|
||||||
}
|
|
||||||
if err := newDecoder.Decode(&newObj); err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot decode incoming new object: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(oldBytes) != 0 {
|
|
||||||
oldObj = handler.DeepCopyObject().(GenericCRD)
|
|
||||||
oldDecoder := json.NewDecoder(bytes.NewBuffer(oldBytes))
|
|
||||||
if ac.DisallowUnknownFields {
|
|
||||||
oldDecoder.DisallowUnknownFields()
|
|
||||||
}
|
|
||||||
if err := oldDecoder.Decode(&oldObj); err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot decode incoming old object: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var patches duck.JSONPatch
|
|
||||||
|
|
||||||
var err error
|
|
||||||
// Skip this step if the type we're dealing with is a duck type, since it is inherently
|
|
||||||
// incomplete and this will patch away all of the unspecified fields.
|
|
||||||
if _, ok := newObj.(duck.Populatable); !ok {
|
|
||||||
// Add these before defaulting fields, otherwise defaulting may cause an illegal patch
|
|
||||||
// because it expects the round tripped through Golang fields to be present already.
|
|
||||||
rtp, err := roundTripPatch(newBytes, newObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot create patch for round tripped newBytes: %v", err)
|
|
||||||
}
|
|
||||||
patches = append(patches, rtp...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the context for defaulting and validation
|
|
||||||
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.SetDefaults(ctx)
|
|
||||||
|
|
||||||
s, ok := oldObj.(apis.HasSpec)
|
|
||||||
if ok {
|
|
||||||
SetUserInfoAnnotations(s, ctx, req.Resource.Group)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
// Default the new object.
|
|
||||||
if patches, err = setDefaults(ctx, patches, newObj); err != nil {
|
|
||||||
logger.Errorw("Failed the resource specific defaulter", zap.Error(err))
|
|
||||||
// Return the error message as-is to give the defaulter callback
|
|
||||||
// discretion over (our portion of) the message that the user sees.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if patches, err = ac.setUserInfoAnnotations(ctx, patches, newObj, req.Resource.Group); err != nil {
|
|
||||||
logger.Errorw("Failed the resource user info annotator", zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// None of the validators will accept a nil value for newObj.
|
|
||||||
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 *AdmissionController) setUserInfoAnnotations(ctx context.Context, patches duck.JSONPatch, new GenericCRD, groupName string) (duck.JSONPatch, error) {
|
|
||||||
if new == nil {
|
|
||||||
return patches, nil
|
|
||||||
}
|
|
||||||
nh, ok := new.(apis.HasSpec)
|
|
||||||
if !ok {
|
|
||||||
return patches, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b, a := new.DeepCopyObject().(apis.HasSpec), nh
|
|
||||||
|
|
||||||
SetUserInfoAnnotations(nh, ctx, groupName)
|
|
||||||
|
|
||||||
patch, err := duck.CreatePatch(b, a)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return append(patches, patch...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// roundTripPatch generates the JSONPatch that corresponds to round tripping the given bytes through
|
|
||||||
// the Golang type (JSON -> Golang type -> JSON). Because it is not always true that
|
|
||||||
// bytes == json.Marshal(json.Unmarshal(bytes)).
|
|
||||||
//
|
|
||||||
// For example, if bytes did not contain a 'spec' field and the Golang type specifies its 'spec'
|
|
||||||
// field without omitempty, then by round tripping through the Golang type, we would have added
|
|
||||||
// `'spec': {}`.
|
|
||||||
func roundTripPatch(bytes []byte, unmarshalled interface{}) (duck.JSONPatch, error) {
|
|
||||||
if unmarshalled == nil {
|
|
||||||
return duck.JSONPatch{}, nil
|
|
||||||
}
|
|
||||||
marshaledBytes, err := json.Marshal(unmarshalled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot marshal interface: %v", err)
|
|
||||||
}
|
|
||||||
return jsonpatch.CreatePatch(bytes, marshaledBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateSecret(ctx context.Context, options *ControllerOptions) (*corev1.Secret, error) {
|
func generateSecret(ctx context.Context, options *ControllerOptions) (*corev1.Secret, error) {
|
||||||
serverKey, serverCert, caCert, err := CreateCerts(ctx, options.ServiceName, options.Namespace)
|
serverKey, serverCert, caCert, err := CreateCerts(ctx, options.ServiceName, options.Namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue