mirror of
https://github.com/niusmallnan/steve.git
synced 2025-08-15 03:53:02 +00:00
Add fake cluster object and move apply action to core steve
This commit is contained in:
parent
2cf9f857b0
commit
df96a3bd4a
124
pkg/resources/cluster/apply.go
Normal file
124
pkg/resources/cluster/apply.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pborman/uuid"
|
||||||
|
"github.com/rancher/apiserver/pkg/types"
|
||||||
|
"github.com/rancher/steve/pkg/attributes"
|
||||||
|
steveschema "github.com/rancher/steve/pkg/schema"
|
||||||
|
"github.com/rancher/steve/pkg/stores/proxy"
|
||||||
|
"github.com/rancher/wrangler/pkg/apply"
|
||||||
|
"github.com/rancher/wrangler/pkg/yaml"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Apply struct {
|
||||||
|
cg proxy.ClientGetter
|
||||||
|
schemaFactory steveschema.Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Apply) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
var (
|
||||||
|
apiContext = types.GetAPIContext(req.Context())
|
||||||
|
input ApplyInput
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&input); err != nil {
|
||||||
|
apiContext.WriteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objs, err := yaml.ToObjects(bytes.NewBufferString(input.YAML))
|
||||||
|
if err != nil {
|
||||||
|
apiContext.WriteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apply, err := a.createApply(apiContext)
|
||||||
|
if err != nil {
|
||||||
|
apiContext.WriteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := apply.WithDefaultNamespace(input.DefaultNamespace).ApplyObjects(objs...); err != nil {
|
||||||
|
apiContext.WriteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var result types.APIObjectList
|
||||||
|
|
||||||
|
for _, obj := range objs {
|
||||||
|
result.Objects = append(result.Objects, a.toAPIObject(apiContext, obj, input.DefaultNamespace))
|
||||||
|
}
|
||||||
|
|
||||||
|
apiContext.WriteResponseList(http.StatusOK, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Apply) toAPIObject(apiContext *types.APIRequest, obj runtime.Object, defaultNamespace string) types.APIObject {
|
||||||
|
if defaultNamespace == "" {
|
||||||
|
defaultNamespace = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
result := types.APIObject{
|
||||||
|
Object: obj,
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaID := a.schemaFactory.ByGVK(obj.GetObjectKind().GroupVersionKind())
|
||||||
|
apiSchema := apiContext.Schemas.LookupSchema(schemaID)
|
||||||
|
if apiSchema != nil {
|
||||||
|
id := m.GetName()
|
||||||
|
ns := m.GetNamespace()
|
||||||
|
|
||||||
|
if ns == "" && attributes.Namespaced(apiSchema) {
|
||||||
|
ns = defaultNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
if ns != "" {
|
||||||
|
id = fmt.Sprintf("%s/%s", ns, id)
|
||||||
|
}
|
||||||
|
result.ID = id
|
||||||
|
result.Type = apiSchema.ID
|
||||||
|
|
||||||
|
if apiSchema.Store != nil {
|
||||||
|
apiContext := apiContext.Clone()
|
||||||
|
apiContext.Namespace = ns
|
||||||
|
if obj, err := apiSchema.Store.ByID(apiContext.Clone(), apiSchema, m.GetName()); err == nil {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Apply) createApply(apiContext *types.APIRequest) (apply.Apply, error) {
|
||||||
|
client, err := a.cg.K8sInterface(apiContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apply := apply.New(client.Discovery(), func(gvr schema.GroupVersionResource) (dynamic.NamespaceableResourceInterface, error) {
|
||||||
|
dynamicClient, err := a.cg.DynamicClient(apiContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dynamicClient.Resource(gvr), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return apply.
|
||||||
|
WithDynamicLookup().
|
||||||
|
WithContext(apiContext.Context()).
|
||||||
|
WithSetID(uuid.New()), nil
|
||||||
|
}
|
188
pkg/resources/cluster/cluster.go
Normal file
188
pkg/resources/cluster/cluster.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rancher/apiserver/pkg/store/empty"
|
||||||
|
"github.com/rancher/apiserver/pkg/types"
|
||||||
|
detector "github.com/rancher/kubernetes-provider-detector"
|
||||||
|
"github.com/rancher/steve/pkg/accesscontrol"
|
||||||
|
"github.com/rancher/steve/pkg/attributes"
|
||||||
|
steveschema "github.com/rancher/steve/pkg/schema"
|
||||||
|
"github.com/rancher/steve/pkg/stores/proxy"
|
||||||
|
"github.com/rancher/wrangler/pkg/genericcondition"
|
||||||
|
"github.com/rancher/wrangler/pkg/schemas"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/version"
|
||||||
|
"k8s.io/client-go/discovery"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(ctx context.Context, apiSchemas *types.APISchemas, cg proxy.ClientGetter, schemaFactory steveschema.Factory) {
|
||||||
|
apiSchemas.InternalSchemas.TypeName("management.cattle.io.cluster", Cluster{})
|
||||||
|
apiSchemas.MustImportAndCustomize(Cluster{}, func(schema *types.APISchema) {
|
||||||
|
schema.CollectionMethods = []string{http.MethodGet}
|
||||||
|
schema.ResourceMethods = []string{http.MethodGet}
|
||||||
|
schema.Attributes["access"] = accesscontrol.AccessListByVerb{
|
||||||
|
"watch": accesscontrol.AccessList{
|
||||||
|
{
|
||||||
|
Namespace: "*",
|
||||||
|
ResourceName: "*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
schema.Store = &Store{
|
||||||
|
provider: provider(ctx, cg),
|
||||||
|
discovery: discoveryClient(cg),
|
||||||
|
}
|
||||||
|
attributes.SetGVK(schema, schema2.GroupVersionKind{
|
||||||
|
Group: "management.cattle.io",
|
||||||
|
Version: "v3",
|
||||||
|
Kind: "Cluster",
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.ActionHandlers = map[string]http.Handler{
|
||||||
|
"apply": &Apply{
|
||||||
|
cg: cg,
|
||||||
|
schemaFactory: schemaFactory,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
schema.ResourceActions = map[string]schemas.Action{
|
||||||
|
"apply": {
|
||||||
|
Input: "applyInput",
|
||||||
|
Output: "applyOutput",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func discoveryClient(cg proxy.ClientGetter) discovery.DiscoveryInterface {
|
||||||
|
k8s, err := cg.AdminK8sInterface()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return k8s.Discovery()
|
||||||
|
}
|
||||||
|
|
||||||
|
func provider(ctx context.Context, cg proxy.ClientGetter) string {
|
||||||
|
var (
|
||||||
|
provider string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
k8s, err := cg.AdminK8sInterface()
|
||||||
|
if err == nil {
|
||||||
|
provider, _ = detector.DetectProvider(ctx, k8s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddApply(apiSchemas *types.APISchemas, schema *types.APISchema) {
|
||||||
|
if _, ok := schema.ActionHandlers["apply"]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cluster := apiSchemas.LookupSchema("management.cattle.io.cluster")
|
||||||
|
if cluster == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
actionHandler, ok := cluster.ActionHandlers["apply"]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if schema.ActionHandlers == nil {
|
||||||
|
schema.ActionHandlers = map[string]http.Handler{}
|
||||||
|
}
|
||||||
|
schema.ActionHandlers["apply"] = actionHandler
|
||||||
|
|
||||||
|
if schema.ResourceActions == nil {
|
||||||
|
schema.ResourceActions = map[string]schemas.Action{}
|
||||||
|
}
|
||||||
|
schema.ResourceActions["apply"] = schemas.Action{
|
||||||
|
Input: "applyInput",
|
||||||
|
Output: "applyOutput",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
empty.Store
|
||||||
|
provider string
|
||||||
|
discovery discovery.DiscoveryInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
|
||||||
|
if apiOp.Namespace == "" && id == "local" {
|
||||||
|
return s.getLocal(), nil
|
||||||
|
}
|
||||||
|
return s.Store.ByID(apiOp, schema, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) getLocal() types.APIObject {
|
||||||
|
var (
|
||||||
|
info *version.Info
|
||||||
|
)
|
||||||
|
|
||||||
|
if s.discovery != nil {
|
||||||
|
info, _ = s.discovery.ServerVersion()
|
||||||
|
}
|
||||||
|
return types.APIObject{
|
||||||
|
ID: "local",
|
||||||
|
Object: &Cluster{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Cluster",
|
||||||
|
APIVersion: "management.cattle.io/v3",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "local",
|
||||||
|
},
|
||||||
|
Spec: Spec{
|
||||||
|
DisplayName: "Local Cluster",
|
||||||
|
Internal: true,
|
||||||
|
},
|
||||||
|
Status: Status{
|
||||||
|
Version: info,
|
||||||
|
Driver: "local",
|
||||||
|
Provider: s.provider,
|
||||||
|
Conditions: []genericcondition.GenericCondition{
|
||||||
|
{
|
||||||
|
Type: "Ready",
|
||||||
|
Status: "True",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
|
||||||
|
if apiOp.Namespace != "" {
|
||||||
|
return s.Store.List(apiOp, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.APIObjectList{
|
||||||
|
Objects: []types.APIObject{
|
||||||
|
s.getLocal(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) {
|
||||||
|
result := make(chan types.APIEvent, 1)
|
||||||
|
go func() {
|
||||||
|
<-apiOp.Context().Done()
|
||||||
|
close(result)
|
||||||
|
}()
|
||||||
|
|
||||||
|
result <- types.APIEvent{
|
||||||
|
Name: "local",
|
||||||
|
ResourceType: "management.cattle.io.clusters",
|
||||||
|
ID: "local",
|
||||||
|
Object: s.getLocal(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
27
pkg/resources/cluster/cluster_type.go
Normal file
27
pkg/resources/cluster/cluster_type.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rancher/wrangler/pkg/genericcondition"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cluster struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
Spec Spec `json:"spec"`
|
||||||
|
Status Status `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Spec struct {
|
||||||
|
DisplayName string `json:"displayName" norman:"required"`
|
||||||
|
Internal bool `json:"internal,omitempty"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Conditions []genericcondition.GenericCondition `json:"conditions,omitempty"`
|
||||||
|
Driver string `json:"driver,omitempty"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Version *version.Info `json:"version,omitempty"`
|
||||||
|
}
|
14
pkg/resources/cluster/rest.go
Normal file
14
pkg/resources/cluster/rest.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApplyInput struct {
|
||||||
|
DefaultNamespace string `json:"defaultNamespace,omitempty"`
|
||||||
|
YAML string `json:"yaml,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplyOutput struct {
|
||||||
|
Resources []runtime.Object `json:"resources,omitempty"`
|
||||||
|
}
|
@ -10,23 +10,28 @@ import (
|
|||||||
"github.com/rancher/steve/pkg/client"
|
"github.com/rancher/steve/pkg/client"
|
||||||
"github.com/rancher/steve/pkg/clustercache"
|
"github.com/rancher/steve/pkg/clustercache"
|
||||||
"github.com/rancher/steve/pkg/resources/apigroups"
|
"github.com/rancher/steve/pkg/resources/apigroups"
|
||||||
|
"github.com/rancher/steve/pkg/resources/cluster"
|
||||||
"github.com/rancher/steve/pkg/resources/common"
|
"github.com/rancher/steve/pkg/resources/common"
|
||||||
"github.com/rancher/steve/pkg/resources/counts"
|
"github.com/rancher/steve/pkg/resources/counts"
|
||||||
"github.com/rancher/steve/pkg/resources/formatters"
|
"github.com/rancher/steve/pkg/resources/formatters"
|
||||||
"github.com/rancher/steve/pkg/schema"
|
"github.com/rancher/steve/pkg/schema"
|
||||||
|
steveschema "github.com/rancher/steve/pkg/schema"
|
||||||
"github.com/rancher/steve/pkg/stores/proxy"
|
"github.com/rancher/steve/pkg/stores/proxy"
|
||||||
"github.com/rancher/steve/pkg/summarycache"
|
"github.com/rancher/steve/pkg/summarycache"
|
||||||
"k8s.io/client-go/discovery"
|
"k8s.io/client-go/discovery"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DefaultSchemas(ctx context.Context, baseSchema *types.APISchemas, ccache clustercache.ClusterCache, cg proxy.ClientGetter) (*types.APISchemas, error) {
|
func DefaultSchemas(ctx context.Context, baseSchema *types.APISchemas, ccache clustercache.ClusterCache,
|
||||||
|
cg proxy.ClientGetter, schemaFactory steveschema.Factory) error {
|
||||||
counts.Register(baseSchema, ccache)
|
counts.Register(baseSchema, ccache)
|
||||||
subscribe.Register(baseSchema)
|
subscribe.Register(baseSchema)
|
||||||
apiroot.Register(baseSchema, []string{"v1"}, "proxy:/apis")
|
apiroot.Register(baseSchema, []string{"v1"}, "proxy:/apis")
|
||||||
return baseSchema, nil
|
cluster.Register(ctx, baseSchema, cg, schemaFactory)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultSchemaTemplates(cf *client.Factory,
|
func DefaultSchemaTemplates(cf *client.Factory,
|
||||||
|
baseSchemas *types.APISchemas,
|
||||||
summaryCache *summarycache.SummaryCache,
|
summaryCache *summarycache.SummaryCache,
|
||||||
lookup accesscontrol.AccessSetLookup,
|
lookup accesscontrol.AccessSetLookup,
|
||||||
discovery discovery.DiscoveryInterface) []schema.Template {
|
discovery discovery.DiscoveryInterface) []schema.Template {
|
||||||
@ -45,5 +50,11 @@ func DefaultSchemaTemplates(cf *client.Factory,
|
|||||||
ID: "pod",
|
ID: "pod",
|
||||||
Formatter: formatters.Pod,
|
Formatter: formatters.Pod,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "management.cattle.io.cluster",
|
||||||
|
Customize: func(apiSchema *types.APISchema) {
|
||||||
|
cluster.AddApply(baseSchemas, apiSchema)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,17 +122,16 @@ func setup(ctx context.Context, server *Server) error {
|
|||||||
|
|
||||||
ccache := clustercache.NewClusterCache(ctx, cf.AdminDynamicClient())
|
ccache := clustercache.NewClusterCache(ctx, cf.AdminDynamicClient())
|
||||||
server.ClusterCache = ccache
|
server.ClusterCache = ccache
|
||||||
|
sf := schema.NewCollection(ctx, server.BaseSchemas, asl)
|
||||||
|
|
||||||
server.BaseSchemas, err = resources.DefaultSchemas(ctx, server.BaseSchemas, ccache, cf)
|
if err = resources.DefaultSchemas(ctx, server.BaseSchemas, ccache, server.ClientFactory, sf); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sf := schema.NewCollection(ctx, server.BaseSchemas, asl)
|
|
||||||
summaryCache := summarycache.New(sf, ccache)
|
summaryCache := summarycache.New(sf, ccache)
|
||||||
summaryCache.Start(ctx)
|
summaryCache.Start(ctx)
|
||||||
|
|
||||||
for _, template := range resources.DefaultSchemaTemplates(cf, summaryCache, asl, server.controllers.K8s.Discovery()) {
|
for _, template := range resources.DefaultSchemaTemplates(cf, server.BaseSchemas, summaryCache, asl, server.controllers.K8s.Discovery()) {
|
||||||
sf.AddTemplate(template)
|
sf.AddTemplate(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user