github.com/regadas/controller-tools@v0.5.1-0.20210408091555-18885b17ff7b/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/regadas/controller-tools/pkg/loader"
    26  	"github.com/regadas/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  	// GenerateEmbeddedObjectMeta specifies if any embedded ObjectMeta should be generated
    91  	GenerateEmbeddedObjectMeta bool
    92  }
    93  
    94  func (p *Parser) init() {
    95  	if p.packages == nil {
    96  		p.packages = make(map[*loader.Package]struct{})
    97  	}
    98  	if p.flattener == nil {
    99  		p.flattener = &Flattener{
   100  			Parser: p,
   101  		}
   102  	}
   103  	if p.Schemata == nil {
   104  		p.Schemata = make(map[TypeIdent]apiext.JSONSchemaProps)
   105  	}
   106  	if p.Types == nil {
   107  		p.Types = make(map[TypeIdent]*markers.TypeInfo)
   108  	}
   109  	if p.PackageOverrides == nil {
   110  		p.PackageOverrides = make(map[string]PackageOverride)
   111  	}
   112  	if p.GroupVersions == nil {
   113  		p.GroupVersions = make(map[*loader.Package]schema.GroupVersion)
   114  	}
   115  	if p.CustomResourceDefinitions == nil {
   116  		p.CustomResourceDefinitions = make(map[schema.GroupKind]apiext.CustomResourceDefinition)
   117  	}
   118  	if p.FlattenedSchemata == nil {
   119  		p.FlattenedSchemata = make(map[TypeIdent]apiext.JSONSchemaProps)
   120  	}
   121  }
   122  
   123  // indexTypes loads all types in the package into Types.
   124  func (p *Parser) indexTypes(pkg *loader.Package) {
   125  	// autodetect
   126  	pkgMarkers, err := markers.PackageMarkers(p.Collector, pkg)
   127  	if err != nil {
   128  		pkg.AddError(err)
   129  	} else {
   130  		if skipPkg := pkgMarkers.Get("kubebuilder:skip"); skipPkg != nil {
   131  			return
   132  		}
   133  		if nameVal := pkgMarkers.Get("groupName"); nameVal != nil {
   134  			versionVal := pkg.Name // a reasonable guess
   135  			if versionMarker := pkgMarkers.Get("versionName"); versionMarker != nil {
   136  				versionVal = versionMarker.(string)
   137  			}
   138  
   139  			p.GroupVersions[pkg] = schema.GroupVersion{
   140  				Version: versionVal,
   141  				Group:   nameVal.(string),
   142  			}
   143  		}
   144  	}
   145  
   146  	if err := markers.EachType(p.Collector, pkg, func(info *markers.TypeInfo) {
   147  		ident := TypeIdent{
   148  			Package: pkg,
   149  			Name:    info.Name,
   150  		}
   151  
   152  		p.Types[ident] = info
   153  	}); err != nil {
   154  		pkg.AddError(err)
   155  	}
   156  }
   157  
   158  // LookupType fetches type info from Types.
   159  func (p *Parser) LookupType(pkg *loader.Package, name string) *markers.TypeInfo {
   160  	return p.Types[TypeIdent{Package: pkg, Name: name}]
   161  }
   162  
   163  // NeedSchemaFor indicates that a schema should be generated for the given type.
   164  func (p *Parser) NeedSchemaFor(typ TypeIdent) {
   165  	p.init()
   166  
   167  	p.NeedPackage(typ.Package)
   168  	if _, knownSchema := p.Schemata[typ]; knownSchema {
   169  		return
   170  	}
   171  
   172  	info, knownInfo := p.Types[typ]
   173  	if !knownInfo {
   174  		typ.Package.AddError(fmt.Errorf("unknown type %s", typ))
   175  		return
   176  	}
   177  
   178  	// avoid tripping recursive schemata, like ManagedFields, by adding an empty WIP schema
   179  	p.Schemata[typ] = apiext.JSONSchemaProps{}
   180  
   181  	schemaCtx := newSchemaContext(typ.Package, p, p.AllowDangerousTypes)
   182  	ctxForInfo := schemaCtx.ForInfo(info)
   183  
   184  	pkgMarkers, err := markers.PackageMarkers(p.Collector, typ.Package)
   185  	if err != nil {
   186  		typ.Package.AddError(err)
   187  	}
   188  	ctxForInfo.PackageMarkers = pkgMarkers
   189  
   190  	schema := infoToSchema(ctxForInfo)
   191  
   192  	p.Schemata[typ] = *schema
   193  }
   194  
   195  func (p *Parser) NeedFlattenedSchemaFor(typ TypeIdent) {
   196  	p.init()
   197  
   198  	if _, knownSchema := p.FlattenedSchemata[typ]; knownSchema {
   199  		return
   200  	}
   201  
   202  	p.NeedSchemaFor(typ)
   203  	partialFlattened := p.flattener.FlattenType(typ)
   204  	fullyFlattened := FlattenEmbedded(partialFlattened, typ.Package)
   205  
   206  	p.FlattenedSchemata[typ] = *fullyFlattened
   207  }
   208  
   209  // NeedCRDFor lives off in spec.go
   210  
   211  // AddPackage indicates that types and type-checking information is needed
   212  // for the the given package, *ignoring* overrides.
   213  // Generally, consumers should call NeedPackage, while PackageOverrides should
   214  // call AddPackage to continue with the normal loading procedure.
   215  func (p *Parser) AddPackage(pkg *loader.Package) {
   216  	p.init()
   217  	if _, checked := p.packages[pkg]; checked {
   218  		return
   219  	}
   220  	p.indexTypes(pkg)
   221  	p.Checker.Check(pkg)
   222  	p.packages[pkg] = struct{}{}
   223  }
   224  
   225  // NeedPackage indicates that types and type-checking information
   226  // is needed for the given package.
   227  func (p *Parser) NeedPackage(pkg *loader.Package) {
   228  	p.init()
   229  	if _, checked := p.packages[pkg]; checked {
   230  		return
   231  	}
   232  	// overrides are going to be written without vendor.  This is why we index by the actual
   233  	// object when we can.
   234  	if override, overridden := p.PackageOverrides[loader.NonVendorPath(pkg.PkgPath)]; overridden {
   235  		override(p, pkg)
   236  		p.packages[pkg] = struct{}{}
   237  		return
   238  	}
   239  	p.AddPackage(pkg)
   240  }