parser.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  17. "k8s.io/apimachinery/pkg/runtime/schema"
  18. "sigs.k8s.io/controller-tools/pkg/loader"
  19. "sigs.k8s.io/controller-tools/pkg/markers"
  20. )
  21. // TypeIdent represents some type in a Package.
  22. type TypeIdent struct {
  23. Package *loader.Package
  24. Name string
  25. }
  26. func (t TypeIdent) String() string {
  27. return fmt.Sprintf("%q.%s", t.Package.ID, t.Name)
  28. }
  29. // PackageOverride overrides the loading of some package
  30. // (potentially setting custom schemata, etc). It must
  31. // call AddPackage if it wants to continue with the default
  32. // loading behavior.
  33. type PackageOverride func(p *Parser, pkg *loader.Package)
  34. // Parser knows how to parse out CRD information and generate
  35. // OpenAPI schemata from some collection of types and markers.
  36. // Most methods on Parser cache their results automatically,
  37. // and thus may be called any number of times.
  38. type Parser struct {
  39. Collector *markers.Collector
  40. // Types contains the known TypeInfo for this parser.
  41. Types map[TypeIdent]*markers.TypeInfo
  42. // Schemata contains the known OpenAPI JSONSchemata for this parser.
  43. Schemata map[TypeIdent]apiext.JSONSchemaProps
  44. // GroupVersions contains the known group-versions of each package in this parser.
  45. GroupVersions map[*loader.Package]schema.GroupVersion
  46. // CustomResourceDefinitions contains the known CustomResourceDefinitions for types in this parser.
  47. CustomResourceDefinitions map[schema.GroupKind]apiext.CustomResourceDefinition
  48. // FlattenedSchemata contains fully flattened schemata for use in building
  49. // CustomResourceDefinition validation. Each schema has been flattened by the flattener,
  50. // and then embedded fields have been flattened with FlattenEmbedded.
  51. FlattenedSchemata map[TypeIdent]apiext.JSONSchemaProps
  52. // PackageOverrides indicates that the loading of any package with
  53. // the given path should be handled by the given overrider.
  54. PackageOverrides map[string]PackageOverride
  55. // checker stores persistent partial type-checking/reference-traversal information.
  56. Checker *loader.TypeChecker
  57. // packages marks packages as loaded, to avoid re-loading them.
  58. packages map[*loader.Package]struct{}
  59. flattener *Flattener
  60. // AllowDangerousTypes controls the handling of non-recommended types such as float. If
  61. // false (the default), these types are not supported.
  62. // There is a continuum here:
  63. // 1. Types that are always supported.
  64. // 2. Types that are allowed by default, but not recommended (warning emitted when they are encountered as per PR #443).
  65. // Possibly they are allowed by default for historical reasons and may even be "on their way out" at some point in the future.
  66. // 3. Types that are not allowed by default, not recommended, but there are some legitimate reasons to need them in certain corner cases.
  67. // Possibly these types should also emit a warning as per PR #443 even when they are "switched on" (an integration point between
  68. // this feature and #443 if desired). This is the category that this flag deals with.
  69. // 4. Types that are not allowed and will not be allowed, possibly because it just "doesn't make sense" or possibly
  70. // because the implementation is too difficult/clunky to promote them to category 3.
  71. // TODO: Should we have a more formal mechanism for putting "type patterns" in each of the above categories?
  72. AllowDangerousTypes bool
  73. // IgnoreUnexportedFields specifies if unexported fields on the struct should be skipped
  74. IgnoreUnexportedFields bool
  75. // GenerateEmbeddedObjectMeta specifies if any embedded ObjectMeta should be generated
  76. GenerateEmbeddedObjectMeta bool
  77. }
  78. func (p *Parser) init() {
  79. if p.packages == nil {
  80. p.packages = make(map[*loader.Package]struct{})
  81. }
  82. if p.flattener == nil {
  83. p.flattener = &Flattener{
  84. Parser: p,
  85. }
  86. }
  87. if p.Schemata == nil {
  88. p.Schemata = make(map[TypeIdent]apiext.JSONSchemaProps)
  89. }
  90. if p.Types == nil {
  91. p.Types = make(map[TypeIdent]*markers.TypeInfo)
  92. }
  93. if p.PackageOverrides == nil {
  94. p.PackageOverrides = make(map[string]PackageOverride)
  95. }
  96. if p.GroupVersions == nil {
  97. p.GroupVersions = make(map[*loader.Package]schema.GroupVersion)
  98. }
  99. if p.CustomResourceDefinitions == nil {
  100. p.CustomResourceDefinitions = make(map[schema.GroupKind]apiext.CustomResourceDefinition)
  101. }
  102. if p.FlattenedSchemata == nil {
  103. p.FlattenedSchemata = make(map[TypeIdent]apiext.JSONSchemaProps)
  104. }
  105. }
  106. // indexTypes loads all types in the package into Types.
  107. func (p *Parser) indexTypes(pkg *loader.Package) {
  108. // autodetect
  109. pkgMarkers, err := markers.PackageMarkers(p.Collector, pkg)
  110. if err != nil {
  111. pkg.AddError(err)
  112. } else {
  113. if skipPkg := pkgMarkers.Get("kubebuilder:skip"); skipPkg != nil {
  114. return
  115. }
  116. if nameVal := pkgMarkers.Get("groupName"); nameVal != nil {
  117. versionVal := pkg.Name // a reasonable guess
  118. if versionMarker := pkgMarkers.Get("versionName"); versionMarker != nil {
  119. versionVal = versionMarker.(string)
  120. }
  121. p.GroupVersions[pkg] = schema.GroupVersion{
  122. Version: versionVal,
  123. Group: nameVal.(string),
  124. }
  125. }
  126. }
  127. if err := markers.EachType(p.Collector, pkg, func(info *markers.TypeInfo) {
  128. ident := TypeIdent{
  129. Package: pkg,
  130. Name: info.Name,
  131. }
  132. p.Types[ident] = info
  133. }); err != nil {
  134. pkg.AddError(err)
  135. }
  136. }
  137. // LookupType fetches type info from Types.
  138. func (p *Parser) LookupType(pkg *loader.Package, name string) *markers.TypeInfo {
  139. return p.Types[TypeIdent{Package: pkg, Name: name}]
  140. }
  141. // NeedSchemaFor indicates that a schema should be generated for the given type.
  142. func (p *Parser) NeedSchemaFor(typ TypeIdent) {
  143. p.init()
  144. p.NeedPackage(typ.Package)
  145. if _, knownSchema := p.Schemata[typ]; knownSchema {
  146. return
  147. }
  148. info, knownInfo := p.Types[typ]
  149. if !knownInfo {
  150. typ.Package.AddError(fmt.Errorf("unknown type %s", typ))
  151. return
  152. }
  153. // avoid tripping recursive schemata, like ManagedFields, by adding an empty WIP schema
  154. p.Schemata[typ] = apiext.JSONSchemaProps{}
  155. schemaCtx := newSchemaContext(typ.Package, p, p.AllowDangerousTypes, p.IgnoreUnexportedFields)
  156. ctxForInfo := schemaCtx.ForInfo(info)
  157. pkgMarkers, err := markers.PackageMarkers(p.Collector, typ.Package)
  158. if err != nil {
  159. typ.Package.AddError(err)
  160. }
  161. ctxForInfo.PackageMarkers = pkgMarkers
  162. schema := infoToSchema(ctxForInfo)
  163. p.Schemata[typ] = *schema
  164. }
  165. func (p *Parser) NeedFlattenedSchemaFor(typ TypeIdent) {
  166. p.init()
  167. if _, knownSchema := p.FlattenedSchemata[typ]; knownSchema {
  168. return
  169. }
  170. p.NeedSchemaFor(typ)
  171. partialFlattened := p.flattener.FlattenType(typ)
  172. fullyFlattened := FlattenEmbedded(partialFlattened, typ.Package)
  173. p.FlattenedSchemata[typ] = *fullyFlattened
  174. }
  175. // NeedCRDFor lives off in spec.go
  176. // AddPackage indicates that types and type-checking information is needed
  177. // for the the given package, *ignoring* overrides.
  178. // Generally, consumers should call NeedPackage, while PackageOverrides should
  179. // call AddPackage to continue with the normal loading procedure.
  180. func (p *Parser) AddPackage(pkg *loader.Package) {
  181. p.init()
  182. if _, checked := p.packages[pkg]; checked {
  183. return
  184. }
  185. p.indexTypes(pkg)
  186. p.Checker.Check(pkg)
  187. p.packages[pkg] = struct{}{}
  188. }
  189. // NeedPackage indicates that types and type-checking information
  190. // is needed for the given package.
  191. func (p *Parser) NeedPackage(pkg *loader.Package) {
  192. p.init()
  193. if _, checked := p.packages[pkg]; checked {
  194. return
  195. }
  196. // overrides are going to be written without vendor. This is why we index by the actual
  197. // object when we can.
  198. if override, overridden := p.PackageOverrides[loader.NonVendorPath(pkg.PkgPath)]; overridden {
  199. override(p, pkg)
  200. p.packages[pkg] = struct{}{}
  201. return
  202. }
  203. p.AddPackage(pkg)
  204. }