add hook handler interface
Signed-off-by: changzhen <changzhen5@huawei.com>
This commit is contained in:
parent
30803c9b8c
commit
0acd626d1b
|
|
@ -260,5 +260,12 @@ func (e *CustomizedExplorer) callHook(ctx context.Context, hook configmanager.We
|
|||
}
|
||||
}
|
||||
|
||||
if !res.Successful {
|
||||
return nil, &webhookutil.ErrCallingWebhook{
|
||||
WebhookName: hook.GetUID(),
|
||||
Reason: fmt.Errorf("webhook call failed, get status code: %d, msg: %s", res.Status.Code, res.Status.Message),
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ type RequestAttributes struct {
|
|||
|
||||
// ResponseAttributes contains the attributes that response by the webhook.
|
||||
type ResponseAttributes struct {
|
||||
Successful bool
|
||||
Status configv1alpha1.RequestStatus
|
||||
Replicas int32
|
||||
ReplicaRequirements *workv1alpha2.ReplicaRequirements
|
||||
Dependencies []configv1alpha1.DependentObjectReference
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func CreateV1alpha1ExploreReview(uid types.UID, attributes *RequestAttributes) *
|
|||
}
|
||||
|
||||
// VerifyExploreReview checks the validity of the provided explore review object, and returns ResponseAttributes,
|
||||
// or an error if the provided admission review was not valid.
|
||||
// or an error if the provided explore review was not valid.
|
||||
func VerifyExploreReview(uid types.UID, operation configv1alpha1.OperationType, review runtime.Object) (response *ResponseAttributes, err error) {
|
||||
switch r := review.(type) {
|
||||
case *configv1alpha1.ExploreReview:
|
||||
|
|
@ -62,41 +62,59 @@ func VerifyExploreReview(uid types.UID, operation configv1alpha1.OperationType,
|
|||
return nil, fmt.Errorf("expected response.uid %q, got %q", uid, r.Response.UID)
|
||||
}
|
||||
|
||||
v1alpha1GVK := configv1alpha1.SchemeGroupVersion.WithKind("ExploreReview")
|
||||
if r.GroupVersionKind() != v1alpha1GVK {
|
||||
return nil, fmt.Errorf("expected webhook response of %v, got %v", v1alpha1GVK.String(), r.GroupVersionKind().String())
|
||||
}
|
||||
|
||||
if err := verifyExploreResponse(operation, r.Response); err != nil {
|
||||
res, err := verifyExploreResponse(operation, r.Response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extractResponseFromV1alpha1ExploreReview(r.Response), nil
|
||||
return res, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected response type %T", review)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyExploreResponse(operation configv1alpha1.OperationType, response *configv1alpha1.ExploreResponse) error {
|
||||
func verifyExploreResponse(operation configv1alpha1.OperationType, response *configv1alpha1.ExploreResponse) (*ResponseAttributes, error) {
|
||||
res := &ResponseAttributes{}
|
||||
|
||||
res.Successful = response.Successful
|
||||
if !response.Successful {
|
||||
if response.Status == nil {
|
||||
return nil, fmt.Errorf("webhook require response.status when response.successful is false")
|
||||
}
|
||||
res.Status = *response.Status
|
||||
return res, nil
|
||||
}
|
||||
|
||||
switch operation {
|
||||
case configv1alpha1.ExploreReplica:
|
||||
if response.Replicas == nil {
|
||||
return fmt.Errorf("webhook returned nil response.replicas")
|
||||
return nil, fmt.Errorf("webhook returned nil response.replicas")
|
||||
}
|
||||
return nil
|
||||
res.Replicas = *response.Replicas
|
||||
res.ReplicaRequirements = response.ReplicaRequirements
|
||||
return res, nil
|
||||
case configv1alpha1.ExploreDependencies:
|
||||
return nil
|
||||
res.Dependencies = response.Dependencies
|
||||
return res, nil
|
||||
case configv1alpha1.ExplorePacking, configv1alpha1.ExploreReplicaRevising,
|
||||
configv1alpha1.ExploreRetaining, configv1alpha1.ExploreStatusAggregating:
|
||||
return verifyExploreResponseWithPatch(response)
|
||||
err := verifyExploreResponseWithPatch(response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Patch = response.Patch
|
||||
res.PatchType = *response.PatchType
|
||||
return res, nil
|
||||
case configv1alpha1.ExploreStatus:
|
||||
return nil
|
||||
res.RawStatus = *response.RawStatus
|
||||
return res, nil
|
||||
case configv1alpha1.ExploreHealthy:
|
||||
if response.Healthy == nil {
|
||||
return fmt.Errorf("webhook returned nil response.healthy")
|
||||
return nil, fmt.Errorf("webhook returned nil response.healthy")
|
||||
}
|
||||
return nil
|
||||
res.Healthy = *response.Healthy
|
||||
return res, nil
|
||||
default:
|
||||
return fmt.Errorf("input wrong operation type: %s", operation)
|
||||
return nil, fmt.Errorf("input wrong operation type: %s", operation)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,15 +134,3 @@ func verifyExploreResponseWithPatch(response *configv1alpha1.ExploreResponse) er
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractResponseFromV1alpha1ExploreReview(response *configv1alpha1.ExploreResponse) *ResponseAttributes {
|
||||
return &ResponseAttributes{
|
||||
Replicas: *response.Replicas,
|
||||
ReplicaRequirements: response.ReplicaRequirements,
|
||||
Dependencies: response.Dependencies,
|
||||
Patch: response.Patch,
|
||||
PatchType: *response.PatchType,
|
||||
RawStatus: *response.RawStatus,
|
||||
Healthy: *response.Healthy,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package explorer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
// Decoder knows how to decode the contents of an explorer
|
||||
// request into a concrete object.
|
||||
type Decoder struct {
|
||||
codecs serializer.CodecFactory
|
||||
}
|
||||
|
||||
// NewDecoder creates a Decoder given the runtime.Scheme.
|
||||
func NewDecoder(scheme *runtime.Scheme) *Decoder {
|
||||
return &Decoder{
|
||||
codecs: serializer.NewCodecFactory(scheme),
|
||||
}
|
||||
}
|
||||
|
||||
// Decode decodes the inlined object in the ExploreRequest into the passed-in runtime.Object.
|
||||
// It errors out if req.Object.Raw is empty i.e. containing 0 raw bytes.
|
||||
func (d *Decoder) Decode(req Request, into runtime.Object) error {
|
||||
if len(req.Object.Raw) == 0 {
|
||||
return fmt.Errorf("there is no context to decode")
|
||||
}
|
||||
if unstructuredInto, isUnstructured := into.(*unstructured.Unstructured); isUnstructured {
|
||||
// unmarshal into unstructured's underlying object to avoid calling the decoder
|
||||
return json.Unmarshal(req.Object.Raw, &unstructuredInto.Object)
|
||||
}
|
||||
|
||||
deserializer := d.codecs.UniversalDeserializer()
|
||||
return runtime.DecodeInto(deserializer, req.Object.Raw, into)
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package explorer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
)
|
||||
|
||||
var admissionScheme = runtime.NewScheme()
|
||||
var admissionCodecs = serializer.NewCodecFactory(admissionScheme)
|
||||
|
||||
// ServeHTTP write reply headers and data to the ResponseWriter and then return.
|
||||
func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var body []byte
|
||||
var err error
|
||||
ctx := r.Context()
|
||||
|
||||
var reviewResponse Response
|
||||
if r.Body == nil {
|
||||
err = errors.New("request body is empty")
|
||||
klog.Errorf("bad request: %w", err)
|
||||
reviewResponse = Errored(http.StatusBadRequest, err)
|
||||
wh.writeResponse(w, reviewResponse)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
if body, err = ioutil.ReadAll(r.Body); err != nil {
|
||||
klog.Errorf("unable to read the body from the incoming request: %w", err)
|
||||
reviewResponse = Errored(http.StatusBadRequest, err)
|
||||
wh.writeResponse(w, reviewResponse)
|
||||
return
|
||||
}
|
||||
|
||||
// verify the content type is accurate
|
||||
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
|
||||
err = fmt.Errorf("contentType=%s, expected application/json", contentType)
|
||||
klog.Errorf("unable to process a request with an unknown content type: %w", err)
|
||||
reviewResponse = Errored(http.StatusBadRequest, err)
|
||||
wh.writeResponse(w, reviewResponse)
|
||||
return
|
||||
}
|
||||
|
||||
request := Request{}
|
||||
er := configv1alpha1.ExploreReview{}
|
||||
// avoid an extra copy
|
||||
er.Request = &request.ExploreRequest
|
||||
_, _, err = admissionCodecs.UniversalDeserializer().Decode(body, nil, &er)
|
||||
if err != nil {
|
||||
klog.Errorf("unable to decode the request: %w", err)
|
||||
reviewResponse = Errored(http.StatusBadRequest, err)
|
||||
wh.writeResponse(w, reviewResponse)
|
||||
return
|
||||
}
|
||||
klog.V(1).Infof("received request UID: %q, kind: %s", request.UID, request.Kind)
|
||||
|
||||
reviewResponse = wh.Handle(ctx, request)
|
||||
wh.writeResponse(w, reviewResponse)
|
||||
}
|
||||
|
||||
// writeResponse writes response to w generically, i.e. without encoding GVK information.
|
||||
func (wh *Webhook) writeResponse(w io.Writer, response Response) {
|
||||
wh.writeExploreResponse(w, configv1alpha1.ExploreReview{
|
||||
Response: &response.ExploreResponse,
|
||||
})
|
||||
}
|
||||
|
||||
// writeExploreResponse writes ar to w.
|
||||
func (wh *Webhook) writeExploreResponse(w io.Writer, review configv1alpha1.ExploreReview) {
|
||||
if err := json.NewEncoder(w).Encode(review); err != nil {
|
||||
klog.Errorf("unable to encode the response: %w", err)
|
||||
wh.writeResponse(w, Errored(http.StatusInternalServerError, err))
|
||||
} else {
|
||||
response := review.Response
|
||||
if response.Successful {
|
||||
klog.V(4).Infof("wrote response UID: %q, successful: %t", response.UID, response.Successful)
|
||||
} else {
|
||||
klog.V(4).Infof("wrote response UID: %q, successful: %t, response.status.code: %d, response.status.message: %s",
|
||||
response.UID, response.Successful, response.Status.Code, response.Status.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package explorer
|
||||
|
||||
// DecoderInjector is used by the ControllerManager to inject decoder into webhook handlers.
|
||||
type DecoderInjector interface {
|
||||
InjectDecoder(*Decoder)
|
||||
}
|
||||
|
||||
// InjectDecoderInto will set decoder on i and return the result if it implements Decoder.
|
||||
// Returns false if i does not implement Decoder.
|
||||
func InjectDecoderInto(decoder *Decoder, i interface{}) bool {
|
||||
if s, ok := i.(DecoderInjector); ok {
|
||||
s.InjectDecoder(decoder)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package explorer
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
)
|
||||
|
||||
// Errored creates a new Response for error-handling a request.
|
||||
func Errored(code int32, err error) Response {
|
||||
return Response{
|
||||
ExploreResponse: configv1alpha1.ExploreResponse{
|
||||
Successful: false,
|
||||
Status: &configv1alpha1.RequestStatus{
|
||||
Code: code,
|
||||
Message: err.Error(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Succeeded constructs a response indicating the given operation is handled successfully.
|
||||
func Succeeded(msg string) Response {
|
||||
return ValidationResponse(true, msg)
|
||||
}
|
||||
|
||||
// ValidationResponse returns a response for handle a explore request.
|
||||
func ValidationResponse(successful bool, msg string) Response {
|
||||
code := http.StatusForbidden
|
||||
if successful {
|
||||
code = http.StatusOK
|
||||
}
|
||||
return Response{
|
||||
ExploreResponse: configv1alpha1.ExploreResponse{
|
||||
Successful: successful,
|
||||
Status: &configv1alpha1.RequestStatus{
|
||||
Code: int32(code),
|
||||
Message: msg,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package explorer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
)
|
||||
|
||||
// Request defines the input for an explorer handler.
|
||||
// It contains information to identify the object in
|
||||
// question (kind, name, namespace), as well as the
|
||||
// operation in request(e.g. ExploreReplica, ExplorePacking,
|
||||
// etc), and the object itself.
|
||||
type Request struct {
|
||||
configv1alpha1.ExploreRequest
|
||||
}
|
||||
|
||||
// Response is the output of an explorer handler.
|
||||
type Response struct {
|
||||
configv1alpha1.ExploreResponse
|
||||
}
|
||||
|
||||
// Complete populates any fields that are yet to be set in
|
||||
// the underlying ExploreResponse, It mutates the response.
|
||||
func (r *Response) Complete(req Request) {
|
||||
r.UID = req.UID
|
||||
|
||||
// ensure that we have a valid status code
|
||||
if r.Status == nil {
|
||||
r.Status = &configv1alpha1.RequestStatus{}
|
||||
}
|
||||
if r.Status.Code == 0 {
|
||||
r.Status.Code = http.StatusOK
|
||||
}
|
||||
}
|
||||
|
||||
// Handler can handle an ExploreRequest.
|
||||
type Handler interface {
|
||||
// Handle yields a response to an ExploreRequest.
|
||||
//
|
||||
// The supplied context is extracted from the received http.Request, allowing wrapping
|
||||
// http.Handlers to inject values into and control cancellation of downstream request processing.
|
||||
Handle(context.Context, Request) Response
|
||||
}
|
||||
|
||||
// Webhook represents each individual webhook.
|
||||
type Webhook struct {
|
||||
// handler actually processes an admission request returning whether it was allowed or denied,
|
||||
// and potentially patches to apply to the handler.
|
||||
handler Handler
|
||||
}
|
||||
|
||||
// NewWebhook return a Webhook
|
||||
func NewWebhook(handler Handler, decoder *Decoder) *Webhook {
|
||||
if !InjectDecoderInto(decoder, handler) {
|
||||
klog.Errorf("Inject decoder into handler err")
|
||||
}
|
||||
return &Webhook{handler: handler}
|
||||
}
|
||||
|
||||
// Handle processes ExploreRequest.
|
||||
func (wh *Webhook) Handle(ctx context.Context, req Request) Response {
|
||||
resp := wh.handler.Handle(ctx, req)
|
||||
resp.Complete(req)
|
||||
return resp
|
||||
}
|
||||
Loading…
Reference in New Issue