mirror of
https://github.com/niusmallnan/steve.git
synced 2025-09-12 21:14:06 +00:00
Refactor
This commit is contained in:
66
pkg/server/store/proxy/client.go
Normal file
66
pkg/server/store/proxy/client.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type ClientFactory struct {
|
||||
cfg rest.Config
|
||||
client dynamic.Interface
|
||||
impersonate bool
|
||||
idToGVR map[string]schema.GroupVersionResource
|
||||
}
|
||||
|
||||
func NewClientFactory(cfg *rest.Config, impersonate bool) *ClientFactory {
|
||||
return &ClientFactory{
|
||||
impersonate: impersonate,
|
||||
cfg: *cfg,
|
||||
idToGVR: map[string]schema.GroupVersionResource{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ClientFactory) Client(ctx *types.APIRequest, schema *types.APISchema) (dynamic.ResourceInterface, error) {
|
||||
gvr := attributes.GVR(schema)
|
||||
if gvr.Resource == "" {
|
||||
return nil, httperror.NewAPIError(validation.NotFound, "Failed to find gvr for "+schema.ID)
|
||||
}
|
||||
|
||||
user, ok := request.UserFrom(ctx.Request.Context())
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to find user context for client")
|
||||
}
|
||||
|
||||
client, err := p.getClient(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.Resource(gvr), nil
|
||||
}
|
||||
|
||||
func (p *ClientFactory) getClient(user user.Info) (dynamic.Interface, error) {
|
||||
if p.impersonate {
|
||||
return p.client, nil
|
||||
}
|
||||
|
||||
if user.GetName() == "" {
|
||||
return nil, fmt.Errorf("failed to determine current user")
|
||||
}
|
||||
|
||||
newCfg := p.cfg
|
||||
newCfg.Impersonate.UserName = user.GetName()
|
||||
newCfg.Impersonate.Groups = user.GetGroups()
|
||||
newCfg.Impersonate.Extra = user.GetExtra()
|
||||
|
||||
return dynamic.NewForConfig(&newCfg)
|
||||
}
|
56
pkg/server/store/proxy/error_wrapper.go
Normal file
56
pkg/server/store/proxy/error_wrapper.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/rancher/steve/pkg/schemaserver/httperror"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
type errorStore struct {
|
||||
types.Store
|
||||
}
|
||||
|
||||
func (e *errorStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
data, err := e.Store.ByID(apiOp, schema, id)
|
||||
return data, translateError(err)
|
||||
}
|
||||
|
||||
func (e *errorStore) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
|
||||
data, err := e.Store.List(apiOp, schema)
|
||||
return data, translateError(err)
|
||||
}
|
||||
|
||||
func (e *errorStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) {
|
||||
data, err := e.Store.Create(apiOp, schema, data)
|
||||
return data, translateError(err)
|
||||
|
||||
}
|
||||
|
||||
func (e *errorStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (types.APIObject, error) {
|
||||
data, err := e.Store.Update(apiOp, schema, data, id)
|
||||
return data, translateError(err)
|
||||
|
||||
}
|
||||
|
||||
func (e *errorStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
data, err := e.Store.Delete(apiOp, schema, id)
|
||||
return data, translateError(err)
|
||||
|
||||
}
|
||||
|
||||
func (e *errorStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types.WatchRequest) (chan types.APIEvent, error) {
|
||||
data, err := e.Store.Watch(apiOp, schema, wr)
|
||||
return data, translateError(err)
|
||||
}
|
||||
|
||||
func translateError(err error) error {
|
||||
if apiError, ok := err.(errors.APIStatus); ok {
|
||||
status := apiError.Status()
|
||||
return httperror.NewAPIError(validation.ErrorCode{
|
||||
Status: int(status.Code),
|
||||
Code: string(status.Reason),
|
||||
}, status.Message)
|
||||
}
|
||||
return err
|
||||
}
|
303
pkg/server/store/proxy/proxy_store.go
Normal file
303
pkg/server/store/proxy/proxy_store.go
Normal file
@@ -0,0 +1,303 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/schemaserver/types"
|
||||
"github.com/rancher/wrangler/pkg/data"
|
||||
"github.com/rancher/wrangler/pkg/schemas/validation"
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
var (
|
||||
lowerChars = regexp.MustCompile("[a-z]+")
|
||||
)
|
||||
|
||||
type ClientGetter interface {
|
||||
Client(ctx *types.APIRequest, schema *types.APISchema, namespace string) (dynamic.ResourceInterface, error)
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
clientGetter ClientGetter
|
||||
}
|
||||
|
||||
func NewProxyStore(clientGetter ClientGetter) types.Store {
|
||||
return &errorStore{
|
||||
Store: &Store{
|
||||
clientGetter: clientGetter,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
result, err := s.byID(apiOp, schema, id)
|
||||
return toAPI(schema, result), err
|
||||
}
|
||||
|
||||
func decodeParams(apiOp *types.APIRequest, target runtime.Object) error {
|
||||
return metav1.ParameterCodec.DecodeParameters(apiOp.Request.URL.Query(), metav1.SchemeGroupVersion, target)
|
||||
}
|
||||
|
||||
func toAPI(schema *types.APISchema, obj *unstructured.Unstructured) types.APIObject {
|
||||
if obj == nil {
|
||||
return types.APIObject{}
|
||||
}
|
||||
|
||||
gvr := attributes.GVR(schema)
|
||||
|
||||
id := obj.GetName()
|
||||
ns := obj.GetNamespace()
|
||||
if ns != "" {
|
||||
id = fmt.Sprintf("%s/%s", ns, id)
|
||||
}
|
||||
t := fmt.Sprintf("%s/%s/%s", gvr.Group, gvr.Version, gvr.Resource)
|
||||
return types.APIObject{
|
||||
Type: t,
|
||||
ID: id,
|
||||
Object: obj,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) byID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, error) {
|
||||
k8sClient, err := s.clientGetter.Client(apiOp, schema, apiOp.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := metav1.GetOptions{}
|
||||
if err := decodeParams(apiOp, &opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k8sClient.Get(id, opts)
|
||||
}
|
||||
|
||||
func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
|
||||
k8sClient, err := s.clientGetter.Client(apiOp, schema, apiOp.Namespace)
|
||||
if err != nil {
|
||||
return types.APIObjectList{}, err
|
||||
}
|
||||
|
||||
opts := metav1.ListOptions{}
|
||||
if err := decodeParams(apiOp, &opts); err != nil {
|
||||
return types.APIObjectList{}, nil
|
||||
}
|
||||
|
||||
resultList, err := k8sClient.List(opts)
|
||||
if err != nil {
|
||||
return types.APIObjectList{}, err
|
||||
}
|
||||
|
||||
result := types.APIObjectList{
|
||||
Revision: resultList.GetResourceVersion(),
|
||||
Continue: resultList.GetContinue(),
|
||||
}
|
||||
|
||||
for i := range resultList.Items {
|
||||
result.Objects = append(result.Objects, toAPI(schema, &resultList.Items[i]))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func returnErr(err error, c chan types.APIEvent) {
|
||||
c <- types.APIEvent{
|
||||
Name: "resource.error",
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) listAndWatch(apiOp *types.APIRequest, k8sClient dynamic.ResourceInterface, schema *types.APISchema, w types.WatchRequest, result chan types.APIEvent) {
|
||||
rev := w.Revision
|
||||
if rev == "" {
|
||||
list, err := k8sClient.List(metav1.ListOptions{
|
||||
Limit: 1,
|
||||
})
|
||||
if err != nil {
|
||||
returnErr(errors.Wrapf(err, "failed to list %s", schema.ID), result)
|
||||
return
|
||||
}
|
||||
rev = list.GetResourceVersion()
|
||||
} else if rev == "-1" {
|
||||
rev = ""
|
||||
}
|
||||
|
||||
timeout := int64(60 * 30)
|
||||
watcher, err := k8sClient.Watch(metav1.ListOptions{
|
||||
Watch: true,
|
||||
TimeoutSeconds: &timeout,
|
||||
ResourceVersion: rev,
|
||||
})
|
||||
if err != nil {
|
||||
returnErr(errors.Wrapf(err, "stopping watch for %s: %v", schema.ID, err), result)
|
||||
return
|
||||
}
|
||||
defer watcher.Stop()
|
||||
logrus.Debugf("opening watcher for %s", schema.ID)
|
||||
|
||||
go func() {
|
||||
<-apiOp.Request.Context().Done()
|
||||
watcher.Stop()
|
||||
}()
|
||||
|
||||
for event := range watcher.ResultChan() {
|
||||
data := event.Object.(*unstructured.Unstructured)
|
||||
result <- s.toAPIEvent(apiOp, schema, event.Type, data)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) {
|
||||
k8sClient, err := s.clientGetter.Client(apiOp, schema, apiOp.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(chan types.APIEvent)
|
||||
go func() {
|
||||
s.listAndWatch(apiOp, k8sClient, schema, w, result)
|
||||
logrus.Debugf("closing watcher for %s", schema.ID)
|
||||
close(result)
|
||||
}()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Store) toAPIEvent(apiOp *types.APIRequest, schema *types.APISchema, et watch.EventType, obj *unstructured.Unstructured) types.APIEvent {
|
||||
name := types.ChangeAPIEvent
|
||||
switch et {
|
||||
case watch.Deleted:
|
||||
name = types.RemoveAPIEvent
|
||||
case watch.Added:
|
||||
name = types.CreateAPIEvent
|
||||
}
|
||||
|
||||
return types.APIEvent{
|
||||
Name: name,
|
||||
Revision: obj.GetResourceVersion(),
|
||||
Object: toAPI(schema, obj),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject) (types.APIObject, error) {
|
||||
var (
|
||||
resp *unstructured.Unstructured
|
||||
)
|
||||
|
||||
input := params.Data()
|
||||
|
||||
if input == nil {
|
||||
input = data.Object{}
|
||||
}
|
||||
|
||||
name := types.Name(input)
|
||||
ns := types.Namespace(input)
|
||||
if name == "" && input.String("metadata", "generateName") == "" {
|
||||
input.SetNested(schema.ID[0:1]+"-", "metadata", "generatedName")
|
||||
}
|
||||
|
||||
gvk := attributes.GVK(schema)
|
||||
input["apiVersion"], input["kind"] = gvk.ToAPIVersionAndKind()
|
||||
|
||||
k8sClient, err := s.clientGetter.Client(apiOp, schema, ns)
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
opts := metav1.CreateOptions{}
|
||||
if err := decodeParams(apiOp, &opts); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
resp, err = k8sClient.Create(&unstructured.Unstructured{Object: input}, opts)
|
||||
return toAPI(schema, resp), err
|
||||
}
|
||||
|
||||
func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, params types.APIObject, id string) (types.APIObject, error) {
|
||||
var (
|
||||
err error
|
||||
input = params.Data()
|
||||
)
|
||||
|
||||
ns := types.Namespace(input)
|
||||
k8sClient, err := s.clientGetter.Client(apiOp, schema, ns)
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
if apiOp.Method == http.MethodPatch {
|
||||
bytes, err := ioutil.ReadAll(io.LimitReader(apiOp.Request.Body, 2<<20))
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
pType := apitypes.StrategicMergePatchType
|
||||
if apiOp.Request.Header.Get("content-type") == string(apitypes.JSONPatchType) {
|
||||
pType = apitypes.JSONPatchType
|
||||
}
|
||||
|
||||
opts := metav1.PatchOptions{}
|
||||
if err := decodeParams(apiOp, &opts); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
resp, err := k8sClient.Patch(id, pType, bytes, opts)
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
return toAPI(schema, resp), nil
|
||||
}
|
||||
|
||||
resourceVersion := input.String("metadata", "resourceVersion")
|
||||
if resourceVersion == "" {
|
||||
return types.APIObject{}, fmt.Errorf("metadata.resourceVersion is required for update")
|
||||
}
|
||||
|
||||
opts := metav1.UpdateOptions{}
|
||||
if err := decodeParams(apiOp, &opts); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
resp, err := k8sClient.Update(&unstructured.Unstructured{Object: input}, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
return toAPI(schema, resp), nil
|
||||
}
|
||||
|
||||
func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||
opts := metav1.DeleteOptions{}
|
||||
if err := decodeParams(apiOp, &opts); err != nil {
|
||||
return types.APIObject{}, nil
|
||||
}
|
||||
|
||||
k8sClient, err := s.clientGetter.Client(apiOp, schema, apiOp.Namespace)
|
||||
if err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
if err := k8sClient.Delete(id, &opts); err != nil {
|
||||
return types.APIObject{}, err
|
||||
}
|
||||
|
||||
obj, err := s.byID(apiOp, schema, id)
|
||||
if err != nil {
|
||||
// ignore lookup error
|
||||
return types.APIObject{}, validation.ErrorCode{
|
||||
Status: http.StatusNoContent,
|
||||
}
|
||||
}
|
||||
return toAPI(schema, obj), nil
|
||||
}
|
Reference in New Issue
Block a user