github.com/caseydavenport/controller-tools@v0.2.6-0.20200519183242-e8a18b1a6750/pkg/crd/conv.go (about)

     1  package crd
     2  
     3  import (
     4  	"fmt"
     5  
     6  	apiextinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
     7  	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
     8  	apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
     9  	"k8s.io/apimachinery/pkg/api/equality"
    10  	"k8s.io/apimachinery/pkg/runtime"
    11  	"k8s.io/apimachinery/pkg/runtime/schema"
    12  )
    13  
    14  var (
    15  	conversionScheme = runtime.NewScheme()
    16  )
    17  
    18  func init() {
    19  	if err := apiextinternal.AddToScheme(conversionScheme); err != nil {
    20  		panic("must be able to add internal apiextensions to the CRD conversion Scheme")
    21  	}
    22  	if err := apiext.AddToScheme(conversionScheme); err != nil {
    23  		panic("must be able to add apiextensions/v1 to the CRD conversion Scheme")
    24  	}
    25  	if err := apiextv1beta1.AddToScheme(conversionScheme); err != nil {
    26  		panic("must be able to add apiextensions/v1beta1 to the CRD conversion Scheme")
    27  	}
    28  }
    29  
    30  // AsVersion converts a CRD from the canonical internal form (currently v1) to some external form.
    31  func AsVersion(original apiext.CustomResourceDefinition, gv schema.GroupVersion) (runtime.Object, error) {
    32  	// We can use the internal versions an existing conversions from kubernetes, since they're not in k/k itself.
    33  	// This punts the problem of conversion down the road for a future maintainer (or future instance of @directxman12)
    34  	// when we have to support older versions that get removed, or when API machinery decides to yell at us for this
    35  	// questionable decision.
    36  	intVer, err := conversionScheme.ConvertToVersion(&original, apiextinternal.SchemeGroupVersion)
    37  	if err != nil {
    38  		return nil, fmt.Errorf("unable to convert to internal CRD version: %w", err)
    39  	}
    40  
    41  	return conversionScheme.ConvertToVersion(intVer, gv)
    42  }
    43  
    44  // mergeIdenticalSubresources checks to see if subresources are identical across
    45  // all versions, and if so, merges them into a top-level version.
    46  //
    47  // This assumes you're not using trivial versions.
    48  func mergeIdenticalSubresources(crd *apiextv1beta1.CustomResourceDefinition) {
    49  	subres := crd.Spec.Versions[0].Subresources
    50  	for _, ver := range crd.Spec.Versions {
    51  		if ver.Subresources == nil || !equality.Semantic.DeepEqual(subres, ver.Subresources) {
    52  			// either all nil, or not identical
    53  			return
    54  		}
    55  	}
    56  
    57  	// things are identical if we've gotten this far, so move the subresources up
    58  	// and discard the identical per-version ones
    59  	crd.Spec.Subresources = subres
    60  	for i := range crd.Spec.Versions {
    61  		crd.Spec.Versions[i].Subresources = nil
    62  	}
    63  }
    64  
    65  // mergeIdenticalSchemata checks to see if schemata are identical across
    66  // all versions, and if so, merges them into a top-level version.
    67  //
    68  // This assumes you're not using trivial versions.
    69  func mergeIdenticalSchemata(crd *apiextv1beta1.CustomResourceDefinition) {
    70  	schema := crd.Spec.Versions[0].Schema
    71  	for _, ver := range crd.Spec.Versions {
    72  		if ver.Schema == nil || !equality.Semantic.DeepEqual(schema, ver.Schema) {
    73  			// either all nil, or not identical
    74  			return
    75  		}
    76  	}
    77  
    78  	// things are identical if we've gotten this far, so move the schemata up
    79  	// to a single schema and discard the identical per-version ones
    80  	crd.Spec.Validation = schema
    81  	for i := range crd.Spec.Versions {
    82  		crd.Spec.Versions[i].Schema = nil
    83  	}
    84  }
    85  
    86  // mergeIdenticalPrinterColumns checks to see if schemata are identical across
    87  // all versions, and if so, merges them into a top-level version.
    88  //
    89  // This assumes you're not using trivial versions.
    90  func mergeIdenticalPrinterColumns(crd *apiextv1beta1.CustomResourceDefinition) {
    91  	cols := crd.Spec.Versions[0].AdditionalPrinterColumns
    92  	for _, ver := range crd.Spec.Versions {
    93  		if len(ver.AdditionalPrinterColumns) == 0 || !equality.Semantic.DeepEqual(cols, ver.AdditionalPrinterColumns) {
    94  			// either all nil, or not identical
    95  			return
    96  		}
    97  	}
    98  
    99  	// things are identical if we've gotten this far, so move the printer columns up
   100  	// and discard the identical per-version ones
   101  	crd.Spec.AdditionalPrinterColumns = cols
   102  	for i := range crd.Spec.Versions {
   103  		crd.Spec.Versions[i].AdditionalPrinterColumns = nil
   104  	}
   105  }
   106  
   107  // MergeIdenticalVersionInfo makes sure that components of the Versions field that are identical
   108  // across all versions get merged into the top-level fields in v1beta1.
   109  //
   110  // This is required by the Kubernetes API server validation.
   111  //
   112  // The reason is that a v1beta1 -> v1 -> v1beta1 conversion cycle would need to
   113  // round-trip identically, v1 doesn't have top-level subresources, and without
   114  // this restriction it would be ambiguous how a v1-with-identical-subresources
   115  // converts into a v1beta1).
   116  func MergeIdenticalVersionInfo(crd *apiextv1beta1.CustomResourceDefinition) {
   117  	if len(crd.Spec.Versions) > 0 {
   118  		mergeIdenticalSubresources(crd)
   119  		mergeIdenticalSchemata(crd)
   120  		mergeIdenticalPrinterColumns(crd)
   121  	}
   122  }