spec.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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.
  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. // NeedCRDFor requests the full CRD for the given group-kind. It requires
  32. // that the packages containing the Go structs for that CRD have already
  33. // been loaded with NeedPackage.
  34. func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) {
  35. p.init()
  36. if _, exists := p.CustomResourceDefinitions[groupKind]; exists {
  37. return
  38. }
  39. var packages []*loader.Package
  40. for pkg, gv := range p.GroupVersions {
  41. if gv.Group != groupKind.Group {
  42. continue
  43. }
  44. packages = append(packages, pkg)
  45. }
  46. defaultPlural := strings.ToLower(flect.Pluralize(groupKind.Kind))
  47. crd := apiext.CustomResourceDefinition{
  48. TypeMeta: metav1.TypeMeta{
  49. APIVersion: apiext.SchemeGroupVersion.String(),
  50. Kind: "CustomResourceDefinition",
  51. },
  52. ObjectMeta: metav1.ObjectMeta{
  53. Name: defaultPlural + "." + groupKind.Group,
  54. },
  55. Spec: apiext.CustomResourceDefinitionSpec{
  56. Group: groupKind.Group,
  57. Names: apiext.CustomResourceDefinitionNames{
  58. Kind: groupKind.Kind,
  59. ListKind: groupKind.Kind + "List",
  60. Plural: defaultPlural,
  61. Singular: strings.ToLower(groupKind.Kind),
  62. },
  63. Scope: apiext.NamespaceScoped,
  64. },
  65. }
  66. for _, pkg := range packages {
  67. typeIdent := TypeIdent{Package: pkg, Name: groupKind.Kind}
  68. typeInfo := p.Types[typeIdent]
  69. if typeInfo == nil {
  70. continue
  71. }
  72. p.NeedFlattenedSchemaFor(typeIdent)
  73. fullSchema := p.FlattenedSchemata[typeIdent]
  74. fullSchema = *fullSchema.DeepCopy() // don't mutate the cache (we might be truncating description, etc)
  75. if maxDescLen != nil {
  76. TruncateDescription(&fullSchema, *maxDescLen)
  77. }
  78. ver := apiext.CustomResourceDefinitionVersion{
  79. Name: p.GroupVersions[pkg].Version,
  80. Served: true,
  81. Schema: &apiext.CustomResourceValidation{
  82. OpenAPIV3Schema: &fullSchema, // fine to take a reference since we deepcopy above
  83. },
  84. }
  85. crd.Spec.Versions = append(crd.Spec.Versions, ver)
  86. }
  87. // markers are applied *after* initial generation of objects
  88. for _, pkg := range packages {
  89. typeIdent := TypeIdent{Package: pkg, Name: groupKind.Kind}
  90. typeInfo := p.Types[typeIdent]
  91. if typeInfo == nil {
  92. continue
  93. }
  94. ver := p.GroupVersions[pkg].Version
  95. for _, markerVals := range typeInfo.Markers {
  96. for _, val := range markerVals {
  97. crdMarker, isCrdMarker := val.(SpecMarker)
  98. if !isCrdMarker {
  99. continue
  100. }
  101. if err := crdMarker.ApplyToCRD(&crd.Spec, ver); err != nil {
  102. pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
  103. }
  104. }
  105. }
  106. }
  107. // fix the name if the plural was changed (this is the form the name *has* to take, so no harm in changing it).
  108. crd.Name = crd.Spec.Names.Plural + "." + groupKind.Group
  109. // nothing to actually write
  110. if len(crd.Spec.Versions) == 0 {
  111. return
  112. }
  113. // it is necessary to make sure the order of CRD versions in crd.Spec.Versions is stable and explicitly set crd.Spec.Version.
  114. // Otherwise, crd.Spec.Version may point to different CRD versions across different runs.
  115. sort.Slice(crd.Spec.Versions, func(i, j int) bool { return crd.Spec.Versions[i].Name < crd.Spec.Versions[j].Name })
  116. // make sure we have *a* storage version
  117. // (default it if we only have one, otherwise, bail)
  118. if len(crd.Spec.Versions) == 1 {
  119. crd.Spec.Versions[0].Storage = true
  120. }
  121. hasStorage := false
  122. for _, ver := range crd.Spec.Versions {
  123. if ver.Storage {
  124. hasStorage = true
  125. break
  126. }
  127. }
  128. if !hasStorage {
  129. // just add the error to the first relevant package for this CRD,
  130. // since there's no specific error location
  131. packages[0].AddError(fmt.Errorf("CRD for %s has no storage version", groupKind))
  132. }
  133. served := false
  134. for _, ver := range crd.Spec.Versions {
  135. if ver.Served {
  136. served = true
  137. break
  138. }
  139. }
  140. if !served {
  141. // just add the error to the first relevant package for this CRD,
  142. // since there's no specific error location
  143. packages[0].AddError(fmt.Errorf("CRD for %s with version(s) %v does not serve any version", groupKind, crd.Spec.Versions))
  144. }
  145. // NB(directxman12): CRD's status doesn't have omitempty markers, which means things
  146. // get serialized as null, which causes the validator to freak out. Manually set
  147. // these to empty till we get a better solution.
  148. crd.Status.Conditions = []apiext.CustomResourceDefinitionCondition{}
  149. crd.Status.StoredVersions = []string{}
  150. p.CustomResourceDefinitions[groupKind] = crd
  151. }