Initial commit

This commit is contained in:
jeff
2019-03-07 17:08:54 +08:00
commit 47bf8820f4
2817 changed files with 960937 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
/*
Copyright 2018 The Kubernetes 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 admission
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
)
// DecodeFunc is a function that implements the Decoder interface.
type DecodeFunc func(types.Request, runtime.Object) error
var _ types.Decoder = DecodeFunc(nil)
// Decode implements the Decoder interface.
func (f DecodeFunc) Decode(req types.Request, obj runtime.Object) error {
return f(req, obj)
}
type decoder struct {
codecs serializer.CodecFactory
}
// NewDecoder creates a Decoder given the runtime.Scheme
func NewDecoder(scheme *runtime.Scheme) (types.Decoder, error) {
return decoder{codecs: serializer.NewCodecFactory(scheme)}, nil
}
// Decode decodes the inlined object in the AdmissionRequest into the passed-in runtime.Object.
func (d decoder) Decode(req types.Request, into runtime.Object) error {
deserializer := d.codecs.UniversalDeserializer()
return runtime.DecodeInto(deserializer, req.AdmissionRequest.Object.Raw, into)
}

View File

@@ -0,0 +1,101 @@
/*
Copyright 2018 The Kubernetes 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 admission provides implementation for admission webhook and methods to implement admission webhook handlers.
The following snippet is an example implementation of mutating handler.
type Mutator struct {
client client.Client
decoder types.Decoder
}
func (m *Mutator) mutatePodsFn(ctx context.Context, pod *corev1.Pod) error {
// your logic to mutate the passed-in pod.
}
func (m *Mutator) Handle(ctx context.Context, req types.Request) types.Response {
pod := &corev1.Pod{}
err := m.decoder.Decode(req, pod)
if err != nil {
return admission.ErrorResponse(http.StatusBadRequest, err)
}
// Do deepcopy before actually mutate the object.
copy := pod.DeepCopy()
err = m.mutatePodsFn(ctx, copy)
if err != nil {
return admission.ErrorResponse(http.StatusInternalServerError, err)
}
return admission.PatchResponse(pod, copy)
}
// InjectClient is called by the Manager and provides a client.Client to the Mutator instance.
func (m *Mutator) InjectClient(c client.Client) error {
h.client = c
return nil
}
// InjectDecoder is called by the Manager and provides a types.Decoder to the Mutator instance.
func (m *Mutator) InjectDecoder(d types.Decoder) error {
h.decoder = d
return nil
}
The following snippet is an example implementation of validating handler.
type Handler struct {
client client.Client
decoder types.Decoder
}
func (v *Validator) validatePodsFn(ctx context.Context, pod *corev1.Pod) (bool, string, error) {
// your business logic
}
func (v *Validator) Handle(ctx context.Context, req types.Request) types.Response {
pod := &corev1.Pod{}
err := h.decoder.Decode(req, pod)
if err != nil {
return admission.ErrorResponse(http.StatusBadRequest, err)
}
allowed, reason, err := h.validatePodsFn(ctx, pod)
if err != nil {
return admission.ErrorResponse(http.StatusInternalServerError, err)
}
return admission.ValidationResponse(allowed, reason)
}
// InjectClient is called by the Manager and provides a client.Client to the Validator instance.
func (v *Validator) InjectClient(c client.Client) error {
h.client = c
return nil
}
// InjectDecoder is called by the Manager and provides a types.Decoder to the Validator instance.
func (v *Validator) InjectDecoder(d types.Decoder) error {
h.decoder = d
return nil
}
*/
package admission
import (
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)
var log = logf.KBLog.WithName("admission")

View File

@@ -0,0 +1,115 @@
/*
Copyright 2018 The Kubernetes 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 admission
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
"k8s.io/api/admission/v1beta1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/metrics"
)
var admissionv1beta1scheme = runtime.NewScheme()
var admissionv1beta1schemecodecs = serializer.NewCodecFactory(admissionv1beta1scheme)
func init() {
addToScheme(admissionv1beta1scheme)
}
func addToScheme(scheme *runtime.Scheme) {
utilruntime.Must(admissionv1beta1.AddToScheme(scheme))
}
var _ http.Handler = &Webhook{}
func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
startTS := time.Now()
defer metrics.RequestLatency.WithLabelValues(wh.Name).Observe(time.Now().Sub(startTS).Seconds())
var body []byte
var err error
var reviewResponse types.Response
if r.Body != nil {
if body, err = ioutil.ReadAll(r.Body); err != nil {
log.Error(err, "unable to read the body from the incoming request")
reviewResponse = ErrorResponse(http.StatusBadRequest, err)
wh.writeResponse(w, reviewResponse)
return
}
} else {
err = errors.New("request body is empty")
log.Error(err, "bad request")
reviewResponse = ErrorResponse(http.StatusBadRequest, err)
wh.writeResponse(w, reviewResponse)
return
}
// verify the content type is accurate
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
err = fmt.Errorf("contentType=%s, expect application/json", contentType)
log.Error(err, "unable to process a request with an unknown content type", "content type", contentType)
reviewResponse = ErrorResponse(http.StatusBadRequest, err)
wh.writeResponse(w, reviewResponse)
return
}
ar := v1beta1.AdmissionReview{}
if _, _, err := admissionv1beta1schemecodecs.UniversalDeserializer().Decode(body, nil, &ar); err != nil {
log.Error(err, "unable to decode the request")
reviewResponse = ErrorResponse(http.StatusBadRequest, err)
wh.writeResponse(w, reviewResponse)
return
}
// TODO: add panic-recovery for Handle
reviewResponse = wh.Handle(context.Background(), types.Request{AdmissionRequest: ar.Request})
wh.writeResponse(w, reviewResponse)
}
func (wh *Webhook) writeResponse(w io.Writer, response types.Response) {
if response.Response.Result.Code != 0 {
if response.Response.Result.Code == http.StatusOK {
metrics.TotalRequests.WithLabelValues(wh.Name, "true").Inc()
} else {
metrics.TotalRequests.WithLabelValues(wh.Name, "false").Inc()
}
}
encoder := json.NewEncoder(w)
responseAdmissionReview := v1beta1.AdmissionReview{
Response: response.Response,
}
err := encoder.Encode(responseAdmissionReview)
if err != nil {
log.Error(err, "unable to encode the response")
wh.writeResponse(w, ErrorResponse(http.StatusInternalServerError, err))
}
}

View File

@@ -0,0 +1,70 @@
/*
Copyright 2018 The Kubernetes 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 admission
import (
"net/http"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/patch"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
)
// ErrorResponse creates a new Response for error-handling a request.
func ErrorResponse(code int32, err error) types.Response {
return types.Response{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Code: code,
Message: err.Error(),
},
},
}
}
// ValidationResponse returns a response for admitting a request.
func ValidationResponse(allowed bool, reason string) types.Response {
resp := types.Response{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: allowed,
},
}
if len(reason) > 0 {
resp.Response.Result = &metav1.Status{
Reason: metav1.StatusReason(reason),
}
}
return resp
}
// PatchResponse returns a new response with json patch.
func PatchResponse(original, current runtime.Object) types.Response {
patches, err := patch.NewJSONPatch(original, current)
if err != nil {
return ErrorResponse(http.StatusInternalServerError, err)
}
return types.Response{
Patches: patches,
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
PatchType: func() *admissionv1beta1.PatchType { pt := admissionv1beta1.PatchTypeJSONPatch; return &pt }(),
},
}
}

View File

@@ -0,0 +1,44 @@
/*
Copyright 2018 The Kubernetes 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 types
import (
"github.com/mattbaird/jsonpatch"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
)
// Request is the input of Handler
type Request struct {
AdmissionRequest *admissionv1beta1.AdmissionRequest
}
// Response is the output of admission.Handler
type Response struct {
// Patches are the JSON patches for mutating webhooks.
// Using this instead of setting Response.Patch to minimize the overhead of serialization and deserialization.
Patches []jsonpatch.JsonPatchOperation
// Response is the admission response. Don't set the Patch field in it.
Response *admissionv1beta1.AdmissionResponse
}
// Decoder is used to decode AdmissionRequest.
type Decoder interface {
// Decode decodes the raw byte object from the AdmissionRequest to the passed-in runtime.Object.
Decode(Request, runtime.Object) error
}

View File

@@ -0,0 +1,259 @@
/*
Copyright 2018 The Kubernetes 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 admission
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"github.com/mattbaird/jsonpatch"
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"
"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"
)
// Handler can handle an AdmissionRequest.
type Handler interface {
Handle(context.Context, atypes.Request) atypes.Response
}
// HandlerFunc implements Handler interface using a single function.
type HandlerFunc func(context.Context, atypes.Request) atypes.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 {
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
once sync.Once
}
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"
}
}
// 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"))
}
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...)
}
var err error
marshaledPatch, err := json.Marshal(patches)
if err != nil {
return ErrorResponse(http.StatusBadRequest, fmt.Errorf("error when marshaling the patch: %v", 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 {
return err
}
}
return nil
}
var _ inject.Decoder = &Webhook{}
// 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 {
return err
}
}
return nil
}

View File

@@ -0,0 +1,51 @@
/*
Copyright 2018 The Kubernetes 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 metrics
import (
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
var (
// TotalRequests is a prometheus metric which counts the total number of requests that
// the webhook server has received.
TotalRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "controller_runtime_webhook_requests_total",
Help: "Total number of admission requests",
},
[]string{"webhook", "succeeded"},
)
// RequestLatency is a prometheus metric which is a histogram of the latency
// of processing admission requests.
RequestLatency = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "controller_runtime_webhook_latency_seconds",
Help: "Histogram of the latency of processing admission requests",
},
[]string{"webhook"},
)
)
func init() {
metrics.Registry.MustRegister(
TotalRequests,
RequestLatency)
}

View File

@@ -0,0 +1,28 @@
/*
Copyright 2018 The Kubernetes 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 types
// WebhookType defines the type of a webhook
type WebhookType int
const (
_ = iota
// WebhookTypeMutating represents mutating type webhook
WebhookTypeMutating WebhookType = iota
// WebhookTypeValidating represents validating type webhook
WebhookTypeValidating
)