github.com/alex123012/deckhouse-controller-tools@v0.0.0-20230510090815-d594daf1af8c/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  	"sigs.k8s.io/controller-tools/pkg/loader"
    26  	"sigs.k8s.io/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  }