spec.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package crd
  14. import (
  15. "fmt"
  16. "sort"
  17. "strings"
  18. "github.com/gobuffalo/flect"
  19. apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/runtime/schema"
  22. "sigs.k8s.io/controller-tools/pkg/loader"
  23. )
  24. // SpecMarker is a marker that knows how to apply itself to a particular
  25. // version in a CRD Spec.
  26. type SpecMarker interface {
  27. // ApplyToCRD applies this marker to the given CRD, in the given version
  28. // within that CRD. It's called after everything else in the CRD is populated.
  29. ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error
  30. }
  31. // Marker is a marker that knows how to apply itself to a particular
  32. // version in a CRD.
  33. type Marker interface {
  34. // ApplyToCRD applies this marker to the given CRD, in the given version
  35. // within that CRD. It's called after everything else in the CRD is populated.
  36. ApplyToCRD(crd *apiext.CustomResourceDefinition, version string) error
  37. }
  38. // NeedCRDFor requests the full CRD for the given group-kind. It requires
  39. // that the packages containing the Go structs for that CRD have already
  40. // been loaded with NeedPackage.
  41. func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) {
  42. p.init()
  43. if _, exists := p.CustomResourceDefinitions[groupKind]; exists {
  44. return
  45. }
  46. var packages []*loader.Package
  47. for pkg, gv := range p.GroupVersions {
  48. if gv.Group != groupKind.Group {
  49. continue
  50. }
  51. packages = append(packages, pkg)
  52. }
  53. defaultPlural := strings.ToLower(flect.Pluralize(groupKind.Kind))
  54. crd := apiext.CustomResourceDefinition{
  55. TypeMeta: metav1.TypeMeta{
  56. APIVersion: apiext.SchemeGroupVersion.String(),
  57. Kind: "CustomResourceDefinition",
  58. },
  59. ObjectMeta: metav1.ObjectMeta{
  60. Name: defaultPlural + "." + groupKind.Group,
  61. },
  62. Spec: apiext.CustomResourceDefinitionSpec{
  63. Group: groupKind.Group,
  64. Names: apiext.CustomResourceDefinitionNames{
  65. Kind: groupKind.Kind,
  66. ListKind: groupKind.Kind + "List",
  67. Plural: defaultPlural,
  68. Singular: strings.ToLower(groupKind.Kind),
  69. },
  70. Scope: apiext.NamespaceScoped,
  71. },
  72. }
  73. for _, pkg := range packages {
  74. typeIdent := TypeIdent{Package: pkg, Name: groupKind.Kind}
  75. typeInfo := p.Types[typeIdent]
  76. if typeInfo == nil {
  77. continue
  78. }
  79. p.NeedFlattenedSchemaFor(typeIdent)
  80. fullSchema := p.FlattenedSchemata[typeIdent]
  81. fullSchema = *fullSchema.DeepCopy() // don't mutate the cache (we might be truncating description, etc)
  82. if maxDescLen != nil {
  83. TruncateDescription(&fullSchema, *maxDescLen)
  84. }
  85. ver := apiext.CustomResourceDefinitionVersion{
  86. Name: p.GroupVersions[pkg].Version,
  87. Served: true,
  88. Schema: &apiext.CustomResourceValidation{
  89. OpenAPIV3Schema: &fullSchema, // fine to take a reference since we deepcopy above
  90. },
  91. }
  92. crd.Spec.Versions = append(crd.Spec.Versions, ver)
  93. }
  94. // markers are applied *after* initial generation of objects
  95. for _, pkg := range packages {
  96. typeIdent := TypeIdent{Package: pkg, Name: groupKind.Kind}
  97. typeInfo := p.Types[typeIdent]
  98. if typeInfo == nil {
  99. continue
  100. }
  101. ver := p.GroupVersions[pkg].Version
  102. for _, markerVals := range typeInfo.Markers {
  103. for _, val := range markerVals {
  104. if specMarker, isSpecMarker := val.(SpecMarker); isSpecMarker {
  105. if err := specMarker.ApplyToCRD(&crd.Spec, ver); err != nil {
  106. pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
  107. }
  108. } else if crdMarker, isCRDMarker := val.(Marker); isCRDMarker {
  109. if err := crdMarker.ApplyToCRD(&crd, ver); err != nil {
  110. pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
  111. }
  112. }
  113. }
  114. }
  115. }
  116. // fix the name if the plural was changed (this is the form the name *has* to take, so no harm in changing it).
  117. crd.Name = crd.Spec.Names.Plural + "." + groupKind.Group
  118. // nothing to actually write
  119. if len(crd.Spec.Versions) == 0 {
  120. return
  121. }
  122. // it is necessary to make sure the order of CRD versions in crd.Spec.Versions is stable and explicitly set crd.Spec.Version.
  123. // Otherwise, crd.Spec.Version may point to different CRD versions across different runs.
  124. sort.Slice(crd.Spec.Versions, func(i, j int) bool { return crd.Spec.Versions[i].Name < crd.Spec.Versions[j].Name })
  125. // make sure we have *a* storage version
  126. // (default it if we only have one, otherwise, bail)
  127. if len(crd.Spec.Versions) == 1 {
  128. crd.Spec.Versions[0].Storage = true
  129. }
  130. hasStorage := false
  131. for _, ver := range crd.Spec.Versions {
  132. if ver.Storage {
  133. hasStorage = true
  134. break
  135. }
  136. }
  137. if !hasStorage {
  138. // just add the error to the first relevant package for this CRD,
  139. // since there's no specific error location
  140. packages[0].AddError(fmt.Errorf("CRD for %s has no storage version", groupKind))
  141. }
  142. served := false
  143. for _, ver := range crd.Spec.Versions {
  144. if ver.Served {
  145. served = true
  146. break
  147. }
  148. }
  149. if !served {
  150. // just add the error to the first relevant package for this CRD,
  151. // since there's no specific error location
  152. packages[0].AddError(fmt.Errorf("CRD for %s with version(s) %v does not serve any version", groupKind, crd.Spec.Versions))
  153. }
  154. p.CustomResourceDefinitions[groupKind] = crd
  155. }