CRD should have server side printing

Add the plumbing for server side printing. Not connected until we support a way to get OpenAPI extensions from CRDs.
parent 8d406bf4
...@@ -55,7 +55,6 @@ go_library( ...@@ -55,7 +55,6 @@ go_library(
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/i18n:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/initsystem:go_default_library", "//pkg/util/initsystem:go_default_library",
"//pkg/util/node:go_default_library", "//pkg/util/node:go_default_library",
"//pkg/version:go_default_library", "//pkg/version:go_default_library",
...@@ -67,6 +66,7 @@ go_library( ...@@ -67,6 +66,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library", "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/duration:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
......
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api"
...@@ -44,7 +45,6 @@ import ( ...@@ -44,7 +45,6 @@ import (
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/printers"
) )
// NewCmdToken returns cobra.Command for token management // NewCmdToken returns cobra.Command for token management
...@@ -310,7 +310,7 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er ...@@ -310,7 +310,7 @@ func RunListTokens(out io.Writer, errW io.Writer, client clientset.Interface) er
fmt.Fprintf(errW, "can't parse expiration time of bootstrap token %s\n", secret.Name) fmt.Fprintf(errW, "can't parse expiration time of bootstrap token %s\n", secret.Name)
continue continue
} }
ttl = printers.ShortHumanDuration(expireTime.Sub(time.Now())) ttl = duration.ShortHumanDuration(expireTime.Sub(time.Now()))
expires = expireTime.Format(time.RFC3339) expires = expireTime.Format(time.RFC3339)
} }
......
...@@ -9,7 +9,6 @@ load( ...@@ -9,7 +9,6 @@ load(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"common.go",
"customcolumn.go", "customcolumn.go",
"humanreadable.go", "humanreadable.go",
"interface.go", "interface.go",
......
...@@ -102,6 +102,7 @@ go_library( ...@@ -102,6 +102,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/duration:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library",
......
...@@ -40,6 +40,7 @@ import ( ...@@ -40,6 +40,7 @@ import (
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api/events" "k8s.io/kubernetes/pkg/api/events"
"k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps"
...@@ -500,7 +501,7 @@ func translateTimestamp(timestamp metav1.Time) string { ...@@ -500,7 +501,7 @@ func translateTimestamp(timestamp metav1.Time) string {
if timestamp.IsZero() { if timestamp.IsZero() {
return "<unknown>" return "<unknown>"
} }
return printers.ShortHumanDuration(time.Now().Sub(timestamp.Time)) return duration.ShortHumanDuration(time.Now().Sub(timestamp.Time))
} }
var ( var (
......
...@@ -64,6 +64,7 @@ filegroup( ...@@ -64,6 +64,7 @@ filegroup(
"//staging/src/k8s.io/apimachinery/pkg/util/cache:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/cache:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/clock:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/clock:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/diff:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/duration:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/errors:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/framer:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/framer:all-srcs",
"//staging/src/k8s.io/apimachinery/pkg/util/httpstream:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/httpstream:all-srcs",
......
...@@ -1051,6 +1051,10 @@ ...@@ -1051,6 +1051,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/duration",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/util/errors", "ImportPath": "k8s.io/apimachinery/pkg/util/errors",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -1911,6 +1915,10 @@ ...@@ -1911,6 +1915,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/client-go/third_party/forked/golang/template",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/client-go/tools/auth", "ImportPath": "k8s.io/client-go/tools/auth",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -2007,6 +2015,10 @@ ...@@ -2007,6 +2015,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/meta/table",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/api/resource", "ImportPath": "k8s.io/apimachinery/pkg/api/resource",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -2043,6 +2055,10 @@ ...@@ -2043,6 +2055,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apimachinery/pkg/conversion", "ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
...@@ -2243,6 +2259,10 @@ ...@@ -2243,6 +2259,10 @@
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}, },
{ {
"ImportPath": "k8s.io/client-go/util/jsonpath",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/client-go/util/workqueue", "ImportPath": "k8s.io/client-go/util/workqueue",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
} }
......
...@@ -219,7 +219,7 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext ...@@ -219,7 +219,7 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext
// if validation passed otherwise, make sure we can actually construct a schema validator from this custom resource validation. // if validation passed otherwise, make sure we can actually construct a schema validator from this custom resource validation.
if len(allErrs) == 0 { if len(allErrs) == 0 {
if _, err := apiservervalidation.NewSchemaValidator(customResourceValidation); err != nil { if _, _, err := apiservervalidation.NewSchemaValidator(customResourceValidation); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, "", fmt.Sprintf("error building validator: %v", err))) allErrs = append(allErrs, field.Invalid(fldPath, "", fmt.Sprintf("error building validator: %v", err)))
} }
} }
......
...@@ -35,6 +35,7 @@ go_library( ...@@ -35,6 +35,7 @@ go_library(
"//vendor/k8s.io/apiextensions-apiserver/pkg/controller/status:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/controller/status:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/features:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
......
...@@ -64,6 +64,7 @@ import ( ...@@ -64,6 +64,7 @@ import (
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer" "k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
"k8s.io/apiextensions-apiserver/pkg/registry/customresource" "k8s.io/apiextensions-apiserver/pkg/registry/customresource"
"k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor"
) )
// crdHandler serves the `/apis` endpoint. // crdHandler serves the `/apis` endpoint.
...@@ -420,7 +421,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -420,7 +421,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
} }
creator := unstructuredCreator{} creator := unstructuredCreator{}
validator, err := apiservervalidation.NewSchemaValidator(crd.Spec.Validation) validator, _, err := apiservervalidation.NewSchemaValidator(crd.Spec.Validation)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -447,6 +448,12 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -447,6 +448,12 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
scaleSpec = crd.Spec.Subresources.Scale scaleSpec = crd.Spec.Subresources.Scale
} }
// TODO: identify how to pass printer specification from the CRD
table, err := tableconvertor.New(nil)
if err != nil {
glog.V(2).Infof("The CRD for %v has an invalid printer specification, falling back to default printing: %v", kind, err)
}
customResourceStorage := customresource.NewStorage( customResourceStorage := customresource.NewStorage(
schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Status.AcceptedNames.Plural}, schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Status.AcceptedNames.Plural},
schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Status.AcceptedNames.ListKind}, schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Status.AcceptedNames.ListKind},
...@@ -459,7 +466,9 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -459,7 +466,9 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
statusSpec, statusSpec,
scaleSpec, scaleSpec,
), ),
r.restOptionsGetter, crd.Status.AcceptedNames.Categories, r.restOptionsGetter,
crd.Status.AcceptedNames.Categories,
table,
) )
selfLinkPrefix := "" selfLinkPrefix := ""
...@@ -506,6 +515,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ...@@ -506,6 +515,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
Kind: kind, Kind: kind,
MetaGroupVersion: metav1.SchemeGroupVersion, MetaGroupVersion: metav1.SchemeGroupVersion,
TableConvertor: customResourceStorage.CustomResource,
} }
ret := &crdInfo{ ret := &crdInfo{
......
...@@ -25,15 +25,15 @@ import ( ...@@ -25,15 +25,15 @@ import (
) )
// NewSchemaValidator creates an openapi schema validator for the given CRD validation. // NewSchemaValidator creates an openapi schema validator for the given CRD validation.
func NewSchemaValidator(customResourceValidation *apiextensions.CustomResourceValidation) (*validate.SchemaValidator, error) { func NewSchemaValidator(customResourceValidation *apiextensions.CustomResourceValidation) (*validate.SchemaValidator, *spec.Schema, error) {
// Convert CRD schema to openapi schema // Convert CRD schema to openapi schema
openapiSchema := &spec.Schema{} openapiSchema := &spec.Schema{}
if customResourceValidation != nil { if customResourceValidation != nil {
if err := ConvertJSONSchemaProps(customResourceValidation.OpenAPIV3Schema, openapiSchema); err != nil { if err := ConvertJSONSchemaProps(customResourceValidation.OpenAPIV3Schema, openapiSchema); err != nil {
return nil, err return nil, nil, err
} }
} }
return validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default), nil return validate.NewSchemaValidator(openapiSchema, nil, "", strfmt.Default), openapiSchema, nil
} }
// ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition. // ValidateCustomResource validates the Custom Resource against the schema in the CustomResourceDefinition.
......
...@@ -54,7 +54,10 @@ filegroup( ...@@ -54,7 +54,10 @@ filegroup(
filegroup( filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [":package-srcs"], srcs = [
":package-srcs",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor:all-srcs",
],
tags = ["automanaged"], tags = ["automanaged"],
) )
...@@ -66,6 +69,7 @@ go_test( ...@@ -66,6 +69,7 @@ go_test(
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
......
...@@ -39,8 +39,8 @@ type CustomResourceStorage struct { ...@@ -39,8 +39,8 @@ type CustomResourceStorage struct {
Scale *ScaleREST Scale *ScaleREST
} }
func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string) CustomResourceStorage { func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string, tableConvertor rest.TableConvertor) CustomResourceStorage {
customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter, categories) customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter, categories, tableConvertor)
customResourceRegistry := NewRegistry(customResourceREST) customResourceRegistry := NewRegistry(customResourceREST)
s := CustomResourceStorage{ s := CustomResourceStorage{
...@@ -75,7 +75,7 @@ type REST struct { ...@@ -75,7 +75,7 @@ type REST struct {
} }
// newREST returns a RESTStorage object that will work against API services. // newREST returns a RESTStorage object that will work against API services.
func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string) (*REST, *StatusREST) { func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string, tableConvertor rest.TableConvertor) (*REST, *StatusREST) {
store := &genericregistry.Store{ store := &genericregistry.Store{
NewFunc: func() runtime.Object { return &unstructured.Unstructured{} }, NewFunc: func() runtime.Object { return &unstructured.Unstructured{} },
NewListFunc: func() runtime.Object { NewListFunc: func() runtime.Object {
...@@ -90,6 +90,8 @@ func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st ...@@ -90,6 +90,8 @@ func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st
CreateStrategy: strategy, CreateStrategy: strategy,
UpdateStrategy: strategy, UpdateStrategy: strategy,
DeleteStrategy: strategy, DeleteStrategy: strategy,
TableConvertor: tableConvertor,
} }
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: strategy.GetAttrs} options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: strategy.GetAttrs}
if err := store.CompleteWithOptions(options); err != nil { if err := store.CompleteWithOptions(options); err != nil {
......
...@@ -40,6 +40,7 @@ import ( ...@@ -40,6 +40,7 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apiserver" "k8s.io/apiextensions-apiserver/pkg/apiserver"
"k8s.io/apiextensions-apiserver/pkg/registry/customresource" "k8s.io/apiextensions-apiserver/pkg/registry/customresource"
"k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor"
) )
func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtesting.EtcdTestServer) { func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtesting.EtcdTestServer) {
...@@ -71,6 +72,9 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin ...@@ -71,6 +72,9 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin
status := &apiextensions.CustomResourceSubresourceStatus{} status := &apiextensions.CustomResourceSubresourceStatus{}
// TODO: identify how to pass printer specification from the CRD
table, _ := tableconvertor.New(nil)
storage := customresource.NewStorage( storage := customresource.NewStorage(
schema.GroupResource{Group: "mygroup.example.com", Resource: "noxus"}, schema.GroupResource{Group: "mygroup.example.com", Resource: "noxus"},
schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "NoxuItemList"}, schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "NoxuItemList"},
...@@ -83,7 +87,9 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin ...@@ -83,7 +87,9 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin
status, status,
scale, scale,
), ),
restOptions, []string{"all"}, restOptions,
[]string{"all"},
table,
) )
return storage, server return storage, server
......
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["tableconvertor.go"],
importpath = "k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta/table:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tableconvertor
import (
"bytes"
"fmt"
"strings"
"github.com/go-openapi/spec"
"k8s.io/apimachinery/pkg/api/meta"
metatable "k8s.io/apimachinery/pkg/api/meta/table"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/client-go/util/jsonpath"
)
const printColumnsKey = "x-kubernetes-print-columns"
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
// New creates a new table convertor for the provided OpenAPI schema. If the printer definition cannot be parsed,
// error will be returned along with a default table convertor.
func New(extensions spec.Extensions) (rest.TableConvertor, error) {
headers := []metav1beta1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
{Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]},
}
c := &convertor{
headers: headers,
}
format, ok := extensions.GetString(printColumnsKey)
if !ok {
return c, nil
}
// "x-kubernetes-print-columns": "custom-columns=NAME:.metadata.name,RSRC:.metadata.resourceVersion"
parts := strings.SplitN(format, "=", 2)
if len(parts) != 2 || parts[0] != "custom-columns" {
return c, fmt.Errorf("unrecognized column definition in 'x-kubernetes-print-columns', only support 'custom-columns=NAME=JSONPATH[,NAME=JSONPATH]'")
}
columnSpecs := strings.Split(parts[1], ",")
var columns []*jsonpath.JSONPath
for _, spec := range columnSpecs {
parts := strings.SplitN(spec, ":", 2)
if len(parts) != 2 || len(parts[0]) == 0 || len(parts[1]) == 0 {
return c, fmt.Errorf("unrecognized column definition in 'x-kubernetes-print-columns', must specify NAME=JSONPATH: %s", spec)
}
path := jsonpath.New(parts[0])
if err := path.Parse(parts[1]); err != nil {
return c, fmt.Errorf("unrecognized column definition in 'x-kubernetes-print-columns': %v", spec)
}
path.AllowMissingKeys(true)
columns = append(columns, path)
headers = append(headers, metav1beta1.TableColumnDefinition{
Name: parts[0],
Type: "string",
Description: fmt.Sprintf("Custom resource definition column from OpenAPI (in JSONPath format): %s", parts[1]),
})
}
c.columns = columns
c.headers = headers
return c, nil
}
type convertor struct {
headers []metav1beta1.TableColumnDefinition
columns []*jsonpath.JSONPath
}
func (c *convertor) ConvertToTable(ctx genericapirequest.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
table := &metav1beta1.Table{
ColumnDefinitions: c.headers,
}
if m, err := meta.ListAccessor(obj); err == nil {
table.ResourceVersion = m.GetResourceVersion()
table.SelfLink = m.GetSelfLink()
table.Continue = m.GetContinue()
} else {
if m, err := meta.CommonAccessor(obj); err == nil {
table.ResourceVersion = m.GetResourceVersion()
table.SelfLink = m.GetSelfLink()
}
}
var err error
buf := &bytes.Buffer{}
table.Rows, err = metatable.MetaToTableRow(obj, func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error) {
cells := make([]interface{}, 2, 2+len(c.columns))
cells[0] = name
cells[1] = age
for _, column := range c.columns {
if err := column.Execute(buf, obj); err != nil {
cells = append(cells, nil)
continue
}
cells = append(cells, buf.String())
buf.Reset()
}
return cells, nil
})
return table, err
}
...@@ -64,6 +64,9 @@ filegroup( ...@@ -64,6 +64,9 @@ filegroup(
filegroup( filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [":package-srcs"], srcs = [
":package-srcs",
"//staging/src/k8s.io/apimachinery/pkg/api/meta/table:all-srcs",
],
tags = ["automanaged"], tags = ["automanaged"],
) )
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["table.go"],
importpath = "k8s.io/apimachinery/pkg/api/meta/table",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/duration:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package table
import (
"time"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/duration"
)
// MetaToTableRow converts a list or object into one or more table rows. The provided rowFn is invoked for
// each accessed item, with name and age being passed to each.
func MetaToTableRow(obj runtime.Object, rowFn func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error)) ([]metav1beta1.TableRow, error) {
if meta.IsListType(obj) {
rows := make([]metav1beta1.TableRow, 0, 16)
err := meta.EachListItem(obj, func(obj runtime.Object) error {
nestedRows, err := MetaToTableRow(obj, rowFn)
if err != nil {
return err
}
rows = append(rows, nestedRows...)
return nil
})
if err != nil {
return nil, err
}
return rows, nil
}
rows := make([]metav1beta1.TableRow, 0, 1)
m, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
row := metav1beta1.TableRow{
Object: runtime.RawExtension{Object: obj},
}
row.Cells, err = rowFn(obj, m, m.GetName(), translateTimestamp(m.GetCreationTimestamp()))
if err != nil {
return nil, err
}
rows = append(rows, row)
return rows, nil
}
// translateTimestamp returns the elapsed time since timestamp in
// human-readable approximation.
func translateTimestamp(timestamp metav1.Time) string {
if timestamp.IsZero() {
return "<unknown>"
}
return duration.ShortHumanDuration(time.Now().Sub(timestamp.Time))
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["duration.go"],
importpath = "k8s.io/apimachinery/pkg/util/duration",
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
/* /*
Copyright 2014 The Kubernetes Authors. Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
...@@ -14,13 +14,15 @@ See the License for the specific language governing permissions and ...@@ -14,13 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package printers package duration
import ( import (
"fmt" "fmt"
"time" "time"
) )
// ShortHumanDuration returns a succint representation of the provided duration
// with limited precision for consumption by humans.
func ShortHumanDuration(d time.Duration) string { func ShortHumanDuration(d time.Duration) string {
// Allow deviation no more than 2 seconds(excluded) to tolerate machine time // Allow deviation no more than 2 seconds(excluded) to tolerate machine time
// inconsistence, it can be considered as almost now. // inconsistence, it can be considered as almost now.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment