mirror of
https://github.com/niusmallnan/steve.git
synced 2025-08-14 11:35:25 +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/clustercache"
|
||||
"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/counts"
|
||||
"github.com/rancher/steve/pkg/resources/formatters"
|
||||
"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/summarycache"
|
||||
"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)
|
||||
subscribe.Register(baseSchema)
|
||||
apiroot.Register(baseSchema, []string{"v1"}, "proxy:/apis")
|
||||
return baseSchema, nil
|
||||
cluster.Register(ctx, baseSchema, cg, schemaFactory)
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefaultSchemaTemplates(cf *client.Factory,
|
||||
baseSchemas *types.APISchemas,
|
||||
summaryCache *summarycache.SummaryCache,
|
||||
lookup accesscontrol.AccessSetLookup,
|
||||
discovery discovery.DiscoveryInterface) []schema.Template {
|
||||
@ -45,5 +50,11 @@ func DefaultSchemaTemplates(cf *client.Factory,
|
||||
ID: "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())
|
||||
server.ClusterCache = ccache
|
||||
sf := schema.NewCollection(ctx, server.BaseSchemas, asl)
|
||||
|
||||
server.BaseSchemas, err = resources.DefaultSchemas(ctx, server.BaseSchemas, ccache, cf)
|
||||
if err != nil {
|
||||
if err = resources.DefaultSchemas(ctx, server.BaseSchemas, ccache, server.ClientFactory, sf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sf := schema.NewCollection(ctx, server.BaseSchemas, asl)
|
||||
summaryCache := summarycache.New(sf, ccache)
|
||||
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)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user