From 48adc8ec042edf930a972e01e959030f67a77023 Mon Sep 17 00:00:00 2001 From: Haowei Cai Date: Thu, 31 Jan 2019 09:35:16 -0800 Subject: [PATCH] apiextensions-apiserver: make API helpers reusable --- .../pkg/apis/apiextensions/helpers.go | 127 ++++++++++++++++-- .../pkg/apiserver/BUILD | 1 - .../customresource_discovery_controller.go | 2 +- .../pkg/apiserver/customresource_handler.go | 10 +- .../pkg/apiserver/helpers.go | 117 ---------------- 5 files changed, 122 insertions(+), 135 deletions(-) delete mode 100644 staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go index 92cad7d9b7..964a6190ba 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go @@ -23,8 +23,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// SetCRDCondition sets the status condition. It either overwrites the existing one or -// creates a new one +var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() + +// SetCRDCondition sets the status condition. It either overwrites the existing one or creates a new one. func SetCRDCondition(crd *CustomResourceDefinition, newCondition CustomResourceDefinitionCondition) { existingCondition := FindCRDCondition(crd, newCondition.Type) if existingCondition == nil { @@ -53,7 +54,7 @@ func RemoveCRDCondition(crd *CustomResourceDefinition, conditionType CustomResou crd.Status.Conditions = newConditions } -// FindCRDCondition returns the condition you're looking for or nil +// FindCRDCondition returns the condition you're looking for or nil. func FindCRDCondition(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) *CustomResourceDefinitionCondition { for i := range crd.Status.Conditions { if crd.Status.Conditions[i].Type == conditionType { @@ -64,17 +65,17 @@ func FindCRDCondition(crd *CustomResourceDefinition, conditionType CustomResourc return nil } -// IsCRDConditionTrue indicates if the condition is present and strictly true +// IsCRDConditionTrue indicates if the condition is present and strictly true. func IsCRDConditionTrue(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) bool { return IsCRDConditionPresentAndEqual(crd, conditionType, ConditionTrue) } -// IsCRDConditionFalse indicates if the condition is present and false true +// IsCRDConditionFalse indicates if the condition is present and false. func IsCRDConditionFalse(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) bool { return IsCRDConditionPresentAndEqual(crd, conditionType, ConditionFalse) } -// IsCRDConditionPresentAndEqual indicates if the condition is present and equal to the arg +// IsCRDConditionPresentAndEqual indicates if the condition is present and equal to the given status. func IsCRDConditionPresentAndEqual(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType, status ConditionStatus) bool { for _, condition := range crd.Status.Conditions { if condition.Type == conditionType { @@ -84,7 +85,7 @@ func IsCRDConditionPresentAndEqual(crd *CustomResourceDefinition, conditionType return false } -// IsCRDConditionEquivalent returns true if the lhs and rhs are equivalent except for times +// IsCRDConditionEquivalent returns true if the lhs and rhs are equivalent except for times. func IsCRDConditionEquivalent(lhs, rhs *CustomResourceDefinitionCondition) bool { if lhs == nil && rhs == nil { return true @@ -96,7 +97,7 @@ func IsCRDConditionEquivalent(lhs, rhs *CustomResourceDefinitionCondition) bool return lhs.Message == rhs.Message && lhs.Reason == rhs.Reason && lhs.Status == rhs.Status && lhs.Type == rhs.Type } -// CRDHasFinalizer returns true if the finalizer is in the list +// CRDHasFinalizer returns true if the finalizer is in the list. func CRDHasFinalizer(crd *CustomResourceDefinition, needle string) bool { for _, finalizer := range crd.Finalizers { if finalizer == needle { @@ -107,7 +108,7 @@ func CRDHasFinalizer(crd *CustomResourceDefinition, needle string) bool { return false } -// CRDRemoveFinalizer removes the finalizer if present +// CRDRemoveFinalizer removes the finalizer if present. func CRDRemoveFinalizer(crd *CustomResourceDefinition, needle string) { newFinalizers := []string{} for _, finalizer := range crd.Finalizers { @@ -118,7 +119,7 @@ func CRDRemoveFinalizer(crd *CustomResourceDefinition, needle string) { crd.Finalizers = newFinalizers } -// HasServedCRDVersion returns true if `version` is in the list of CRD's versions and the Served flag is set. +// HasServedCRDVersion returns true if the given version is in the list of CRD's versions and the Served flag is set. func HasServedCRDVersion(crd *CustomResourceDefinition, version string) bool { for _, v := range crd.Spec.Versions { if v.Name == version { @@ -139,6 +140,7 @@ func GetCRDStorageVersion(crd *CustomResourceDefinition) (string, error) { return "", fmt.Errorf("invalid CustomResourceDefinition, no storage version") } +// IsStoredVersion returns whether the given version is the storage version of the CRD. func IsStoredVersion(crd *CustomResourceDefinition, version string) bool { for _, v := range crd.Status.StoredVersions { if version == v { @@ -147,3 +149,108 @@ func IsStoredVersion(crd *CustomResourceDefinition, version string) bool { } return false } + +// GetSchemaForVersion returns the validation schema for the given version or nil. +func GetSchemaForVersion(crd *CustomResourceDefinition, version string) (*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 or nil. +func GetSubresourcesForVersion(crd *CustomResourceDefinition, version string) (*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 or nil. +// 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 *CustomResourceDefinition, version string) ([]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) +} + +// HasPerVersionSchema returns true if a CRD uses per-version schema. +func HasPerVersionSchema(versions []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 []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 []CustomResourceDefinitionVersion) bool { + for _, v := range versions { + if len(v.AdditionalPrinterColumns) > 0 { + return true + } + } + return false +} + +// 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 []CustomResourceColumnDefinition) []CustomResourceColumnDefinition { + if len(columns) > 0 { + return columns + } + return []CustomResourceColumnDefinition{ + {Name: "Age", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"], JSONPath: ".metadata.creationTimestamp"}, + } +} + +// HasVersionServed returns true if given CRD has given version served. +func HasVersionServed(crd *CustomResourceDefinition, version string) bool { + for _, v := range crd.Spec.Versions { + if !v.Served || v.Name != version { + continue + } + return true + } + return false +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD index 1dbe1d792c..3304465ac1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/BUILD @@ -13,7 +13,6 @@ go_library( "customresource_discovery.go", "customresource_discovery_controller.go", "customresource_handler.go", - "helpers.go", ], importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver", importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver", diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go index 3b13e8357d..86203b2ba0 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go @@ -136,7 +136,7 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error { Categories: crd.Status.AcceptedNames.Categories, }) - subresources, err := getSubresourcesForVersion(crd, version.Version) + subresources, err := apiextensions.GetSubresourcesForVersion(crd, version.Version) if err != nil { return err } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index b26859e03e..c5c9a73228 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -239,7 +239,7 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { } var handler http.HandlerFunc - subresources, err := getSubresourcesForVersion(crd, requestInfo.APIVersion) + subresources, err := apiextensions.GetSubresourcesForVersion(crd, requestInfo.APIVersion) if err != nil { utilruntime.HandleError(err) http.Error(w, "the server could not properly serve the CR subresources", http.StatusInternalServerError) @@ -424,8 +424,6 @@ func (r *crdHandler) GetCustomResourceListerCollectionDeleter(crd *apiextensions return info.storages[info.storageVersion].CustomResource, nil } -var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() - func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResourceDefinition) (*crdInfo, error) { storageMap := r.customStorage.Load().(crdStorageMap) if ret, ok := storageMap[crd.UID]; ok { @@ -471,7 +469,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource typer := newUnstructuredObjectTyper(parameterScheme) creator := unstructuredCreator{} - validationSchema, err := getSchemaForVersion(crd, v.Name) + validationSchema, err := apiextensions.GetSchemaForVersion(crd, v.Name) if err != nil { utilruntime.HandleError(err) return nil, fmt.Errorf("the server could not properly serve the CR schema") @@ -483,7 +481,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource var statusSpec *apiextensions.CustomResourceSubresourceStatus var statusValidator *validate.SchemaValidator - subresources, err := getSubresourcesForVersion(crd, v.Name) + subresources, err := apiextensions.GetSubresourcesForVersion(crd, v.Name) if err != nil { utilruntime.HandleError(err) return nil, fmt.Errorf("the server could not properly serve the CR subresources") @@ -507,7 +505,7 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource scaleSpec = subresources.Scale } - columns, err := getColumnsForVersion(crd, v.Name) + columns, err := apiextensions.GetColumnsForVersion(crd, v.Name) if err != nil { utilruntime.HandleError(err) return nil, fmt.Errorf("the server could not properly serve the CR columns") diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go deleted file mode 100644 index ae77bfcd5a..0000000000 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go +++ /dev/null @@ -1,117 +0,0 @@ -/* -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 -}