Add fake cluster object and move apply action to core steve

This commit is contained in:
Darren Shepherd 2021-03-01 17:12:28 -07:00
parent 2cf9f857b0
commit df96a3bd4a
6 changed files with 369 additions and 6 deletions

View 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
}

View 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
}

View 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"`
}

View 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"`
}

View File

@ -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)
},
},
}
}

View File

@ -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)
}