Add write operation for dynamic resource (#5601)

add write operation for dynamic resource

Signed-off-by: wenhaozhou <wenhaozhou@yunify.com>
This commit is contained in:
Wenhao Zhou
2023-05-12 11:33:48 +08:00
committed by GitHub
parent 967861ff63
commit 53d4900816
4 changed files with 150 additions and 20 deletions

View File

@@ -2,12 +2,14 @@ package filters
import (
"fmt"
"io"
"net/http"
"github.com/emicklei/go-restful/v3"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"sigs.k8s.io/controller-runtime/pkg/client"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
@@ -15,6 +17,8 @@ import (
"kubesphere.io/kubesphere/pkg/models/resources/v1beta1"
)
var NotSupportedVerbError = fmt.Errorf("not supported verb")
type DynamicResourceHandler struct {
v1beta1.ResourceManager
serviceErrorHandleFallback restful.ServiceErrorHandleFunction
@@ -34,12 +38,6 @@ func (d *DynamicResourceHandler) HandleServiceError(serviceError restful.Service
return
}
// TODO support write operation and workspace scope API
if req.Request.Method != http.MethodGet {
d.serviceErrorHandleFallback(serviceError, req, w)
return
}
reqInfo, exist := request.RequestInfoFrom(req.Request.Context())
if !exist {
responsewriters.InternalError(w, req.Request, fmt.Errorf("no RequestInfo found in the context"))
@@ -75,11 +73,38 @@ func (d *DynamicResourceHandler) HandleServiceError(serviceError restful.Service
return
}
var object client.Object
if reqInfo.Verb == request.VerbCreate || reqInfo.Verb == request.VerbUpdate || reqInfo.Verb == request.VerbPatch {
rawData, err := io.ReadAll(req.Request.Body)
if err != nil {
api.HandleError(w, req, err)
return
}
object, err = d.CreateObjectFromRawData(gvr, rawData)
if err != nil {
api.HandleError(w, req, err)
return
}
}
var result interface{}
if reqInfo.Verb == "list" {
switch reqInfo.Verb {
case request.VerbGet:
result, err = d.GetResource(req.Request.Context(), gvr, reqInfo.Namespace, reqInfo.Name)
case request.VerbList:
result, err = d.ListResources(req.Request.Context(), gvr, reqInfo.Namespace, query.ParseQueryParameter(req))
} else {
result, err = d.GetResource(req.Request.Context(), gvr, reqInfo.Name, reqInfo.Namespace)
case request.VerbCreate:
err = d.CreateResource(req.Request.Context(), object)
case request.VerbUpdate:
err = d.UpdateResource(req.Request.Context(), object)
case request.VerbDelete:
err = d.DeleteResource(req.Request.Context(), gvr, reqInfo.Namespace, reqInfo.Name)
case request.VerbPatch:
err = d.PatchResource(req.Request.Context(), object)
default:
err = NotSupportedVerbError
}
if err != nil {

View File

@@ -39,6 +39,16 @@ import (
"kubesphere.io/kubesphere/pkg/utils/iputil"
)
const (
VerbCreate = "create"
VerbGet = "get"
VerbList = "list"
VerbUpdate = "update"
VerbDelete = "delete"
VerbWatch = "watch"
VerbPatch = "patch"
)
type RequestInfoResolver interface {
NewRequestInfo(req *http.Request) (*RequestInfo, error)
}
@@ -191,15 +201,15 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
} else {
switch req.Method {
case "POST":
requestInfo.Verb = "create"
requestInfo.Verb = VerbCreate
case "GET", "HEAD":
requestInfo.Verb = "get"
requestInfo.Verb = VerbGet
case "PUT":
requestInfo.Verb = "update"
requestInfo.Verb = VerbUpdate
case "PATCH":
requestInfo.Verb = "patch"
requestInfo.Verb = VerbPatch
case "DELETE":
requestInfo.Verb = "delete"
requestInfo.Verb = VerbDelete
default:
requestInfo.Verb = ""
}
@@ -259,7 +269,7 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
requestInfo.ResourceScope = r.resolveResourceScope(requestInfo)
// if there's no name on the request and we thought it was a get before, then the actual verb is a list or a watch
if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
if len(requestInfo.Name) == 0 && requestInfo.Verb == VerbGet {
opts := metainternalversion.ListOptions{}
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil {
// An error in parsing request will result in default to "list" and not setting "name" field.
@@ -277,9 +287,9 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
}
if opts.Watch {
requestInfo.Verb = "watch"
requestInfo.Verb = VerbWatch
} else {
requestInfo.Verb = "list"
requestInfo.Verb = VerbList
}
if opts.FieldSelector != nil {
@@ -292,7 +302,7 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
}
// URL forms: /api/v1/watch/namespaces?labelSelector=kubesphere.io/workspace=system-workspace
if requestInfo.Verb == "watch" {
if requestInfo.Verb == VerbWatch {
selector := req.URL.Query().Get("labelSelector")
if strings.HasPrefix(selector, workspaceSelectorPrefix) {
workspace := strings.TrimPrefix(selector, workspaceSelectorPrefix)
@@ -302,7 +312,7 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
}
// if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" {
if len(requestInfo.Name) == 0 && requestInfo.Verb == VerbDelete {
requestInfo.Verb = "deletecollection"
}

View File

@@ -21,10 +21,21 @@ import (
type ResourceManager interface {
IsServed(schema.GroupVersionResource) (bool, error)
CreateObjectFromRawData(gvr schema.GroupVersionResource, rawData []byte) (client.Object, error)
CreateResource(ctx context.Context, object client.Object) error
UpdateResource(ctx context.Context, object client.Object) error
PatchResource(ctx context.Context, object client.Object) error
DeleteResource(ctx context.Context, gvr schema.GroupVersionResource, namespace string, name string) error
GetResource(ctx context.Context, gvr schema.GroupVersionResource, namespace string, name string) (client.Object, error)
ListResources(ctx context.Context, gvr schema.GroupVersionResource, namespace string, query *query.Query) (client.ObjectList, error)
Get(ctx context.Context, namespace, name string, object client.Object) error
List(ctx context.Context, namespace string, query *query.Query, object client.ObjectList) error
Create(ctx context.Context, object client.Object) error
Delete(ctx context.Context, object client.Object) error
Update(ctx context.Context, old, new client.Object) error
Patch(ctx context.Context, old, new client.Object) error
}
// CompareFunc return true is left greater than right

View File

@@ -2,6 +2,7 @@ package v1beta1
import (
"context"
"encoding/json"
"strings"
"k8s.io/apimachinery/pkg/api/errors"
@@ -19,7 +20,8 @@ import (
const labelResourceServed = "kubesphere.io/resource-served"
// TODO If delete the crd at the cluster when ks is running, the client.cache doesn`t return err but empty result
// Note that: If delete the crd at the cluster when is running, the client.cache does not return err but empty result
func New(client client.Client, cache cache.Cache) ResourceManager {
return &resourceManager{
client: client,
@@ -57,6 +59,39 @@ func (h *resourceManager) GetResource(ctx context.Context, gvr schema.GroupVersi
return obj, nil
}
func (h *resourceManager) CreateObjectFromRawData(gvr schema.GroupVersionResource, rawData []byte) (client.Object, error) {
var obj client.Object
gvk, err := h.getGVK(gvr)
if err != nil {
return nil, err
}
if h.client.Scheme().Recognizes(gvk) {
gvkObject, err := h.client.Scheme().New(gvk)
if err != nil {
return nil, err
}
obj = gvkObject.(client.Object)
} else {
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
obj = u
}
err = json.Unmarshal(rawData, obj)
if err != nil {
return nil, err
}
// The object`s GroupVersionKind could be overridden if apiVersion and kind of rawData are different
// with GroupVersionKind from url, so that we should check GroupVersionKind after Unmarshal rawDate.
if obj.GetObjectKind().GroupVersionKind().String() != gvk.String() {
return nil, errors.NewBadRequest("wrong resource GroupVersionKind")
}
return obj, nil
}
func (h *resourceManager) ListResources(ctx context.Context, gvr schema.GroupVersionResource, namespace string, query *query.Query) (client.ObjectList, error) {
var obj client.ObjectList
@@ -85,6 +120,38 @@ func (h *resourceManager) ListResources(ctx context.Context, gvr schema.GroupVer
return obj, nil
}
func (h *resourceManager) DeleteResource(ctx context.Context, gvr schema.GroupVersionResource, namespace, name string) error {
resource, err := h.GetResource(ctx, gvr, namespace, name)
if err != nil {
return err
}
return h.Delete(ctx, resource)
}
func (h *resourceManager) UpdateResource(ctx context.Context, object client.Object) error {
old := object.DeepCopyObject().(client.Object)
err := h.Get(ctx, object.GetNamespace(), object.GetName(), old)
if err != nil {
return err
}
return h.Update(ctx, old, object)
}
func (h *resourceManager) PatchResource(ctx context.Context, object client.Object) error {
old := object.DeepCopyObject().(client.Object)
err := h.Get(ctx, object.GetNamespace(), object.GetName(), old)
if err != nil {
return err
}
return h.Patch(ctx, old, object)
}
func (h *resourceManager) CreateResource(ctx context.Context, object client.Object) error {
return h.Create(ctx, object)
}
func convertGVKToList(gvk schema.GroupVersionKind) schema.GroupVersionKind {
if strings.HasSuffix(gvk.Kind, "List") {
return gvk
@@ -154,6 +221,23 @@ func (h *resourceManager) List(ctx context.Context, namespace string, query *que
return nil
}
func (h *resourceManager) Create(ctx context.Context, object client.Object) error {
return h.client.Create(ctx, object)
}
func (h *resourceManager) Delete(ctx context.Context, object client.Object) error {
return h.client.Delete(ctx, object)
}
func (h *resourceManager) Update(ctx context.Context, old, new client.Object) error {
new.SetResourceVersion(old.GetResourceVersion())
return h.client.Update(ctx, new)
}
func (h *resourceManager) Patch(ctx context.Context, old, new client.Object) error {
return h.client.Patch(ctx, new, client.MergeFrom(old))
}
func compare(left, right runtime.Object, field query.Field) bool {
l, err := meta.Accessor(left)
if err != nil {