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