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