mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +00:00
API server serves CRD per-version schema/subresources/columns
This commit is contained in:
parent
0ab3d2c344
commit
1282ed5efe
@ -136,7 +136,11 @@ func (c *DiscoveryController) sync(version schema.GroupVersion) error {
|
|||||||
Categories: crd.Status.AcceptedNames.Categories,
|
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{
|
apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{
|
||||||
Name: crd.Status.AcceptedNames.Plural + "/status",
|
Name: crd.Status.AcceptedNames.Plural + "/status",
|
||||||
Namespaced: crd.Spec.Scope == apiextensions.NamespaceScoped,
|
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{
|
apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{
|
||||||
Group: autoscaling.GroupName,
|
Group: autoscaling.GroupName,
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
|
@ -220,10 +220,16 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var handler http.HandlerFunc
|
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 {
|
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)
|
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)
|
handler = r.serveScale(w, req, requestInfo, crdInfo, terminating, supportedTypes)
|
||||||
case len(subresource) == 0:
|
case len(subresource) == 0:
|
||||||
handler = r.serveResource(w, req, requestInfo, crdInfo, terminating, supportedTypes)
|
handler = r.serveResource(w, req, requestInfo, crdInfo, terminating, supportedTypes)
|
||||||
@ -443,19 +449,28 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
|||||||
typer := newUnstructuredObjectTyper(parameterScheme)
|
typer := newUnstructuredObjectTyper(parameterScheme)
|
||||||
creator := unstructuredCreator{}
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusSpec *apiextensions.CustomResourceSubresourceStatus
|
var statusSpec *apiextensions.CustomResourceSubresourceStatus
|
||||||
var statusValidator *validate.SchemaValidator
|
var statusValidator *validate.SchemaValidator
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && crd.Spec.Subresources != nil && crd.Spec.Subresources.Status != nil {
|
subresources, err := getSubresourcesForVersion(crd, v.Name)
|
||||||
statusSpec = crd.Spec.Subresources.Status
|
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
|
// 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 validationSchema != nil && validationSchema.OpenAPIV3Schema != nil && validationSchema.OpenAPIV3Schema.Properties != nil {
|
||||||
if statusSchema, ok := crd.Spec.Validation.OpenAPIV3Schema.Properties["status"]; ok {
|
if statusSchema, ok := validationSchema.OpenAPIV3Schema.Properties["status"]; ok {
|
||||||
openapiSchema := &spec.Schema{}
|
openapiSchema := &spec.Schema{}
|
||||||
if err := apiservervalidation.ConvertJSONSchemaProps(&statusSchema, openapiSchema); err != nil {
|
if err := apiservervalidation.ConvertJSONSchemaProps(&statusSchema, openapiSchema); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -466,11 +481,16 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
|||||||
}
|
}
|
||||||
|
|
||||||
var scaleSpec *apiextensions.CustomResourceSubresourceScale
|
var scaleSpec *apiextensions.CustomResourceSubresourceScale
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && crd.Spec.Subresources != nil && crd.Spec.Subresources.Scale != nil {
|
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Scale != nil {
|
||||||
scaleSpec = crd.Spec.Subresources.Scale
|
scaleSpec = subresources.Scale
|
||||||
}
|
}
|
||||||
|
|
||||||
table, err := tableconvertor.New(crd.Spec.AdditionalPrinterColumns)
|
columns, err := getColumnsForVersion(crd, v.Name)
|
||||||
|
if err != nil {
|
||||||
|
utilruntime.HandleError(err)
|
||||||
|
return nil, fmt.Errorf("the server could not properly serve the CR columns")
|
||||||
|
}
|
||||||
|
table, err := tableconvertor.New(columns)
|
||||||
if err != 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)
|
glog.V(2).Infof("The CRD for %v has an invalid printer specification, falling back to default printing: %v", kind, err)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user