1
0
mirror of https://github.com/rancher/steve.git synced 2025-08-31 15:11:31 +00:00

Show server side columns

This commit is contained in:
Darren Shepherd
2020-02-08 13:04:20 -07:00
parent 9f771dcf65
commit 600f875fce
12 changed files with 171 additions and 243 deletions

View File

@@ -1,42 +0,0 @@
package common
import (
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/schema/table"
"github.com/rancher/steve/pkg/schemaserver/types"
"github.com/rancher/wrangler/pkg/schemas"
"github.com/rancher/wrangler/pkg/schemas/mappers"
)
var (
NameColumn = table.Column{
Name: "Name",
Field: "metadata.name",
Type: "string",
Format: "name",
}
CreatedColumn = table.Column{
Name: "Created",
Field: "metadata.creationTimestamp",
Type: "string",
Format: "date",
}
)
type DefaultColumns struct {
mappers.EmptyMapper
}
func (d *DefaultColumns) ModifySchema(schema *schemas.Schema, schemas *schemas.Schemas) error {
as := &types.APISchema{
Schema: schema,
}
if attributes.Columns(as) == nil {
attributes.SetColumns(as, []table.Column{
NameColumn,
CreatedColumn,
})
}
return nil
}

View File

@@ -1,11 +1,12 @@
package common
import (
"net/http"
"fmt"
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/schema/table"
"github.com/rancher/steve/pkg/schemaserver/types"
"github.com/rancher/wrangler/pkg/ratelimit"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
@@ -18,6 +19,11 @@ type DynamicColumns struct {
client *rest.RESTClient
}
type ColumnDefinition struct {
metav1.TableColumnDefinition `json:",inline"`
Field string `json:"field,omitempty"`
}
func NewDynamicColumns(config *rest.Config) (*DynamicColumns, error) {
c, err := newClient(config)
if err != nil {
@@ -28,15 +34,6 @@ func NewDynamicColumns(config *rest.Config) (*DynamicColumns, error) {
}, nil
}
func hasGet(methods []string) bool {
for _, method := range methods {
if method == http.MethodGet {
return true
}
}
return false
}
func (d *DynamicColumns) SetColumns(schema *types.APISchema) error {
if attributes.Columns(schema) != nil {
return nil
@@ -46,11 +43,6 @@ func (d *DynamicColumns) SetColumns(schema *types.APISchema) error {
if gvr.Resource == "" {
return nil
}
nsed := attributes.Namespaced(schema)
if !hasGet(schema.CollectionMethods) {
return nil
}
r := d.client.Get()
if gvr.Group == "" {
@@ -59,33 +51,29 @@ func (d *DynamicColumns) SetColumns(schema *types.APISchema) error {
r.Prefix("apis", gvr.Group)
}
r.Prefix(gvr.Version)
if nsed {
r.Prefix("namespaces", "default")
}
r.Prefix(gvr.Resource)
r.VersionedParams(&metav1.ListOptions{
Limit: 1,
}, metav1.ParameterCodec)
obj, err := r.Do().Get()
if err != nil {
return err
return nil
}
t, ok := obj.(*metav1.Table)
if !ok {
return nil
}
var cols []table.Column
for _, cd := range t.ColumnDefinitions {
cols = append(cols, table.Column{
Name: cd.Name,
Field: "metadata.computed.fields." + cd.Name,
Type: cd.Type,
Format: cd.Format,
})
}
if len(cols) > 0 {
if len(t.ColumnDefinitions) > 0 {
var cols []ColumnDefinition
for i, colDef := range t.ColumnDefinitions {
cols = append(cols, ColumnDefinition{
TableColumnDefinition: colDef,
Field: fmt.Sprintf("$.metadata.fields[%d]", i),
})
}
attributes.SetColumns(schema, cols)
schema.Attributes["server-side-column"] = "true"
}
return nil
@@ -93,6 +81,9 @@ func (d *DynamicColumns) SetColumns(schema *types.APISchema) error {
func newClient(config *rest.Config) (*rest.RESTClient, error) {
scheme := runtime.NewScheme()
if err := internalversion.AddToScheme(scheme); err != nil {
return nil, err
}
if err := metav1.AddMetaToScheme(scheme); err != nil {
return nil, err
}
@@ -101,9 +92,9 @@ func newClient(config *rest.Config) (*rest.RESTClient, error) {
}
config = rest.CopyConfig(config)
config.RateLimiter = ratelimit.None
config.UserAgent = rest.DefaultKubernetesUserAgent()
config.AcceptContentTypes = "application/json;as=Table;v=v1beta1;g=meta.k8s.io"
config.ContentType = "application/json;as=Table;v=v1beta1;g=meta.k8s.io"
config.AcceptContentTypes = "application/json;as=Table;v=v1;g=meta.k8s.io"
config.GroupVersion = &schema.GroupVersion{}
config.NegotiatedSerializer = serializer.NewCodecFactory(scheme)
config.APIPath = "/"

View File

@@ -11,7 +11,6 @@ func DefaultTemplate(clientGetter proxy.ClientGetter) schema.Template {
return schema.Template{
Store: proxy.NewProxyStore(clientGetter),
Formatter: Formatter,
Mapper: &DefaultColumns{},
}
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/rancher/steve/pkg/schemaserver/types"
"github.com/rancher/steve/pkg/server/handler"
"github.com/rancher/steve/pkg/server/resources"
"github.com/rancher/steve/pkg/server/resources/common"
)
var ErrConfigRequired = errors.New("rest config is required")
@@ -52,18 +53,24 @@ func setup(ctx context.Context, server *Server) (http.Handler, *schema.Collectio
return nil, nil, err
}
ccache := clustercache.NewClusterCache(ctx, cf.DynamicClient())
ccache := clustercache.NewClusterCache(ctx, cf.MetadataClient())
server.BaseSchemas = resources.DefaultSchemas(server.BaseSchemas, server.K8s.Discovery(), ccache)
server.SchemaTemplates = append(server.SchemaTemplates, resources.DefaultSchemaTemplates(cf)...)
asl := server.AccessSetLookup
if asl == nil {
asl = accesscontrol.NewAccessStore(server.RBAC)
asl = accesscontrol.NewAccessStore(ctx, true, server.RBAC)
}
sf := schema.NewCollection(server.BaseSchemas, asl)
cols, err := common.NewDynamicColumns(server.RestConfig)
if err != nil {
return nil, nil, err
}
sf := schema.NewCollection(ctx, server.BaseSchemas, asl)
sync := schemacontroller.Register(ctx,
cols,
server.K8s.Discovery(),
server.CRD.CustomResourceDefinition(),
server.API.APIService(),

View File

@@ -1,66 +0,0 @@
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)
}

View File

@@ -96,7 +96,9 @@ func (s *Store) byID(apiOp *types.APIRequest, schema *types.APISchema, id string
return nil, err
}
return k8sClient.Get(id, opts)
obj, err := k8sClient.Get(id, opts)
rowToObject(obj)
return obj, err
}
func moveFromUnderscore(obj map[string]interface{}) map[string]interface{} {
@@ -130,6 +132,51 @@ func moveToUnderscore(obj *unstructured.Unstructured) *unstructured.Unstructured
return obj
}
func rowToObject(obj *unstructured.Unstructured) {
if obj.Object["kind"] != "Table" ||
obj.Object["apiVersion"] != "meta.k8s.io/v1" {
return
}
items := tableToObjects(obj.Object)
if len(items) == 1 {
obj.Object = items[0].Object
}
}
func tableToList(obj *unstructured.UnstructuredList) {
if obj.Object["kind"] != "Table" ||
obj.Object["apiVersion"] != "meta.k8s.io/v1" {
return
}
obj.Items = tableToObjects(obj.Object)
}
func tableToObjects(obj map[string]interface{}) []unstructured.Unstructured {
var result []unstructured.Unstructured
rows, _ := obj["rows"].([]interface{})
for _, row := range rows {
m, ok := row.(map[string]interface{})
if !ok {
continue
}
cells := m["cells"]
object, ok := m["object"].(map[string]interface{})
if !ok {
continue
}
data.PutValue(object, cells, "metadata", "fields")
result = append(result, unstructured.Unstructured{
Object: object,
})
}
return result
}
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 {
@@ -146,6 +193,8 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP
return types.APIObjectList{}, err
}
tableToList(resultList)
result := types.APIObjectList{
Revision: resultList.GetResourceVersion(),
Continue: resultList.GetContinue(),
@@ -230,6 +279,10 @@ func (s *Store) toAPIEvent(apiOp *types.APIRequest, schema *types.APISchema, et
name = types.CreateAPIEvent
}
if unstr, ok := obj.(*unstructured.Unstructured); ok {
rowToObject(unstr)
}
event := types.APIEvent{
Name: name,
Object: toAPI(schema, obj),