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:
parent
8d406bf48b
commit
fb6b1c0fba
@ -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(
|
|||||||
"//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 (
|
|||||||
"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 (
|
|||||||
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
|
|||||||
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(
|
|||||||
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(
|
|||||||
"//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 (
|
|||||||
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 {
|
|||||||
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(
|
|||||||
"//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",
|
||||||
|
@ -1050,6 +1050,10 @@
|
|||||||
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
|
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
|
||||||
"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"
|
||||||
@ -1910,6 +1914,10 @@
|
|||||||
"ImportPath": "k8s.io/client-go/testing",
|
"ImportPath": "k8s.io/client-go/testing",
|
||||||
"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"
|
||||||
@ -2006,6 +2014,10 @@
|
|||||||
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
|
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
|
||||||
"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"
|
||||||
@ -2042,6 +2054,10 @@
|
|||||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||||
"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"
|
||||||
@ -2242,6 +2258,10 @@
|
|||||||
"ImportPath": "k8s.io/client-go/util/flowcontrol",
|
"ImportPath": "k8s.io/client-go/util/flowcontrol",
|
||||||
"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
|
|||||||
|
|
||||||
// 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(
|
|||||||
"//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 (
|
|||||||
"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
|
|||||||
}
|
}
|
||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
Kind: kind,
|
Kind: kind,
|
||||||
|
|
||||||
MetaGroupVersion: metav1.SchemeGroupVersion,
|
MetaGroupVersion: metav1.SchemeGroupVersion,
|
||||||
|
|
||||||
|
TableConvertor: customResourceStorage.CustomResource,
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := &crdInfo{
|
ret := &crdInfo{
|
||||||
|
@ -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(
|
|||||||
|
|
||||||
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(
|
|||||||
"//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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|||||||
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 (
|
|||||||
"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
|
|||||||
|
|
||||||
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
|
|||||||
status,
|
status,
|
||||||
scale,
|
scale,
|
||||||
),
|
),
|
||||||
restOptions, []string{"all"},
|
restOptions,
|
||||||
|
[]string{"all"},
|
||||||
|
table,
|
||||||
)
|
)
|
||||||
|
|
||||||
return storage, server
|
return storage, server
|
||||||
|
@ -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"],
|
||||||
|
)
|
@ -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
|
||||||
|
}
|
@ -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"],
|
||||||
)
|
)
|
||||||
|
29
staging/src/k8s.io/apimachinery/pkg/api/meta/table/BUILD
Normal file
29
staging/src/k8s.io/apimachinery/pkg/api/meta/table/BUILD
Normal 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"],
|
||||||
|
)
|
71
staging/src/k8s.io/apimachinery/pkg/api/meta/table/table.go
Normal file
71
staging/src/k8s.io/apimachinery/pkg/api/meta/table/table.go
Normal 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))
|
||||||
|
}
|
22
staging/src/k8s.io/apimachinery/pkg/util/duration/BUILD
Normal file
22
staging/src/k8s.io/apimachinery/pkg/util/duration/BUILD
Normal 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"],
|
||||||
|
)
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
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
|
|||||||
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.
|
Loading…
Reference in New Issue
Block a user