Bumping k8s version to 1.13.0-beta.1
This commit is contained in:
13
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go
generated
vendored
13
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go
generated
vendored
@@ -31,6 +31,7 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
|
||||
@@ -78,6 +79,11 @@ type ExtraConfig struct {
|
||||
// MasterCount is used to detect whether cluster is HA, and if it is
|
||||
// the CRD Establishing will be hold by 5 seconds.
|
||||
MasterCount int
|
||||
|
||||
// ServiceResolver is used in CR webhook converters to resolve webhook's service names
|
||||
ServiceResolver webhook.ServiceResolver
|
||||
// AuthResolverWrapper is used in CR webhook converters
|
||||
AuthResolverWrapper webhook.AuthenticationInfoResolverWrapper
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -167,7 +173,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
|
||||
delegate: delegateHandler,
|
||||
}
|
||||
establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
|
||||
crdHandler := NewCustomResourceDefinitionHandler(
|
||||
crdHandler, err := NewCustomResourceDefinitionHandler(
|
||||
versionDiscoveryHandler,
|
||||
groupDiscoveryHandler,
|
||||
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(),
|
||||
@@ -175,8 +181,13 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
|
||||
c.ExtraConfig.CRDRESTOptionsGetter,
|
||||
c.GenericConfig.AdmissionControl,
|
||||
establishingController,
|
||||
c.ExtraConfig.ServiceResolver,
|
||||
c.ExtraConfig.AuthResolverWrapper,
|
||||
c.ExtraConfig.MasterCount,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler)
|
||||
s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler)
|
||||
|
||||
|
67
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter.go
generated
vendored
67
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter.go
generated
vendored
@@ -20,39 +20,74 @@ import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
)
|
||||
|
||||
// NewCRDConverter returns a new CRD converter based on the conversion settings in crd object.
|
||||
func NewCRDConverter(crd *apiextensions.CustomResourceDefinition) (safe, unsafe runtime.ObjectConvertor) {
|
||||
// CRConverterFactory is the factory for all CR converters.
|
||||
type CRConverterFactory struct {
|
||||
// webhookConverterFactory is the factory for webhook converters.
|
||||
// This field should not be used if CustomResourceWebhookConversion feature is disabled.
|
||||
webhookConverterFactory *webhookConverterFactory
|
||||
}
|
||||
|
||||
// NewCRConverterFactory creates a new CRConverterFactory
|
||||
func NewCRConverterFactory(serviceResolver webhook.ServiceResolver, authResolverWrapper webhook.AuthenticationInfoResolverWrapper) (*CRConverterFactory, error) {
|
||||
converterFactory := &CRConverterFactory{}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceWebhookConversion) {
|
||||
webhookConverterFactory, err := newWebhookConverterFactory(serviceResolver, authResolverWrapper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
converterFactory.webhookConverterFactory = webhookConverterFactory
|
||||
}
|
||||
return converterFactory, nil
|
||||
}
|
||||
|
||||
// NewConverter returns a new CR converter based on the conversion settings in crd object.
|
||||
func (m *CRConverterFactory) NewConverter(crd *apiextensions.CustomResourceDefinition) (safe, unsafe runtime.ObjectConvertor, err error) {
|
||||
validVersions := map[schema.GroupVersion]bool{}
|
||||
for _, version := range crd.Spec.Versions {
|
||||
validVersions[schema.GroupVersion{Group: crd.Spec.Group, Version: version.Name}] = true
|
||||
}
|
||||
|
||||
// The only converter right now is nopConverter. More converters will be returned based on the
|
||||
// CRD object when they introduced.
|
||||
unsafe = &crdConverter{
|
||||
clusterScoped: crd.Spec.Scope == apiextensions.ClusterScoped,
|
||||
delegate: &nopConverter{
|
||||
validVersions: validVersions,
|
||||
},
|
||||
switch crd.Spec.Conversion.Strategy {
|
||||
case apiextensions.NoneConverter:
|
||||
unsafe = &crConverter{
|
||||
clusterScoped: crd.Spec.Scope == apiextensions.ClusterScoped,
|
||||
delegate: &nopConverter{
|
||||
validVersions: validVersions,
|
||||
},
|
||||
}
|
||||
return &safeConverterWrapper{unsafe}, unsafe, nil
|
||||
case apiextensions.WebhookConverter:
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceWebhookConversion) {
|
||||
return nil, nil, fmt.Errorf("webhook conversion is disabled on this cluster")
|
||||
}
|
||||
unsafe, err := m.webhookConverterFactory.NewWebhookConverter(validVersions, crd)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &safeConverterWrapper{unsafe}, unsafe, nil
|
||||
}
|
||||
return &safeConverterWrapper{unsafe}, unsafe
|
||||
|
||||
return nil, nil, fmt.Errorf("unknown conversion strategy %q for CRD %s", crd.Spec.Conversion.Strategy, crd.Name)
|
||||
}
|
||||
|
||||
var _ runtime.ObjectConvertor = &crdConverter{}
|
||||
var _ runtime.ObjectConvertor = &crConverter{}
|
||||
|
||||
// crdConverter extends the delegate with generic CRD conversion behaviour. The delegate will implement the
|
||||
// crConverter extends the delegate with generic CR conversion behaviour. The delegate will implement the
|
||||
// user defined conversion strategy given in the CustomResourceDefinition.
|
||||
type crdConverter struct {
|
||||
type crConverter struct {
|
||||
delegate runtime.ObjectConvertor
|
||||
clusterScoped bool
|
||||
}
|
||||
|
||||
func (c *crdConverter) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
|
||||
func (c *crConverter) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
|
||||
// We currently only support metadata.namespace and metadata.name.
|
||||
switch {
|
||||
case label == "metadata.name":
|
||||
@@ -64,12 +99,12 @@ func (c *crdConverter) ConvertFieldLabel(gvk schema.GroupVersionKind, label, val
|
||||
}
|
||||
}
|
||||
|
||||
func (c *crdConverter) Convert(in, out, context interface{}) error {
|
||||
func (c *crConverter) Convert(in, out, context interface{}) error {
|
||||
return c.delegate.Convert(in, out, context)
|
||||
}
|
||||
|
||||
// ConvertToVersion converts in object to the given gvk in place and returns the same `in` object.
|
||||
func (c *crdConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
|
||||
func (c *crConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
|
||||
// Run the converter on the list items instead of list itself
|
||||
if list, ok := in.(*unstructured.UnstructuredList); ok {
|
||||
for i := range list.Items {
|
||||
|
6
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/nop_converter.go
generated
vendored
6
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/nop_converter.go
generated
vendored
@@ -49,11 +49,11 @@ func (c *nopConverter) Convert(in, out, context interface{}) error {
|
||||
|
||||
outGVK := unstructOut.GroupVersionKind()
|
||||
if !c.validVersions[outGVK.GroupVersion()] {
|
||||
return fmt.Errorf("request to convert CRD from an invalid group/version: %s", outGVK.String())
|
||||
return fmt.Errorf("request to convert CR from an invalid group/version: %s", outGVK.String())
|
||||
}
|
||||
inGVK := unstructIn.GroupVersionKind()
|
||||
if !c.validVersions[inGVK.GroupVersion()] {
|
||||
return fmt.Errorf("request to convert CRD to an invalid group/version: %s", inGVK.String())
|
||||
return fmt.Errorf("request to convert CR to an invalid group/version: %s", inGVK.String())
|
||||
}
|
||||
|
||||
unstructOut.SetUnstructuredContent(unstructIn.UnstructuredContent())
|
||||
@@ -72,7 +72,7 @@ func (c *nopConverter) ConvertToVersion(in runtime.Object, target runtime.GroupV
|
||||
return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", kind, target)
|
||||
}
|
||||
if !c.validVersions[gvk.GroupVersion()] {
|
||||
return nil, fmt.Errorf("request to convert CRD to an invalid group/version: %s", gvk.String())
|
||||
return nil, fmt.Errorf("request to convert CR to an invalid group/version: %s", gvk.String())
|
||||
}
|
||||
in.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
return in, nil
|
||||
|
350
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter.go
generated
vendored
Normal file
350
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter.go
generated
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
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 conversion
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
internal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
)
|
||||
|
||||
type webhookConverterFactory struct {
|
||||
clientManager webhook.ClientManager
|
||||
}
|
||||
|
||||
func newWebhookConverterFactory(serviceResolver webhook.ServiceResolver, authResolverWrapper webhook.AuthenticationInfoResolverWrapper) (*webhookConverterFactory, error) {
|
||||
clientManager, err := webhook.NewClientManager(v1beta1.SchemeGroupVersion, v1beta1.AddToScheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authInfoResolver, err := webhook.NewDefaultAuthenticationInfoResolver("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set defaults which may be overridden later.
|
||||
clientManager.SetAuthenticationInfoResolver(authInfoResolver)
|
||||
clientManager.SetAuthenticationInfoResolverWrapper(authResolverWrapper)
|
||||
clientManager.SetServiceResolver(serviceResolver)
|
||||
return &webhookConverterFactory{clientManager}, nil
|
||||
}
|
||||
|
||||
// webhookConverter is a converter that calls an external webhook to do the CR conversion.
|
||||
type webhookConverter struct {
|
||||
validVersions map[schema.GroupVersion]bool
|
||||
clientManager webhook.ClientManager
|
||||
restClient *rest.RESTClient
|
||||
name string
|
||||
nopConverter nopConverter
|
||||
}
|
||||
|
||||
func webhookClientConfigForCRD(crd *internal.CustomResourceDefinition) *webhook.ClientConfig {
|
||||
apiConfig := crd.Spec.Conversion.WebhookClientConfig
|
||||
ret := webhook.ClientConfig{
|
||||
Name: fmt.Sprintf("conversion_webhook_for_%s", crd.Name),
|
||||
CABundle: apiConfig.CABundle,
|
||||
}
|
||||
if apiConfig.URL != nil {
|
||||
ret.URL = *apiConfig.URL
|
||||
}
|
||||
if apiConfig.Service != nil {
|
||||
ret.Service = &webhook.ClientConfigService{
|
||||
Name: apiConfig.Service.Name,
|
||||
Namespace: apiConfig.Service.Namespace,
|
||||
}
|
||||
if apiConfig.Service.Path != nil {
|
||||
ret.Service.Path = *apiConfig.Service.Path
|
||||
}
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
var _ runtime.ObjectConvertor = &webhookConverter{}
|
||||
|
||||
func (f *webhookConverterFactory) NewWebhookConverter(validVersions map[schema.GroupVersion]bool, crd *internal.CustomResourceDefinition) (*webhookConverter, error) {
|
||||
restClient, err := f.clientManager.HookClient(*webhookClientConfigForCRD(crd))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &webhookConverter{
|
||||
clientManager: f.clientManager,
|
||||
validVersions: validVersions,
|
||||
restClient: restClient,
|
||||
name: crd.Name,
|
||||
nopConverter: nopConverter{validVersions: validVersions},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (webhookConverter) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
|
||||
return "", "", errors.New("unstructured cannot convert field labels")
|
||||
}
|
||||
|
||||
func (c *webhookConverter) Convert(in, out, context interface{}) error {
|
||||
unstructIn, ok := in.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("input type %T in not valid for unstructured conversion", in)
|
||||
}
|
||||
|
||||
unstructOut, ok := out.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return fmt.Errorf("output type %T in not valid for unstructured conversion", out)
|
||||
}
|
||||
|
||||
outGVK := unstructOut.GroupVersionKind()
|
||||
if !c.validVersions[outGVK.GroupVersion()] {
|
||||
return fmt.Errorf("request to convert CR from an invalid group/version: %s", outGVK.String())
|
||||
}
|
||||
inGVK := unstructIn.GroupVersionKind()
|
||||
if !c.validVersions[inGVK.GroupVersion()] {
|
||||
return fmt.Errorf("request to convert CR to an invalid group/version: %s", inGVK.String())
|
||||
}
|
||||
|
||||
converted, err := c.ConvertToVersion(unstructIn, outGVK.GroupVersion())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unstructuredConverted, ok := converted.(runtime.Unstructured)
|
||||
if !ok {
|
||||
// this should not happened
|
||||
return fmt.Errorf("CR conversion failed")
|
||||
}
|
||||
unstructOut.SetUnstructuredContent(unstructuredConverted.UnstructuredContent())
|
||||
return nil
|
||||
}
|
||||
|
||||
func createConversionReview(obj runtime.Object, apiVersion string) *v1beta1.ConversionReview {
|
||||
listObj, isList := obj.(*unstructured.UnstructuredList)
|
||||
var objects []runtime.RawExtension
|
||||
if isList {
|
||||
for i := 0; i < len(listObj.Items); i++ {
|
||||
// Only sent item for conversion, if the apiVersion is different
|
||||
if listObj.Items[i].GetAPIVersion() != apiVersion {
|
||||
objects = append(objects, runtime.RawExtension{Object: &listObj.Items[i]})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if obj.GetObjectKind().GroupVersionKind().GroupVersion().String() != apiVersion {
|
||||
objects = []runtime.RawExtension{{Object: obj}}
|
||||
}
|
||||
}
|
||||
return &v1beta1.ConversionReview{
|
||||
Request: &v1beta1.ConversionRequest{
|
||||
Objects: objects,
|
||||
DesiredAPIVersion: apiVersion,
|
||||
UID: uuid.NewUUID(),
|
||||
},
|
||||
Response: &v1beta1.ConversionResponse{},
|
||||
}
|
||||
}
|
||||
|
||||
func getRawExtensionObject(rx runtime.RawExtension) (runtime.Object, error) {
|
||||
if rx.Object != nil {
|
||||
return rx.Object, nil
|
||||
}
|
||||
u := unstructured.Unstructured{}
|
||||
err := u.UnmarshalJSON(rx.Raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// getTargetGroupVersion returns group/version which should be used to convert in objects to.
|
||||
// String version of the return item is APIVersion.
|
||||
func getTargetGroupVersion(in runtime.Object, target runtime.GroupVersioner) (schema.GroupVersion, error) {
|
||||
fromGVK := in.GetObjectKind().GroupVersionKind()
|
||||
toGVK, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{fromGVK})
|
||||
if !ok {
|
||||
// TODO: should this be a typed error?
|
||||
return schema.GroupVersion{}, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", fromGVK.String(), target)
|
||||
}
|
||||
return toGVK.GroupVersion(), nil
|
||||
}
|
||||
|
||||
func (c *webhookConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
|
||||
// In general, the webhook should not do any defaulting or validation. A special case of that is an empty object
|
||||
// conversion that must result an empty object and practically is the same as nopConverter.
|
||||
// A smoke test in API machinery calls the converter on empty objects. As this case happens consistently
|
||||
// it special cased here not to call webhook converter. The test initiated here:
|
||||
// https://github.com/kubernetes/kubernetes/blob/dbb448bbdcb9e440eee57024ffa5f1698956a054/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L201
|
||||
if isEmptyUnstructuredObject(in) {
|
||||
return c.nopConverter.ConvertToVersion(in, target)
|
||||
}
|
||||
|
||||
toGV, err := getTargetGroupVersion(in, target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !c.validVersions[toGV] {
|
||||
return nil, fmt.Errorf("request to convert CR to an invalid group/version: %s", toGV.String())
|
||||
}
|
||||
fromGV := in.GetObjectKind().GroupVersionKind().GroupVersion()
|
||||
if !c.validVersions[fromGV] {
|
||||
return nil, fmt.Errorf("request to convert CR from an invalid group/version: %s", fromGV.String())
|
||||
}
|
||||
listObj, isList := in.(*unstructured.UnstructuredList)
|
||||
if isList {
|
||||
for i, item := range listObj.Items {
|
||||
fromGV := item.GroupVersionKind().GroupVersion()
|
||||
if !c.validVersions[fromGV] {
|
||||
return nil, fmt.Errorf("input list has invalid group/version `%v` at `%v` index", fromGV, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request := createConversionReview(in, toGV.String())
|
||||
if len(request.Request.Objects) == 0 {
|
||||
if !isList {
|
||||
return in, nil
|
||||
}
|
||||
out := listObj.DeepCopy()
|
||||
out.SetAPIVersion(toGV.String())
|
||||
return out, nil
|
||||
}
|
||||
response := &v1beta1.ConversionReview{}
|
||||
// TODO: Figure out if adding one second timeout make sense here.
|
||||
ctx := context.TODO()
|
||||
r := c.restClient.Post().Context(ctx).Body(request).Do()
|
||||
if err := r.Into(response); err != nil {
|
||||
// TODO: Return a webhook specific error to be able to convert it to meta.Status
|
||||
return nil, fmt.Errorf("calling to conversion webhook failed for %s: %v", c.name, err)
|
||||
}
|
||||
|
||||
if response.Response == nil {
|
||||
// TODO: Return a webhook specific error to be able to convert it to meta.Status
|
||||
return nil, fmt.Errorf("conversion webhook response was absent for %s", c.name)
|
||||
}
|
||||
|
||||
if response.Response.Result.Status != v1.StatusSuccess {
|
||||
// TODO return status message as error
|
||||
return nil, fmt.Errorf("conversion request failed for %v, Response: %v", in.GetObjectKind(), response)
|
||||
}
|
||||
|
||||
if len(response.Response.ConvertedObjects) != len(request.Request.Objects) {
|
||||
return nil, fmt.Errorf("expected %v converted objects, got %v", len(request.Request.Objects), len(response.Response.ConvertedObjects))
|
||||
}
|
||||
|
||||
if isList {
|
||||
convertedList := listObj.DeepCopy()
|
||||
// Collection of items sent for conversion is different than list items
|
||||
// because only items that needed conversion has been sent.
|
||||
convertedIndex := 0
|
||||
for i := 0; i < len(listObj.Items); i++ {
|
||||
if listObj.Items[i].GetAPIVersion() == toGV.String() {
|
||||
// This item has not been sent for conversion, skip it.
|
||||
continue
|
||||
}
|
||||
converted, err := getRawExtensionObject(response.Response.ConvertedObjects[convertedIndex])
|
||||
convertedIndex++
|
||||
original := listObj.Items[i]
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid converted object at index %v: %v", convertedIndex, err)
|
||||
}
|
||||
if e, a := toGV, converted.GetObjectKind().GroupVersionKind().GroupVersion(); e != a {
|
||||
return nil, fmt.Errorf("invalid converted object at index %v: invalid groupVersion, e=%v, a=%v", convertedIndex, e, a)
|
||||
}
|
||||
if e, a := original.GetObjectKind().GroupVersionKind().Kind, converted.GetObjectKind().GroupVersionKind().Kind; e != a {
|
||||
return nil, fmt.Errorf("invalid converted object at index %v: invalid kind, e=%v, a=%v", convertedIndex, e, a)
|
||||
}
|
||||
unstructConverted, ok := converted.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
// this should not happened
|
||||
return nil, fmt.Errorf("CR conversion failed")
|
||||
}
|
||||
if err := validateConvertedObject(&listObj.Items[i], unstructConverted); err != nil {
|
||||
return nil, fmt.Errorf("invalid converted object at index %v: %v", convertedIndex, err)
|
||||
}
|
||||
convertedList.Items[i] = *unstructConverted
|
||||
}
|
||||
convertedList.SetAPIVersion(toGV.String())
|
||||
return convertedList, nil
|
||||
}
|
||||
|
||||
if len(response.Response.ConvertedObjects) != 1 {
|
||||
// This should not happened
|
||||
return nil, fmt.Errorf("CR conversion failed")
|
||||
}
|
||||
converted, err := getRawExtensionObject(response.Response.ConvertedObjects[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e, a := toGV, converted.GetObjectKind().GroupVersionKind().GroupVersion(); e != a {
|
||||
return nil, fmt.Errorf("invalid converted object: invalid groupVersion, e=%v, a=%v", e, a)
|
||||
}
|
||||
if e, a := in.GetObjectKind().GroupVersionKind().Kind, converted.GetObjectKind().GroupVersionKind().Kind; e != a {
|
||||
return nil, fmt.Errorf("invalid converted object: invalid kind, e=%v, a=%v", e, a)
|
||||
}
|
||||
unstructConverted, ok := converted.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
// this should not happened
|
||||
return nil, fmt.Errorf("CR conversion failed")
|
||||
}
|
||||
unstructIn, ok := in.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
// this should not happened
|
||||
return nil, fmt.Errorf("CR conversion failed")
|
||||
}
|
||||
if err := validateConvertedObject(unstructIn, unstructConverted); err != nil {
|
||||
return nil, fmt.Errorf("invalid converted object: %v", err)
|
||||
}
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
func validateConvertedObject(unstructIn, unstructOut *unstructured.Unstructured) error {
|
||||
if e, a := unstructIn.GetKind(), unstructOut.GetKind(); e != a {
|
||||
return fmt.Errorf("must have the same kind: %v != %v", e, a)
|
||||
}
|
||||
if e, a := unstructIn.GetName(), unstructOut.GetName(); e != a {
|
||||
return fmt.Errorf("must have the same name: %v != %v", e, a)
|
||||
}
|
||||
if e, a := unstructIn.GetNamespace(), unstructOut.GetNamespace(); e != a {
|
||||
return fmt.Errorf("must have the same namespace: %v != %v", e, a)
|
||||
}
|
||||
if e, a := unstructIn.GetUID(), unstructOut.GetUID(); e != a {
|
||||
return fmt.Errorf("must have the same UID: %v != %v", e, a)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isEmptyUnstructuredObject returns true if in is an empty unstructured object, i.e. an unstructured object that does
|
||||
// not have any field except apiVersion and kind.
|
||||
func isEmptyUnstructuredObject(in runtime.Object) bool {
|
||||
u, ok := in.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if len(u.Object) != 2 {
|
||||
return false
|
||||
}
|
||||
if _, ok := u.Object["kind"]; !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := u.Object["apiVersion"]; !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
@@ -21,7 +21,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/klog"
|
||||
|
||||
autoscaling "k8s.io/api/autoscaling/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -136,7 +136,11 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error {
|
||||
Categories: crd.Status.AcceptedNames.Categories,
|
||||
})
|
||||
|
||||
if crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil {
|
||||
subresources, err := getSubresourcesForVersion(crd, version.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if subresources != nil && subresources.Status != nil {
|
||||
apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{
|
||||
Name: crd.Status.AcceptedNames.Plural + "/status",
|
||||
Namespaced: crd.Spec.Scope == apiextensions.NamespaceScoped,
|
||||
@@ -145,7 +149,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error {
|
||||
})
|
||||
}
|
||||
|
||||
if crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil {
|
||||
if subresources != nil && subresources.Scale != nil {
|
||||
apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{
|
||||
Group: autoscaling.GroupName,
|
||||
Version: "v1",
|
||||
@@ -194,9 +198,9 @@ func sortGroupDiscoveryByKubeAwareVersion(gd []metav1.GroupVersionForDiscovery)
|
||||
func (c *DiscoveryController) Run(stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.queue.ShutDown()
|
||||
defer glog.Infof("Shutting down DiscoveryController")
|
||||
defer klog.Infof("Shutting down DiscoveryController")
|
||||
|
||||
glog.Infof("Starting DiscoveryController")
|
||||
klog.Infof("Starting DiscoveryController")
|
||||
|
||||
if !cache.WaitForCacheSync(stopCh, c.crdsSynced) {
|
||||
utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
|
||||
@@ -242,14 +246,14 @@ func (c *DiscoveryController) enqueue(obj *apiextensions.CustomResourceDefinitio
|
||||
|
||||
func (c *DiscoveryController) addCustomResourceDefinition(obj interface{}) {
|
||||
castObj := obj.(*apiextensions.CustomResourceDefinition)
|
||||
glog.V(4).Infof("Adding customresourcedefinition %s", castObj.Name)
|
||||
klog.V(4).Infof("Adding customresourcedefinition %s", castObj.Name)
|
||||
c.enqueue(castObj)
|
||||
}
|
||||
|
||||
func (c *DiscoveryController) updateCustomResourceDefinition(oldObj, newObj interface{}) {
|
||||
castNewObj := newObj.(*apiextensions.CustomResourceDefinition)
|
||||
castOldObj := oldObj.(*apiextensions.CustomResourceDefinition)
|
||||
glog.V(4).Infof("Updating customresourcedefinition %s", castOldObj.Name)
|
||||
klog.V(4).Infof("Updating customresourcedefinition %s", castOldObj.Name)
|
||||
// Enqueue both old and new object to make sure we remove and add appropriate Versions.
|
||||
// The working queue will resolve any duplicates and only changes will stay in the queue.
|
||||
c.enqueue(castNewObj)
|
||||
@@ -261,15 +265,15 @@ func (c *DiscoveryController) deleteCustomResourceDefinition(obj interface{}) {
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
glog.Errorf("Couldn't get object from tombstone %#v", obj)
|
||||
klog.Errorf("Couldn't get object from tombstone %#v", obj)
|
||||
return
|
||||
}
|
||||
castObj, ok = tombstone.Obj.(*apiextensions.CustomResourceDefinition)
|
||||
if !ok {
|
||||
glog.Errorf("Tombstone contained object that is not expected %#v", obj)
|
||||
klog.Errorf("Tombstone contained object that is not expected %#v", obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
glog.V(4).Infof("Deleting customresourcedefinition %q", castObj.Name)
|
||||
klog.V(4).Infof("Deleting customresourcedefinition %q", castObj.Name)
|
||||
c.enqueue(castObj)
|
||||
}
|
||||
|
71
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go
generated
vendored
71
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go
generated
vendored
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/go-openapi/spec"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/validate"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/klog"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -67,6 +67,7 @@ import (
|
||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||
"k8s.io/apiextensions-apiserver/pkg/registry/customresource"
|
||||
"k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
)
|
||||
|
||||
// crdHandler serves the `/apis` endpoint.
|
||||
@@ -93,6 +94,8 @@ type crdHandler struct {
|
||||
// MasterCount is used to implement sleep to improve
|
||||
// CRD establishing process for HA clusters.
|
||||
masterCount int
|
||||
|
||||
converterFactory *conversion.CRConverterFactory
|
||||
}
|
||||
|
||||
// crdInfo stores enough information to serve the storage for the custom resource
|
||||
@@ -129,7 +132,9 @@ func NewCustomResourceDefinitionHandler(
|
||||
restOptionsGetter generic.RESTOptionsGetter,
|
||||
admission admission.Interface,
|
||||
establishingController *establish.EstablishingController,
|
||||
masterCount int) *crdHandler {
|
||||
serviceResolver webhook.ServiceResolver,
|
||||
authResolverWrapper webhook.AuthenticationInfoResolverWrapper,
|
||||
masterCount int) (*crdHandler, error) {
|
||||
ret := &crdHandler{
|
||||
versionDiscoveryHandler: versionDiscoveryHandler,
|
||||
groupDiscoveryHandler: groupDiscoveryHandler,
|
||||
@@ -147,10 +152,15 @@ func NewCustomResourceDefinitionHandler(
|
||||
ret.removeDeadStorage()
|
||||
},
|
||||
})
|
||||
crConverterFactory, err := conversion.NewCRConverterFactory(serviceResolver, authResolverWrapper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.converterFactory = crConverterFactory
|
||||
|
||||
ret.customStorage.Store(crdStorageMap{})
|
||||
|
||||
return ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -220,10 +230,16 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
var handler http.HandlerFunc
|
||||
subresources, err := getSubresourcesForVersion(crd, requestInfo.APIVersion)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
http.Error(w, "the server could not properly serve the CR subresources", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case subresource == "status" && crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil:
|
||||
case subresource == "status" && subresources != nil && subresources.Status != nil:
|
||||
handler = r.serveStatus(w, req, requestInfo, crdInfo, terminating, supportedTypes)
|
||||
case subresource == "scale" && crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil:
|
||||
case subresource == "scale" && subresources != nil && subresources.Scale != nil:
|
||||
handler = r.serveScale(w, req, requestInfo, crdInfo, terminating, supportedTypes)
|
||||
case len(subresource) == 0:
|
||||
handler = r.serveResource(w, req, requestInfo, crdInfo, terminating, supportedTypes)
|
||||
@@ -334,11 +350,11 @@ func (r *crdHandler) updateCustomResourceDefinition(oldObj, newObj interface{})
|
||||
return
|
||||
}
|
||||
if apiequality.Semantic.DeepEqual(&newCRD.Spec, oldInfo.spec) && apiequality.Semantic.DeepEqual(&newCRD.Status.AcceptedNames, oldInfo.acceptedNames) {
|
||||
glog.V(6).Infof("Ignoring customresourcedefinition %s update because neither spec, nor accepted names changed", oldCRD.Name)
|
||||
klog.V(6).Infof("Ignoring customresourcedefinition %s update because neither spec, nor accepted names changed", oldCRD.Name)
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Updating customresourcedefinition %s", oldCRD.Name)
|
||||
klog.V(4).Infof("Updating customresourcedefinition %s", oldCRD.Name)
|
||||
|
||||
// Copy because we cannot write to storageMap without a race
|
||||
// as it is used without locking elsewhere.
|
||||
@@ -378,7 +394,7 @@ func (r *crdHandler) removeDeadStorage() {
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
glog.V(4).Infof("Removing dead CRD storage for %s/%s", s.spec.Group, s.spec.Names.Kind)
|
||||
klog.V(4).Infof("Removing dead CRD storage for %s/%s", s.spec.Group, s.spec.Names.Kind)
|
||||
for _, storage := range s.storages {
|
||||
// destroy only the main storage. Those for the subresources share cacher and etcd clients.
|
||||
storage.CustomResource.DestroyFunc()
|
||||
@@ -427,7 +443,10 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
||||
scaleScopes := map[string]handlers.RequestScope{}
|
||||
|
||||
for _, v := range crd.Spec.Versions {
|
||||
safeConverter, unsafeConverter := conversion.NewCRDConverter(crd)
|
||||
safeConverter, unsafeConverter, err := r.converterFactory.NewConverter(crd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// In addition to Unstructured objects (Custom Resources), we also may sometimes need to
|
||||
// decode unversioned Options objects, so we delegate to parameterScheme for such types.
|
||||
parameterScheme := runtime.NewScheme()
|
||||
@@ -443,19 +462,28 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
||||
typer := newUnstructuredObjectTyper(parameterScheme)
|
||||
creator := unstructuredCreator{}
|
||||
|
||||
validator, _, err := apiservervalidation.NewSchemaValidator(crd.Spec.Validation)
|
||||
validationSchema, err := getSchemaForVersion(crd, v.Name)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return nil, fmt.Errorf("the server could not properly serve the CR schema")
|
||||
}
|
||||
validator, _, err := apiservervalidation.NewSchemaValidator(validationSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var statusSpec *apiextensions.CustomResourceSubresourceStatus
|
||||
var statusValidator *validate.SchemaValidator
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil {
|
||||
statusSpec = crd.Spec.Subresources.Status
|
||||
|
||||
subresources, err := getSubresourcesForVersion(crd, v.Name)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return nil, fmt.Errorf("the server could not properly serve the CR subresources")
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil {
|
||||
statusSpec = subresources.Status
|
||||
// for the status subresource, validate only against the status schema
|
||||
if crd.Spec.Validation != nil && crd.Spec.Validation.OpenAPIV3Schema != nil && crd.Spec.Validation.OpenAPIV3Schema.Properties != nil {
|
||||
if statusSchema, ok := crd.Spec.Validation.OpenAPIV3Schema.Properties["status"]; ok {
|
||||
if validationSchema != nil && validationSchema.OpenAPIV3Schema != nil && validationSchema.OpenAPIV3Schema.Properties != nil {
|
||||
if statusSchema, ok := validationSchema.OpenAPIV3Schema.Properties["status"]; ok {
|
||||
openapiSchema := &spec.Schema{}
|
||||
if err := apiservervalidation.ConvertJSONSchemaProps(&statusSchema, openapiSchema); err != nil {
|
||||
return nil, err
|
||||
@@ -466,13 +494,18 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
||||
}
|
||||
|
||||
var scaleSpec *apiextensions.CustomResourceSubresourceScale
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil {
|
||||
scaleSpec = crd.Spec.Subresources.Scale
|
||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Scale != nil {
|
||||
scaleSpec = subresources.Scale
|
||||
}
|
||||
|
||||
table, err := tableconvertor.New(crd.Spec.AdditionalPrinterColumns)
|
||||
columns, err := getColumnsForVersion(crd, v.Name)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("The CRD for %v has an invalid printer specification, falling back to default printing: %v", kind, err)
|
||||
utilruntime.HandleError(err)
|
||||
return nil, fmt.Errorf("the server could not properly serve the CR columns")
|
||||
}
|
||||
table, err := tableconvertor.New(columns)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("The CRD for %v has an invalid printer specification, falling back to default printing: %v", kind, err)
|
||||
}
|
||||
|
||||
storages[v.Name] = customresource.NewStorage(
|
||||
|
14
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler_test.go
generated
vendored
14
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler_test.go
generated
vendored
@@ -79,14 +79,24 @@ func TestConvertFieldLabel(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
crd := apiextensions.CustomResourceDefinition{}
|
||||
crd := apiextensions.CustomResourceDefinition{
|
||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||
Conversion: &apiextensions.CustomResourceConversion{
|
||||
Strategy: "None",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if test.clusterScoped {
|
||||
crd.Spec.Scope = apiextensions.ClusterScoped
|
||||
} else {
|
||||
crd.Spec.Scope = apiextensions.NamespaceScoped
|
||||
}
|
||||
_, c := conversion.NewCRDConverter(&crd)
|
||||
f, err := conversion.NewCRConverterFactory(nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, c, err := f.NewConverter(&crd)
|
||||
|
||||
label, value, err := c.ConvertFieldLabel(schema.GroupVersionKind{}, test.label, "value")
|
||||
if e, a := test.expectError, err != nil; e != a {
|
||||
|
117
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go
generated
vendored
Normal file
117
vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
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 apiserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
)
|
||||
|
||||
// getSchemaForVersion returns the validation schema for given version in given CRD.
|
||||
func getSchemaForVersion(crd *apiextensions.CustomResourceDefinition, version string) (*apiextensions.CustomResourceValidation, error) {
|
||||
if !hasPerVersionSchema(crd.Spec.Versions) {
|
||||
return crd.Spec.Validation, nil
|
||||
}
|
||||
if crd.Spec.Validation != nil {
|
||||
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version schemas must be mutual exclusive", crd.Name, version)
|
||||
}
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if version == v.Name {
|
||||
return v.Schema, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
||||
}
|
||||
|
||||
// getSubresourcesForVersion returns the subresources for given version in given CRD.
|
||||
func getSubresourcesForVersion(crd *apiextensions.CustomResourceDefinition, version string) (*apiextensions.CustomResourceSubresources, error) {
|
||||
if !hasPerVersionSubresources(crd.Spec.Versions) {
|
||||
return crd.Spec.Subresources, nil
|
||||
}
|
||||
if crd.Spec.Subresources != nil {
|
||||
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version subresources must be mutual exclusive", crd.Name, version)
|
||||
}
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if version == v.Name {
|
||||
return v.Subresources, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
||||
}
|
||||
|
||||
// getColumnsForVersion returns the columns for given version in given CRD.
|
||||
// NOTE: the newly logically-defaulted columns is not pointing to the original CRD object.
|
||||
// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through
|
||||
// the original CRD object instead.
|
||||
func getColumnsForVersion(crd *apiextensions.CustomResourceDefinition, version string) ([]apiextensions.CustomResourceColumnDefinition, error) {
|
||||
if !hasPerVersionColumns(crd.Spec.Versions) {
|
||||
return serveDefaultColumnsIfEmpty(crd.Spec.AdditionalPrinterColumns), nil
|
||||
}
|
||||
if len(crd.Spec.AdditionalPrinterColumns) > 0 {
|
||||
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version additionalPrinterColumns must be mutual exclusive", crd.Name, version)
|
||||
}
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if version == v.Name {
|
||||
return serveDefaultColumnsIfEmpty(v.AdditionalPrinterColumns), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
||||
}
|
||||
|
||||
// serveDefaultColumnsIfEmpty applies logically defaulting to columns, if the input columns is empty.
|
||||
// NOTE: in this way, the newly logically-defaulted columns is not pointing to the original CRD object.
|
||||
// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through
|
||||
// the original CRD object instead.
|
||||
func serveDefaultColumnsIfEmpty(columns []apiextensions.CustomResourceColumnDefinition) []apiextensions.CustomResourceColumnDefinition {
|
||||
if len(columns) > 0 {
|
||||
return columns
|
||||
}
|
||||
return []apiextensions.CustomResourceColumnDefinition{
|
||||
{Name: "Age", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"], JSONPath: ".metadata.creationTimestamp"},
|
||||
}
|
||||
}
|
||||
|
||||
// hasPerVersionSchema returns true if a CRD uses per-version schema.
|
||||
func hasPerVersionSchema(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if v.Schema != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPerVersionSubresources returns true if a CRD uses per-version subresources.
|
||||
func hasPerVersionSubresources(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if v.Subresources != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasPerVersionColumns returns true if a CRD uses per-version columns.
|
||||
func hasPerVersionColumns(versions []apiextensions.CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if len(v.AdditionalPrinterColumns) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Reference in New Issue
Block a user