conv.go 4.6 KB

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