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