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.
This commit is contained in:
Clayton Coleman 2018-02-22 14:24:52 -05:00
parent 8d406bf48b
commit fb6b1c0fba
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
21 changed files with 345 additions and 19 deletions

View File

@ -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",

View File

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

View File

@ -9,7 +9,6 @@ load(
go_library(
name = "go_default_library",
srcs = [
"common.go",
"customcolumn.go",
"humanreadable.go",
"interface.go",

View File

@ -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",

View File

@ -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 "<unknown>"
}
return printers.ShortHumanDuration(time.Now().Sub(timestamp.Time))
return duration.ShortHumanDuration(time.Now().Sub(timestamp.Time))
}
var (

View File

@ -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",

View File

@ -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"

View File

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

View File

@ -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",

View File

@ -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{

View File

@ -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.

View File

@ -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",

View File

@ -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 {

View File

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

View File

@ -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"],
)

View File

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

View File

@ -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"],
)

View File

@ -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"],
)

View File

@ -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 "<unknown>"
}
return duration.ShortHumanDuration(time.Now().Sub(timestamp.Time))
}

View File

@ -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"],
)

View File

@ -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.