diff --git a/Gopkg.lock b/Gopkg.lock index 8e864470..a245213d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -927,7 +927,7 @@ [[projects]] branch = "master" - digest = "1:655572a72adfa087e24c0d8ca498a1f517ecddee1acf9768e9e89c8c3178a9e5" + digest = "1:bb5bc945cb10dbe8d011d5b6477576bd72ef6420a1329c5f12e1f5de1db32877" name = "knative.dev/pkg" packages = [ "apis", @@ -946,7 +946,7 @@ "metrics/metricskey", ] pruneopts = "T" - revision = "73863941e2242e1c0e47bb70021c915707995b9f" + revision = "9118872a32f620bbf4798b4e6103094f6b7bce6c" [[projects]] branch = "master" @@ -957,7 +957,7 @@ "tools/dep-collector", ] pruneopts = "UT" - revision = "b639ab478b1e4882090abb51a043ba2dc9aaafcb" + revision = "5ce07d0fc6603e4cf576f91cdd85dcd74c4d629c" [solve-meta] analyzer-name = "dep" diff --git a/vendor/knative.dev/pkg/Gopkg.lock b/vendor/knative.dev/pkg/Gopkg.lock index 126cae9d..e11a0767 100644 --- a/vendor/knative.dev/pkg/Gopkg.lock +++ b/vendor/knative.dev/pkg/Gopkg.lock @@ -240,21 +240,21 @@ revision = "c3068f13fcc3961fd05f96f13c8250e350db4209" [[projects]] - digest = "1:3efb665a5beaa0266ff287cdb58dff8a966631000e9f8ad8d832a288b90ca247" + digest = "1:d7736b4372fa7b6d447118aca5a9a6d90a61d4e0d467316ac631e4ba58458ae4" name = "github.com/google/mako" packages = [ "clients/proto/analyzers/threshold_analyzer_go_proto", "clients/proto/analyzers/utest_analyzer_go_proto", "clients/proto/analyzers/window_deviation_go_proto", - "helpers/go/quickstore", - "helpers/proto/quickstore/quickstore_go_proto", + "go/quickstore", "internal/go/common", "internal/quickstore_microservice/proto/quickstore_go_proto", + "proto/quickstore/quickstore_go_proto", "spec/proto/mako_go_proto", ] pruneopts = "NUT" - revision = "d56a6e811df75f8e4d6e7c256d25acf19ef16d03" - version = "v0.0.0-rc.4" + revision = "122f8dcef9e3906310e7dba05849cedb5be43b24" + version = "0.1.0" [[projects]] digest = "1:ab3ec1fe3e39bac4b3ab63390767766622be35b7cab03f47f787f9ec60522a53" @@ -1280,8 +1280,8 @@ "github.com/google/go-cmp/cmp/cmpopts", "github.com/google/go-github/github", "github.com/google/mako/clients/proto/analyzers/threshold_analyzer_go_proto", - "github.com/google/mako/helpers/go/quickstore", - "github.com/google/mako/helpers/proto/quickstore/quickstore_go_proto", + "github.com/google/mako/go/quickstore", + "github.com/google/mako/proto/quickstore/quickstore_go_proto", "github.com/google/mako/spec/proto/mako_go_proto", "github.com/google/uuid", "github.com/gorilla/websocket", @@ -1356,7 +1356,6 @@ "k8s.io/client-go/kubernetes", "k8s.io/client-go/kubernetes/fake", "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/plugin/pkg/client/auth/gcp", "k8s.io/client-go/rest", diff --git a/vendor/knative.dev/pkg/Gopkg.toml b/vendor/knative.dev/pkg/Gopkg.toml index 8cd92c45..72d2f47a 100644 --- a/vendor/knative.dev/pkg/Gopkg.toml +++ b/vendor/knative.dev/pkg/Gopkg.toml @@ -58,6 +58,10 @@ required = [ name = "contrib.go.opencensus.io/exporter/stackdriver" version = "v0.12.2" +[[constraint]] + name = "github.com/google/mako" + version = "v0.1.0" + [[constraint]] name = "knative.dev/test-infra" branch = "master" diff --git a/vendor/knative.dev/pkg/apis/istio/v1alpha3/destinationrule_types.go b/vendor/knative.dev/pkg/apis/istio/v1alpha3/destinationrule_types.go index 54a43b35..820a85b6 100644 --- a/vendor/knative.dev/pkg/apis/istio/v1alpha3/destinationrule_types.go +++ b/vendor/knative.dev/pkg/apis/istio/v1alpha3/destinationrule_types.go @@ -125,6 +125,25 @@ type DestinationRuleSpec struct { // One or more named sets that represent individual versions of a // service. Traffic policies can be overridden at subset level. 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 diff --git a/vendor/knative.dev/pkg/apis/istio/v1alpha3/virtualservice_types.go b/vendor/knative.dev/pkg/apis/istio/v1alpha3/virtualservice_types.go index adb41d9e..65d83f50 100644 --- a/vendor/knative.dev/pkg/apis/istio/v1alpha3/virtualservice_types.go +++ b/vendor/knative.dev/pkg/apis/istio/v1alpha3/virtualservice_types.go @@ -141,6 +141,25 @@ type VirtualServiceSpec struct { TCP []TCPRoute `json:"tcp,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 diff --git a/vendor/knative.dev/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go b/vendor/knative.dev/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go index b503c307..24e3fa6a 100644 --- a/vendor/knative.dev/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go +++ b/vendor/knative.dev/pkg/apis/istio/v1alpha3/zz_generated.deepcopy.go @@ -200,6 +200,11 @@ func (in *DestinationRuleSpec) DeepCopyInto(out *DestinationRuleSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ExportTo != nil { + in, out := &in.ExportTo, &out.ExportTo + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -1145,6 +1150,11 @@ func (in *VirtualServiceSpec) DeepCopyInto(out *VirtualServiceSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ExportTo != nil { + in, out := &in.ExportTo, &out.ExportTo + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/vendor/knative.dev/pkg/resolver/addressable_resolver.go b/vendor/knative.dev/pkg/resolver/addressable_resolver.go index 790fb50e..5bb27f4b 100644 --- a/vendor/knative.dev/pkg/resolver/addressable_resolver.go +++ b/vendor/knative.dev/pkg/resolver/addressable_resolver.go @@ -70,11 +70,11 @@ func (r *URIResolver) URIFromDestination(dest apisv1alpha1.Destination, parent i if err != nil { return "", err } - return extendPath(url, dest.Path).String(), nil + return extendPath(url.DeepCopy(), dest.Path).String(), 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") diff --git a/vendor/knative.dev/pkg/test/mako/alerter/slack/config.go b/vendor/knative.dev/pkg/test/mako/alerter/slack/config.go index cab27035..69cc406c 100644 --- a/vendor/knative.dev/pkg/test/mako/alerter/slack/config.go +++ b/vendor/knative.dev/pkg/test/mako/alerter/slack/config.go @@ -51,6 +51,7 @@ func init() { contents, err := ioutil.ReadFile(configFile) if err != nil { log.Printf("Failed to load the config file: %v", err) + return } config := &config{} if err = yaml.Unmarshal(contents, &config); err != nil { diff --git a/vendor/knative.dev/pkg/test/mako/sidecar.go b/vendor/knative.dev/pkg/test/mako/sidecar.go index cbb0fdff..3e49b14b 100644 --- a/vendor/knative.dev/pkg/test/mako/sidecar.go +++ b/vendor/knative.dev/pkg/test/mako/sidecar.go @@ -18,6 +18,7 @@ package mako import ( "context" + "fmt" "log" "runtime" "strings" @@ -25,8 +26,9 @@ import ( "cloud.google.com/go/compute/metadata" "knative.dev/pkg/injection/clients/kubeclient" - "github.com/google/mako/helpers/go/quickstore" - qpb "github.com/google/mako/helpers/proto/quickstore/quickstore_go_proto" + "github.com/google/mako/go/quickstore" + qpb "github.com/google/mako/proto/quickstore/quickstore_go_proto" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" "knative.dev/pkg/changeset" "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. - version, err := kubeclient.Get(ctx).Discovery().ServerVersion() + kc := kubeclient.Get(ctx) + version, err := kc.Discovery().ServerVersion() if err != nil { 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 { log.Printf("GCP project ID is not available: %v", err) } else { 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{ BenchmarkKey: MustGetBenchmark(), diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go index d08ea254..fed24f71 100644 --- a/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/apicoverage_recorder.go @@ -162,7 +162,7 @@ func (a *APICoverageRecorder) GetResourceCoverage(w http.ResponseWriter, r *http var ignoredFields coveragecalculator.IgnoredFields ignoredFieldsFilePath := os.Getenv("KO_DATA_PATH") + "/ignoredfields.yaml" 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] @@ -184,7 +184,7 @@ func (a *APICoverageRecorder) GetTotalCoverage(w http.ResponseWriter, r *http.Re ignoredFieldsFilePath := os.Getenv("KO_DATA_PATH") + "/ignoredfields.yaml" 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{} @@ -200,6 +200,7 @@ func (a *APICoverageRecorder) GetTotalCoverage(w http.ResponseWriter, r *http.Re var body []byte if body, err = json.Marshal(totalCoverage); err != nil { fmt.Fprintf(w, "error marshalling total coverage response: %v", err) + return } if _, err = w.Write(body); err != nil { diff --git a/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/webhook.go b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/webhook.go index ea7aef8b..ca027d44 100644 --- a/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/webhook.go +++ b/vendor/knative.dev/pkg/test/webhook-apicoverage/webhook/webhook.go @@ -237,7 +237,7 @@ func BuildWebhookConfiguration(componentCommonName string, webhookName string, n FailurePolicy: admissionregistrationv1beta1.Fail, ClientAuth: tls.NoClientCert, RegistrationDelay: time.Second * 2, - Port: 443, + Port: 8443, // Using port greater than 1024 as webhook runs as non-root user. Namespace: namespace, DeploymentName: componentCommonName, ServiceName: componentCommonName, diff --git a/vendor/knative.dev/pkg/webhook/resource_admission_controller.go b/vendor/knative.dev/pkg/webhook/resource_admission_controller.go new file mode 100644 index 00000000..cf0ec50c --- /dev/null +++ b/vendor/knative.dev/pkg/webhook/resource_admission_controller.go @@ -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) +} diff --git a/vendor/knative.dev/pkg/webhook/webhook.go b/vendor/knative.dev/pkg/webhook/webhook.go index 15d90e5e..2efdaefb 100644 --- a/vendor/knative.dev/pkg/webhook/webhook.go +++ b/vendor/knative.dev/pkg/webhook/webhook.go @@ -17,7 +17,6 @@ limitations under the License. package webhook import ( - "bytes" "context" "crypto/tls" "crypto/x509" @@ -25,30 +24,22 @@ import ( "errors" "fmt" "net/http" - "sort" - "strings" "time" "go.uber.org/zap" "knative.dev/pkg/apis" "knative.dev/pkg/apis/duck" - "knative.dev/pkg/kmp" "knative.dev/pkg/logging" "knative.dev/pkg/logging/logkey" - "github.com/markbates/inflect" - "github.com/mattbaird/jsonpatch" admissionv1beta1 "k8s.io/api/admission/v1beta1" - admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes" - clientadmissionregistrationv1beta1 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1" ) const ( @@ -105,34 +96,15 @@ type ControllerOptions struct { 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 // pilot configuration. type AdmissionController struct { - Client kubernetes.Interface - Options ControllerOptions - Handlers map[schema.GroupVersionKind]GenericCRD - Logger *zap.SugaredLogger + Client kubernetes.Interface + Options ControllerOptions + Logger *zap.SugaredLogger + resourceAdmissionController ResourceAdmissionController - 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 + WithContext func(context.Context) context.Context } // NewAdmissionController constructs an AdmissionController @@ -153,12 +125,15 @@ func NewAdmissionController( } return &AdmissionController{ - Client: client, - Options: opts, - Handlers: handlers, - Logger: logger, - WithContext: ctx, - DisallowUnknownFields: disallowUnknownFields, + Client: client, + Options: opts, + resourceAdmissionController: ResourceAdmissionController{ + Handlers: handlers, + Options: opts, + DisallowUnknownFields: disallowUnknownFields, + }, + Logger: logger, + WithContext: ctx, }, nil } @@ -238,7 +213,7 @@ func getOrGenerateKeyCertsFromSecret(ctx context.Context, client kubernetes.Inte // validate performs validation on the provided "new" CRD. // For legacy purposes, this also does apis.Immutable validation, // which is deprecated and will be removed in a future release. -func validate(ctx context.Context, new GenericCRD) error { +func validate(ctx context.Context, new apis.Validatable) error { if apis.IsInUpdate(ctx) { old := apis.GetBaseline(ctx) if immutableNew, ok := new.(apis.Immutable); ok { @@ -317,8 +292,7 @@ func (ac *AdmissionController) Run(stop <-chan struct{}) error { select { case <-time.After(ac.Options.RegistrationDelay): - cl := ac.Client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations() - if err := ac.register(ctx, cl, caCert); err != nil { + if err := ac.resourceAdmissionController.Register(ctx, ac.Client, caCert); err != nil { logger.Errorw("failed to register webhook", zap.Error(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 // serving resources. 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) } - reviewResponse := ac.admit(ctx, review.Request) + reviewResponse := ac.resourceAdmissionController.Admit(ctx, review.Request) var response admissionv1beta1.AdmissionReview if reviewResponse != nil { 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) { serverKey, serverCert, caCert, err := CreateCerts(ctx, options.ServiceName, options.Namespace) if err != nil {