use istio client-go library instead of knative (#1661)

use istio client-go library instead of knative
bump kubernetes dependency version
change code coverage to codecov
This commit is contained in:
zryfish
2019-12-13 11:26:18 +08:00
committed by GitHub
parent f249a6e081
commit ea88c8803d
2071 changed files with 354531 additions and 108336 deletions

View File

@@ -18,242 +18,183 @@ package admission
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"github.com/appscode/jsonpatch"
"github.com/go-logr/logr"
"gomodules.xyz/jsonpatch/v2"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
atypes "sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
"sigs.k8s.io/controller-runtime/pkg/webhook/types"
)
var (
errUnableToEncodeResponse = errors.New("unable to encode response")
)
// Request defines the input for an admission handler.
// It contains information to identify the object in
// question (group, version, kind, resource, subresource,
// name, namespace), as well as the operation in question
// (e.g. Get, Create, etc), and the object itself.
type Request struct {
admissionv1beta1.AdmissionRequest
}
// Response is the output of an admission handler.
// It contains a response indicating if a given
// operation is allowed, as well as a set of patches
// to mutate the object in the case of a mutating admission handler.
type Response struct {
// Patches are the JSON patches for mutating webhooks.
// Using this instead of setting Response.Patch to minimize
// overhead of serialization and deserialization.
// Patches set here will override any patches in the response,
// so leave this empty if you want to set the patch response directly.
Patches []jsonpatch.JsonPatchOperation
// AdmissionResponse is the raw admission response.
// The Patch field in it will be overwritten by the listed patches.
admissionv1beta1.AdmissionResponse
}
// Complete populates any fields that are yet to be set in
// the underlying AdmissionResponse, It mutates the response.
func (r *Response) Complete(req Request) error {
r.UID = req.UID
// ensure that we have a valid status code
if r.Result == nil {
r.Result = &metav1.Status{}
}
if r.Result.Code == 0 {
r.Result.Code = http.StatusOK
}
// TODO(directxman12): do we need to populate this further, and/or
// is code actually necessary (the same webhook doesn't use it)
if len(r.Patches) == 0 {
return nil
}
var err error
r.Patch, err = json.Marshal(r.Patches)
if err != nil {
return err
}
patchType := admissionv1beta1.PatchTypeJSONPatch
r.PatchType = &patchType
return nil
}
// Handler can handle an AdmissionRequest.
type Handler interface {
Handle(context.Context, atypes.Request) atypes.Response
// Handle yields a response to an AdmissionRequest.
//
// The supplied context is extracted from the received http.Request, allowing wrapping
// http.Handlers to inject values into and control cancelation of downstream request processing.
Handle(context.Context, Request) Response
}
// HandlerFunc implements Handler interface using a single function.
type HandlerFunc func(context.Context, atypes.Request) atypes.Response
type HandlerFunc func(context.Context, Request) Response
var _ Handler = HandlerFunc(nil)
// Handle process the AdmissionRequest by invoking the underlying function.
func (f HandlerFunc) Handle(ctx context.Context, req atypes.Request) atypes.Response {
func (f HandlerFunc) Handle(ctx context.Context, req Request) Response {
return f(ctx, req)
}
// Webhook represents each individual webhook.
type Webhook struct {
// Name is the name of the webhook
Name string
// Type is the webhook type, i.e. mutating, validating
Type types.WebhookType
// Path is the path this webhook will serve.
Path string
// Rules maps to the Rules field in admissionregistrationv1beta1.Webhook
Rules []admissionregistrationv1beta1.RuleWithOperations
// FailurePolicy maps to the FailurePolicy field in admissionregistrationv1beta1.Webhook
// This optional. If not set, will be defaulted to Ignore (fail-open) by the server.
// More details: https://github.com/kubernetes/api/blob/f5c295feaba2cbc946f0bbb8b535fc5f6a0345ee/admissionregistration/v1beta1/types.go#L144-L147
FailurePolicy *admissionregistrationv1beta1.FailurePolicyType
// NamespaceSelector maps to the NamespaceSelector field in admissionregistrationv1beta1.Webhook
// This optional.
NamespaceSelector *metav1.LabelSelector
// Handlers contains a list of handlers. Each handler may only contains the business logic for its own feature.
// For example, feature foo and bar can be in the same webhook if all the other configurations are the same.
// The handler will be invoked sequentially as the order in the list.
// Note: if you are using mutating webhook with multiple handlers, it's your responsibility to
// ensure the handlers are not generating conflicting JSON patches.
Handlers []Handler
// Handler actually processes an admission request returning whether it was allowed or denied,
// and potentially patches to apply to the handler.
Handler Handler
once sync.Once
// decoder is constructed on receiving a scheme and passed down to then handler
decoder *Decoder
log logr.Logger
}
func (w *Webhook) setDefaults() {
if len(w.Path) == 0 {
if len(w.Rules) == 0 || len(w.Rules[0].Resources) == 0 {
// can't do defaulting, skip it.
return
}
if w.Type == types.WebhookTypeMutating {
w.Path = "/mutate-" + w.Rules[0].Resources[0]
} else if w.Type == types.WebhookTypeValidating {
w.Path = "/validate-" + w.Rules[0].Resources[0]
}
}
if len(w.Name) == 0 {
reg := regexp.MustCompile("[^a-zA-Z0-9]+")
processedPath := strings.ToLower(reg.ReplaceAllString(w.Path, ""))
w.Name = processedPath + ".example.com"
}
// InjectLogger gets a handle to a logging instance, hopefully with more info about this particular webhook.
func (w *Webhook) InjectLogger(l logr.Logger) error {
w.log = l
return nil
}
// Add adds additional handler(s) in the webhook
func (w *Webhook) Add(handlers ...Handler) {
w.Handlers = append(w.Handlers, handlers...)
}
// Webhook implements Handler interface.
var _ Handler = &Webhook{}
// Handle processes AdmissionRequest.
// If the webhook is mutating type, it delegates the AdmissionRequest to each handler and merge the patches.
// If the webhook is validating type, it delegates the AdmissionRequest to each handler and
// deny the request if anyone denies.
func (w *Webhook) Handle(ctx context.Context, req atypes.Request) atypes.Response {
if req.AdmissionRequest == nil {
return ErrorResponse(http.StatusBadRequest, errors.New("got an empty AdmissionRequest"))
func (w *Webhook) Handle(ctx context.Context, req Request) Response {
resp := w.Handler.Handle(ctx, req)
if err := resp.Complete(req); err != nil {
w.log.Error(err, "unable to encode response")
return Errored(http.StatusInternalServerError, errUnableToEncodeResponse)
}
var resp atypes.Response
switch w.Type {
case types.WebhookTypeMutating:
resp = w.handleMutating(ctx, req)
case types.WebhookTypeValidating:
resp = w.handleValidating(ctx, req)
default:
return ErrorResponse(http.StatusInternalServerError, errors.New("you must specify your webhook type"))
}
resp.Response.UID = req.AdmissionRequest.UID
return resp
}
func (w *Webhook) handleMutating(ctx context.Context, req atypes.Request) atypes.Response {
patches := []jsonpatch.JsonPatchOperation{}
for _, handler := range w.Handlers {
resp := handler.Handle(ctx, req)
if !resp.Response.Allowed {
setStatusOKInAdmissionResponse(resp.Response)
return resp
}
if resp.Response.PatchType != nil && *resp.Response.PatchType != admissionv1beta1.PatchTypeJSONPatch {
return ErrorResponse(http.StatusInternalServerError,
fmt.Errorf("unexpected patch type returned by the handler: %v, only allow: %v",
resp.Response.PatchType, admissionv1beta1.PatchTypeJSONPatch))
}
patches = append(patches, resp.Patches...)
}
// InjectScheme injects a scheme into the webhook, in order to construct a Decoder.
func (w *Webhook) InjectScheme(s *runtime.Scheme) error {
// TODO(directxman12): we should have a better way to pass this down
var err error
marshaledPatch, err := json.Marshal(patches)
w.decoder, err = NewDecoder(s)
if err != nil {
return ErrorResponse(http.StatusBadRequest, fmt.Errorf("error when marshaling the patch: %v", err))
return err
}
return atypes.Response{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Code: http.StatusOK,
},
Patch: marshaledPatch,
PatchType: func() *admissionv1beta1.PatchType { pt := admissionv1beta1.PatchTypeJSONPatch; return &pt }(),
},
}
}
func (w *Webhook) handleValidating(ctx context.Context, req atypes.Request) atypes.Response {
for _, handler := range w.Handlers {
resp := handler.Handle(ctx, req)
if !resp.Response.Allowed {
setStatusOKInAdmissionResponse(resp.Response)
return resp
}
}
return atypes.Response{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Code: http.StatusOK,
},
},
}
}
func setStatusOKInAdmissionResponse(resp *admissionv1beta1.AdmissionResponse) {
if resp == nil {
return
}
if resp.Result == nil {
resp.Result = &metav1.Status{}
}
if resp.Result.Code == 0 {
resp.Result.Code = http.StatusOK
}
}
// GetName returns the name of the webhook.
func (w *Webhook) GetName() string {
w.once.Do(w.setDefaults)
return w.Name
}
// GetPath returns the path that the webhook registered.
func (w *Webhook) GetPath() string {
w.once.Do(w.setDefaults)
return w.Path
}
// GetType returns the type of the webhook.
func (w *Webhook) GetType() types.WebhookType {
w.once.Do(w.setDefaults)
return w.Type
}
// Handler returns a http.Handler for the webhook
func (w *Webhook) Handler() http.Handler {
w.once.Do(w.setDefaults)
return w
}
// Validate validates if the webhook is valid.
func (w *Webhook) Validate() error {
w.once.Do(w.setDefaults)
if len(w.Rules) == 0 {
return errors.New("field Rules should not be empty")
}
if len(w.Name) == 0 {
return errors.New("field Name should not be empty")
}
if w.Type != types.WebhookTypeMutating && w.Type != types.WebhookTypeValidating {
return fmt.Errorf("unsupported Type: %v, only WebhookTypeMutating and WebhookTypeValidating are supported", w.Type)
}
if len(w.Path) == 0 {
return errors.New("field Path should not be empty")
}
if len(w.Handlers) == 0 {
return errors.New("field Handler should not be empty")
}
return nil
}
var _ inject.Client = &Webhook{}
// InjectClient injects the client into the handlers
func (w *Webhook) InjectClient(c client.Client) error {
for _, handler := range w.Handlers {
if _, err := inject.ClientInto(c, handler); err != nil {
// inject the decoder here too, just in case the order of calling this is not
// scheme first, then inject func
if w.Handler != nil {
if _, err := InjectDecoderInto(w.GetDecoder(), w.Handler); err != nil {
return err
}
}
return nil
}
var _ inject.Decoder = &Webhook{}
// GetDecoder returns a decoder to decode the objects embedded in admission requests.
// It may be nil if we haven't received a scheme to use to determine object types yet.
func (w *Webhook) GetDecoder() *Decoder {
return w.decoder
}
// InjectDecoder injects the decoder into the handlers
func (w *Webhook) InjectDecoder(d atypes.Decoder) error {
for _, handler := range w.Handlers {
if _, err := inject.DecoderInto(d, handler); err != nil {
// InjectFunc injects the field setter into the webhook.
func (w *Webhook) InjectFunc(f inject.Func) error {
// inject directly into the handlers. It would be more correct
// to do this in a sync.Once in Handle (since we don't have some
// other start/finalize-type method), but it's more efficient to
// do it here, presumably.
// also inject a decoder, and wrap this so that we get a setFields
// that injects a decoder (hopefully things don't ignore the duplicate
// InjectorInto call).
var setFields inject.Func
setFields = func(target interface{}) error {
if err := f(target); err != nil {
return err
}
if _, err := inject.InjectorInto(setFields, target); err != nil {
return err
}
if _, err := InjectDecoderInto(w.GetDecoder(), target); err != nil {
return err
}
return nil
}
return nil
return setFields(w.Handler)
}