diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index e7525f6cb77..ddc77a00694 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -55,7 +55,6 @@ go_library( "//pkg/apis/core:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", - "//pkg/printers:go_default_library", "//pkg/util/initsystem:go_default_library", "//pkg/util/node:go_default_library", "//pkg/version:go_default_library", @@ -67,6 +66,7 @@ go_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/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/version:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", diff --git a/cmd/kubeadm/app/cmd/token.go b/cmd/kubeadm/app/cmd/token.go index bacf0a94faa..84058eea4f3 100644 --- a/cmd/kubeadm/app/cmd/token.go +++ b/cmd/kubeadm/app/cmd/token.go @@ -31,6 +31,7 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/util/duration" "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" bootstrapapi "k8s.io/client-go/tools/bootstrap/token/api" @@ -44,7 +45,6 @@ import ( kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token" api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/printers" ) // NewCmdToken returns cobra.Command for token management @@ -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) continue } - ttl = printers.ShortHumanDuration(expireTime.Sub(time.Now())) + ttl = duration.ShortHumanDuration(expireTime.Sub(time.Now())) expires = expireTime.Format(time.RFC3339) } diff --git a/pkg/printers/BUILD b/pkg/printers/BUILD index 2351f9878d7..4e2dd00009d 100644 --- a/pkg/printers/BUILD +++ b/pkg/printers/BUILD @@ -9,7 +9,6 @@ load( go_library( name = "go_default_library", srcs = [ - "common.go", "customcolumn.go", "humanreadable.go", "interface.go", diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index 48c813413c8..1921aa44b81 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -102,6 +102,7 @@ go_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/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/sets:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", diff --git a/pkg/printers/internalversion/printers.go b/pkg/printers/internalversion/printers.go index cddfc3403d8..b164e0d8596 100644 --- a/pkg/printers/internalversion/printers.go +++ b/pkg/printers/internalversion/printers.go @@ -40,6 +40,7 @@ import ( metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/duration" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/api/events" "k8s.io/kubernetes/pkg/apis/apps" @@ -500,7 +501,7 @@ func translateTimestamp(timestamp metav1.Time) string { if timestamp.IsZero() { return "" } - return printers.ShortHumanDuration(time.Now().Sub(timestamp.Time)) + return duration.ShortHumanDuration(time.Now().Sub(timestamp.Time)) } var ( diff --git a/staging/BUILD b/staging/BUILD index e48344604ea..75f83b27ab8 100644 --- a/staging/BUILD +++ b/staging/BUILD @@ -64,6 +64,7 @@ filegroup( "//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/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/framer:all-srcs", "//staging/src/k8s.io/apimachinery/pkg/util/httpstream:all-srcs", diff --git a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json index f3c20dfe3b3..8ee004eb1bd 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json @@ -1050,6 +1050,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/util/diff", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/util/duration", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/util/errors", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -1910,6 +1914,10 @@ "ImportPath": "k8s.io/client-go/testing", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/third_party/forked/golang/template", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/tools/auth", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -2006,6 +2014,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/api/meta", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/api/meta/table", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/api/resource", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -2042,6 +2054,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -2242,6 +2258,10 @@ "ImportPath": "k8s.io/client-go/util/flowcontrol", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/client-go/util/jsonpath", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/util/workqueue", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go index be4a0e34f46..713fcfa9058 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go @@ -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 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))) } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD index 454689b3c47..09d21156d73 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD @@ -35,6 +35,7 @@ go_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/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/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index 40c58e04210..dda7d465c75 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -64,6 +64,7 @@ import ( "k8s.io/apiextensions-apiserver/pkg/controller/finalizer" apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features" "k8s.io/apiextensions-apiserver/pkg/registry/customresource" + "k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor" ) // crdHandler serves the `/apis` endpoint. @@ -420,7 +421,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource } creator := unstructuredCreator{} - validator, err := apiservervalidation.NewSchemaValidator(crd.Spec.Validation) + validator, _, err := apiservervalidation.NewSchemaValidator(crd.Spec.Validation) if err != nil { return nil, err } @@ -447,6 +448,12 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource 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( 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}, @@ -459,7 +466,9 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource statusSpec, scaleSpec, ), - r.restOptionsGetter, crd.Status.AcceptedNames.Categories, + r.restOptionsGetter, + crd.Status.AcceptedNames.Categories, + table, ) selfLinkPrefix := "" @@ -506,6 +515,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource Kind: kind, MetaGroupVersion: metav1.SchemeGroupVersion, + + TableConvertor: customResourceStorage.CustomResource, } ret := &crdInfo{ diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go index de8e1af04d6..558ddb1fcdc 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation/validation.go @@ -25,15 +25,15 @@ import ( ) // 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 openapiSchema := &spec.Schema{} if customResourceValidation != 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. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/BUILD index de104e2f7a4..73ac1009eed 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/BUILD @@ -54,7 +54,10 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor:all-srcs", + ], tags = ["automanaged"], ) @@ -66,6 +69,7 @@ go_test( "//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/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/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go index 305cee3b664..bf54fe33b9a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go @@ -39,8 +39,8 @@ type CustomResourceStorage struct { Scale *ScaleREST } -func NewStorage(resource schema.GroupResource, listKind schema.GroupVersionKind, strategy customResourceStrategy, optsGetter generic.RESTOptionsGetter, categories []string) CustomResourceStorage { - customResourceREST, customResourceStatusREST := newREST(resource, listKind, strategy, optsGetter, categories) +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, tableConvertor) customResourceRegistry := NewRegistry(customResourceREST) s := CustomResourceStorage{ @@ -75,7 +75,7 @@ type REST struct { } // 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{ NewFunc: func() runtime.Object { return &unstructured.Unstructured{} }, NewListFunc: func() runtime.Object { @@ -90,6 +90,8 @@ func newREST(resource schema.GroupResource, listKind schema.GroupVersionKind, st CreateStrategy: strategy, UpdateStrategy: strategy, DeleteStrategy: strategy, + + TableConvertor: tableConvertor, } options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: strategy.GetAttrs} if err := store.CompleteWithOptions(options); err != nil { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go index f5502854512..8bee3396c94 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go @@ -40,6 +40,7 @@ import ( "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apiserver" "k8s.io/apiextensions-apiserver/pkg/registry/customresource" + "k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor" ) func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtesting.EtcdTestServer) { @@ -71,6 +72,9 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin status := &apiextensions.CustomResourceSubresourceStatus{} + // TODO: identify how to pass printer specification from the CRD + table, _ := tableconvertor.New(nil) + storage := customresource.NewStorage( schema.GroupResource{Group: "mygroup.example.com", Resource: "noxus"}, schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "NoxuItemList"}, @@ -83,7 +87,9 @@ func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtestin status, scale, ), - restOptions, []string{"all"}, + restOptions, + []string{"all"}, + table, ) return storage, server diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/BUILD new file mode 100644 index 00000000000..e6150d20368 --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/BUILD @@ -0,0 +1,33 @@ +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"], +) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go new file mode 100644 index 00000000000..4eb62da6b7c --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go @@ -0,0 +1,120 @@ +/* +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 +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD index 24d9aaabc91..a02a1fb5b88 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/BUILD @@ -64,6 +64,9 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apimachinery/pkg/api/meta/table:all-srcs", + ], tags = ["automanaged"], ) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/table/BUILD b/staging/src/k8s.io/apimachinery/pkg/api/meta/table/BUILD new file mode 100644 index 00000000000..e3ffd24934d --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/table/BUILD @@ -0,0 +1,29 @@ +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"], +) diff --git a/staging/src/k8s.io/apimachinery/pkg/api/meta/table/table.go b/staging/src/k8s.io/apimachinery/pkg/api/meta/table/table.go new file mode 100644 index 00000000000..a0097a4e26d --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/meta/table/table.go @@ -0,0 +1,71 @@ +/* +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 "" + } + return duration.ShortHumanDuration(time.Now().Sub(timestamp.Time)) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/duration/BUILD b/staging/src/k8s.io/apimachinery/pkg/util/duration/BUILD new file mode 100644 index 00000000000..6b9e7880eca --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/duration/BUILD @@ -0,0 +1,22 @@ +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"], +) diff --git a/pkg/printers/common.go b/staging/src/k8s.io/apimachinery/pkg/util/duration/duration.go similarity index 86% rename from pkg/printers/common.go rename to staging/src/k8s.io/apimachinery/pkg/util/duration/duration.go index 382ff5639ea..00404c6cdda 100644 --- a/pkg/printers/common.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/duration/duration.go @@ -1,5 +1,5 @@ /* -Copyright 2014 The Kubernetes Authors. +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. @@ -14,13 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -package printers +package duration import ( "fmt" "time" ) +// ShortHumanDuration returns a succint representation of the provided duration +// with limited precision for consumption by humans. func ShortHumanDuration(d time.Duration) string { // Allow deviation no more than 2 seconds(excluded) to tolerate machine time // inconsistence, it can be considered as almost now.