karmada/examples/customresourceinterpreter/webhook/app/workloadwebhook.go

156 lines
5.8 KiB
Go

package app
import (
"context"
"encoding/json"
"fmt"
"net/http"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
"k8s.io/utils/pointer"
workloadv1alpha1 "github.com/karmada-io/karmada/examples/customresourceinterpreter/apis/workload/v1alpha1"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
"github.com/karmada-io/karmada/pkg/webhook/interpreter"
)
// Check if our workloadInterpreter implements necessary interface
var _ interpreter.Handler = &workloadInterpreter{}
var _ interpreter.DecoderInjector = &workloadInterpreter{}
// workloadInterpreter explore resource with request operation.
type workloadInterpreter struct {
decoder *interpreter.Decoder
}
// Handle implements interpreter.Handler interface.
// It yields a response to an ExploreRequest.
func (e *workloadInterpreter) Handle(_ context.Context, req interpreter.Request) interpreter.Response {
workload := &workloadv1alpha1.Workload{}
err := e.decoder.Decode(req, workload)
if err != nil {
return interpreter.Errored(http.StatusBadRequest, err)
}
klog.Infof("Explore workload(%s/%s) for request: %s", workload.GetNamespace(), workload.GetName(), req.Operation)
switch req.Operation {
case configv1alpha1.InterpreterOperationInterpretReplica:
return e.responseWithExploreReplica(workload)
case configv1alpha1.InterpreterOperationReviseReplica:
return e.responseWithExploreReviseReplica(workload, req)
case configv1alpha1.InterpreterOperationRetain:
return e.responseWithExploreRetaining(workload, req)
case configv1alpha1.InterpreterOperationAggregateStatus:
return e.responseWithExploreAggregateStatus(workload, req)
case configv1alpha1.InterpreterOperationInterpretHealth:
return e.responseWithExploreInterpretHealth(workload)
case configv1alpha1.InterpreterOperationInterpretStatus:
return e.responseWithExploreInterpretStatus(workload)
case configv1alpha1.InterpreterOperationInterpretDependency:
return e.responseWithExploreDependency(workload)
default:
return interpreter.Errored(http.StatusBadRequest, fmt.Errorf("wrong request operation type: %s", req.Operation))
}
}
// InjectDecoder implements interpreter.DecoderInjector interface.
func (e *workloadInterpreter) InjectDecoder(d *interpreter.Decoder) {
e.decoder = d
}
func (e *workloadInterpreter) responseWithExploreReplica(workload *workloadv1alpha1.Workload) interpreter.Response {
res := interpreter.Succeeded("")
res.Replicas = workload.Spec.Replicas
return res
}
func (e *workloadInterpreter) responseWithExploreDependency(workload *workloadv1alpha1.Workload) interpreter.Response {
res := interpreter.Succeeded("")
res.Dependencies = []configv1alpha1.DependentObjectReference{{APIVersion: "v1", Kind: "ConfigMap",
Namespace: workload.Namespace, Name: workload.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef.Name}}
return res
}
func (e *workloadInterpreter) responseWithExploreReviseReplica(workload *workloadv1alpha1.Workload, req interpreter.Request) interpreter.Response {
wantedWorkload := workload.DeepCopy()
wantedWorkload.Spec.Replicas = req.DesiredReplicas
marshaledBytes, err := json.Marshal(wantedWorkload)
if err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
return interpreter.PatchResponseFromRaw(req.Object.Raw, marshaledBytes)
}
func (e *workloadInterpreter) responseWithExploreRetaining(desiredWorkload *workloadv1alpha1.Workload, req interpreter.Request) interpreter.Response {
if req.ObservedObject == nil {
err := fmt.Errorf("nil observedObject in exploreReview with operation type: %s", req.Operation)
return interpreter.Errored(http.StatusBadRequest, err)
}
observerWorkload := &workloadv1alpha1.Workload{}
err := e.decoder.DecodeRaw(*req.ObservedObject, observerWorkload)
if err != nil {
return interpreter.Errored(http.StatusBadRequest, err)
}
// Suppose we want to retain the `.spec.paused` field of the actual observed workload object in member cluster,
// and prevent from being overwritten by karmada controller-plane.
wantedWorkload := desiredWorkload.DeepCopy()
wantedWorkload.Spec.Paused = observerWorkload.Spec.Paused
marshaledBytes, err := json.Marshal(wantedWorkload)
if err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
return interpreter.PatchResponseFromRaw(req.Object.Raw, marshaledBytes)
}
func (e *workloadInterpreter) responseWithExploreAggregateStatus(workload *workloadv1alpha1.Workload, req interpreter.Request) interpreter.Response {
wantedWorkload := workload.DeepCopy()
var readyReplicas int32
for _, item := range req.AggregatedStatus {
if item.Status == nil {
continue
}
status := &workloadv1alpha1.WorkloadStatus{}
if err := json.Unmarshal(item.Status.Raw, status); err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
readyReplicas += status.ReadyReplicas
}
wantedWorkload.Status.ReadyReplicas = readyReplicas
marshaledBytes, err := json.Marshal(wantedWorkload)
if err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
return interpreter.PatchResponseFromRaw(req.Object.Raw, marshaledBytes)
}
func (e *workloadInterpreter) responseWithExploreInterpretHealth(workload *workloadv1alpha1.Workload) interpreter.Response {
healthy := pointer.Bool(false)
if workload.Status.ReadyReplicas == *workload.Spec.Replicas {
healthy = pointer.Bool(true)
}
res := interpreter.Succeeded("")
res.Healthy = healthy
return res
}
func (e *workloadInterpreter) responseWithExploreInterpretStatus(workload *workloadv1alpha1.Workload) interpreter.Response {
status := workloadv1alpha1.WorkloadStatus{
ReadyReplicas: workload.Status.ReadyReplicas,
}
marshaledBytes, err := json.Marshal(status)
if err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
res := interpreter.Succeeded("")
res.RawStatus = &runtime.RawExtension{
Raw: marshaledBytes,
}
return res
}