upgrade controller-tools to v0.4.1

Signed-off-by: yuswift <yuswift2018@gmail.com>
This commit is contained in:
yuswift
2021-04-12 16:31:10 +08:00
parent adef4b5e43
commit 644a08aff3
28 changed files with 1817 additions and 147 deletions

View File

@@ -18,7 +18,9 @@ package crd
import (
"fmt"
"go/ast"
"go/types"
"os"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextlegacy "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
@@ -84,6 +86,9 @@ type Generator struct {
CRDVersions []string `marker:"crdVersions,optional"`
}
func (Generator) CheckFilter() loader.NodeFilter {
return filterTypesForCRDs
}
func (Generator) RegisterMarkers(into *markers.Registry) error {
return crdmarkers.Register(into)
}
@@ -156,6 +161,11 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
}
for i, crd := range versionedCRDs {
// defaults are not allowed to be specified in v1beta1 CRDs, so strip them
// before writing to a file
if crdVersions[i] == "v1beta1" {
removeDefaultsFromSchemas(crd.(*apiextlegacy.CustomResourceDefinition))
}
var fileName string
if i == 0 {
fileName = fmt.Sprintf("%s_%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural)
@@ -171,6 +181,49 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
return nil
}
// removeDefaultsFromSchemas will remove all instances of default values being
// specified across all defined API versions
func removeDefaultsFromSchemas(crd *apiextlegacy.CustomResourceDefinition) {
if crd.Spec.Validation != nil {
removeDefaultsFromSchemaProps(crd.Spec.Validation.OpenAPIV3Schema)
}
for _, versionSpec := range crd.Spec.Versions {
if versionSpec.Schema != nil {
removeDefaultsFromSchemaProps(versionSpec.Schema.OpenAPIV3Schema)
}
}
}
// removeDefaultsFromSchemaProps will recurse into JSONSchemaProps to remove
// all instances of default values being specified
func removeDefaultsFromSchemaProps(v *apiextlegacy.JSONSchemaProps) {
if v == nil {
return
}
if v.Default != nil {
fmt.Fprintln(os.Stderr, "Warning: default unsupported in CRD version v1beta1, v1 required. Removing defaults.")
}
// nil-out the default field
v.Default = nil
for name, prop := range v.Properties {
// iter var reference is fine -- we handle the persistence of the modfications on the line below
//nolint:gosec
removeDefaultsFromSchemaProps(&prop)
v.Properties[name] = prop
}
if v.Items != nil {
removeDefaultsFromSchemaProps(v.Items.Schema)
for i := range v.Items.JSONSchemas {
props := v.Items.JSONSchemas[i]
removeDefaultsFromSchemaProps(&props)
v.Items.JSONSchemas[i] = props
}
}
}
// toTrivialVersions strips out all schemata except for the storage schema,
// and moves that up into the root object. This makes the CRD compatible
// with pre 1.13 clusters.
@@ -277,3 +330,22 @@ func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) map[schema.GroupKi
return kubeKinds
}
// filterTypesForCRDs filters out all nodes that aren't used in CRD generation,
// like interfaces and struct fields without JSON tag.
func filterTypesForCRDs(node ast.Node) bool {
switch node := node.(type) {
case *ast.InterfaceType:
// skip interfaces, we never care about references in them
return false
case *ast.StructType:
return true
case *ast.Field:
_, hasTag := loader.ParseAstTag(node.Tag).Lookup("json")
// fields without JSON tags mean we have custom serialization,
// so only visit fields with tags.
return hasTag
default:
return true
}
}

View File

@@ -24,6 +24,14 @@ import (
// KnownPackages overrides types in some comment packages that have custom validation
// but don't have validation markers on them (since they're from core Kubernetes).
var KnownPackages = map[string]PackageOverride{
"k8s.io/api/core/v1": func(p *Parser, pkg *loader.Package) {
// Explicit defaulting for the corev1.Protocol type in lieu of https://github.com/kubernetes/enhancements/pull/1928
p.Schemata[TypeIdent{Name: "Protocol", Package: pkg}] = apiext.JSONSchemaProps{
Type: "string",
Default: &apiext.JSON{Raw: []byte(`"TCP"`)},
}
p.AddPackage(pkg)
},
"k8s.io/apimachinery/pkg/apis/meta/v1": func(p *Parser, pkg *loader.Package) {
// ObjectMeta is managed by the Kubernetes API server, so no need to

View File

@@ -40,6 +40,8 @@ var ValidationMarkers = mustMakeAllWithPrefix("kubebuilder:validation", markers.
ExclusiveMaximum(false),
ExclusiveMinimum(false),
MultipleOf(0),
MinProperties(0),
MaxProperties(0),
// string markers
@@ -78,12 +80,20 @@ var FieldOnlyMarkers = []*definitionWithHelp{
must(markers.MakeAnyTypeDefinition("kubebuilder:default", markers.DescribesField, Default{})).
WithHelp(Default{}.Help()),
must(markers.MakeDefinition("kubebuilder:pruning:PreserveUnknownFields", markers.DescribesField, XPreserveUnknownFields{})).
WithHelp(XPreserveUnknownFields{}.Help()),
must(markers.MakeDefinition("kubebuilder:validation:EmbeddedResource", markers.DescribesField, XEmbeddedResource{})).
WithHelp(XEmbeddedResource{}.Help()),
}
// ValidationIshMarkers are field-and-type markers that don't fall under the
// :validation: prefix, and/or don't have a name that directly matches their
// type.
var ValidationIshMarkers = []*definitionWithHelp{
must(markers.MakeDefinition("kubebuilder:pruning:PreserveUnknownFields", markers.DescribesField, XPreserveUnknownFields{})).
WithHelp(XPreserveUnknownFields{}.Help()),
must(markers.MakeDefinition("kubebuilder:pruning:PreserveUnknownFields", markers.DescribesType, XPreserveUnknownFields{})).
WithHelp(XPreserveUnknownFields{}.Help()),
}
func init() {
AllDefinitions = append(AllDefinitions, ValidationMarkers...)
@@ -99,6 +109,7 @@ func init() {
}
AllDefinitions = append(AllDefinitions, FieldOnlyMarkers...)
AllDefinitions = append(AllDefinitions, ValidationIshMarkers...)
}
// +controllertools:marker:generateHelp:category="CRD validation"
@@ -106,7 +117,7 @@ func init() {
type Maximum int
// +controllertools:marker:generateHelp:category="CRD validation"
// Minimum specifies the minimum numeric value that this field can have.
// Minimum specifies the minimum numeric value that this field can have. Negative integers are supported.
type Minimum int
// +controllertools:marker:generateHelp:category="CRD validation"
@@ -145,6 +156,14 @@ type MinItems int
// UniqueItems specifies that all items in this list must be unique.
type UniqueItems bool
// +controllertools:marker:generateHelp:category="CRD validation"
// MaxProperties restricts the number of keys in an object
type MaxProperties int
// +controllertools:marker:generateHelp:category="CRD validation"
// MinProperties restricts the number of keys in an object
type MinProperties int
// +controllertools:marker:generateHelp:category="CRD validation"
// Enum specifies that this (scalar) field is restricted to the *exact* values specified here.
type Enum []interface{}
@@ -191,6 +210,10 @@ type Default struct {
// if nested properties or additionalProperties are specified in the schema.
// This can either be true or undefined. False
// is forbidden.
//
// NB: The kubebuilder:validation:XPreserveUnknownFields variant is deprecated
// in favor of the kubebuilder:pruning:PreserveUnknownFields variant. They function
// identically.
type XPreserveUnknownFields struct{}
// +controllertools:marker:generateHelp:category="CRD validation"
@@ -289,6 +312,24 @@ func (m UniqueItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
return nil
}
func (m MinProperties) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "object" {
return fmt.Errorf("must apply minproperties to an object")
}
val := int64(m)
schema.MinProperties = &val
return nil
}
func (m MaxProperties) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "object" {
return fmt.Errorf("must apply maxproperties to an object")
}
val := int64(m)
schema.MaxProperties = &val
return nil
}
func (m Enum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
// TODO(directxman12): this is a bit hacky -- we should
// probably support AnyType better + using the schema structure

View File

@@ -139,6 +139,17 @@ func (MaxLength) Help() *markers.DefinitionHelp {
}
}
func (MaxProperties) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "restricts the number of keys in an object",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (Maximum) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
@@ -172,11 +183,22 @@ func (MinLength) Help() *markers.DefinitionHelp {
}
}
func (MinProperties) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "restricts the number of keys in an object",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (Minimum) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the minimum numeric value that this field can have.",
Summary: "specifies the minimum numeric value that this field can have. Negative integers are supported.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
@@ -401,7 +423,7 @@ func (XPreserveUnknownFields) Help() *markers.DefinitionHelp {
Category: "CRD processing",
DetailedHelp: markers.DetailedHelp{
Summary: "PreserveUnknownFields stops the apiserver from pruning fields which are not specified. ",
Details: "By default the apiserver drops unknown fields from the request payload during the decoding step. This marker stops the API server from doing so. It affects fields recursively, but switches back to normal pruning behaviour if nested properties or additionalProperties are specified in the schema. This can either be true or undefined. False is forbidden.",
Details: "By default the apiserver drops unknown fields from the request payload during the decoding step. This marker stops the API server from doing so. It affects fields recursively, but switches back to normal pruning behaviour if nested properties or additionalProperties are specified in the schema. This can either be true or undefined. False is forbidden. \n NB: The kubebuilder:validation:XPreserveUnknownFields variant is deprecated in favor of the kubebuilder:pruning:PreserveUnknownFields variant. They function identically.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}

View File

@@ -18,7 +18,6 @@ package crd
import (
"fmt"
"go/ast"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -216,7 +215,7 @@ func (p *Parser) AddPackage(pkg *loader.Package) {
return
}
p.indexTypes(pkg)
p.Checker.Check(pkg, filterTypesForCRDs)
p.Checker.Check(pkg)
p.packages[pkg] = struct{}{}
}
@@ -236,22 +235,3 @@ func (p *Parser) NeedPackage(pkg *loader.Package) {
}
p.AddPackage(pkg)
}
// filterTypesForCRDs filters out all nodes that aren't used in CRD generation,
// like interfaces and struct fields without JSON tag.
func filterTypesForCRDs(node ast.Node) bool {
switch node := node.(type) {
case *ast.InterfaceType:
// skip interfaces, we never care about references in them
return false
case *ast.StructType:
return true
case *ast.Field:
_, hasTag := loader.ParseAstTag(node.Tag).Lookup("json")
// fields without JSON tags mean we have custom serialization,
// so only visit fields with tags.
return hasTag
default:
return true
}
}

View File

@@ -19,7 +19,6 @@ package crd
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
@@ -109,11 +108,6 @@ func (c *schemaContext) requestSchema(pkgPath, typeName string) {
// infoToSchema creates a schema for the type in the given set of type information.
func infoToSchema(ctx *schemaContext) *apiext.JSONSchemaProps {
if obj := ctx.pkg.Types.Scope().Lookup(ctx.info.Name); obj != nil && implementsJSONMarshaler(obj.Type()) {
schema := &apiext.JSONSchemaProps{Type: "Any"}
applyMarkers(ctx, ctx.info.Markers, schema, ctx.info.RawSpec.Type)
return schema
}
return typeToSchema(ctx, ctx.info.RawSpec.Type)
}
@@ -431,16 +425,3 @@ func builtinToType(basic *types.Basic, allowDangerousTypes bool) (typ string, fo
return typ, format, nil
}
// Open coded go/types representation of encoding/json.Marshaller
var jsonMarshaler = types.NewInterfaceType([]*types.Func{
types.NewFunc(token.NoPos, nil, "MarshalJSON",
types.NewSignature(nil, nil,
types.NewTuple(
types.NewVar(token.NoPos, nil, "", types.NewSlice(types.Universe.Lookup("byte").Type())),
types.NewVar(token.NoPos, nil, "", types.Universe.Lookup("error").Type())), false)),
}, nil).Complete()
func implementsJSONMarshaler(typ types.Type) bool {
return types.Implements(typ, jsonMarshaler) || types.Implements(types.NewPointer(typ), jsonMarshaler)
}

View File

@@ -27,6 +27,10 @@ type SchemaVisitor interface {
// this visitor will be called again with `nil` to indicate that
// all children have been visited. If a nil visitor is returned,
// children are not visited.
//
// It is *NOT* safe to save references to the given schema.
// Make deepcopies if you need to keep things around beyond
// the lifetime of the call.
Visit(schema *apiext.JSONSchemaProps) SchemaVisitor
}
@@ -102,6 +106,8 @@ func (w schemaWalker) walkSchema(schema *apiext.JSONSchemaProps) {
// walkMap walks over values of the given map, saving changes to them.
func (w schemaWalker) walkMap(defs map[string]apiext.JSONSchemaProps) {
for name, def := range defs {
// this is iter var reference is because we immediately preseve it below
//nolint:gosec
w.walkSchema(&def)
// make sure the edits actually go through since we can't
// take a reference to the value in the map

View File

@@ -58,6 +58,14 @@ type Generator struct {
Year string `marker:",optional"`
}
func (Generator) CheckFilter() loader.NodeFilter {
return func(node ast.Node) bool {
// ignore interfaces
_, isIface := node.(*ast.InterfaceType)
return !isIface
}
}
func (Generator) RegisterMarkers(into *markers.Registry) error {
if err := markers.RegisterAll(into,
enablePkgMarker, legacyEnablePkgMarker, enableTypeMarker,
@@ -144,7 +152,7 @@ func (d Generator) Generate(ctx *genall.GenerationContext) error {
}
for _, root := range ctx.Roots {
outContents := objGenCtx.GenerateForPackage(root)
outContents := objGenCtx.generateForPackage(root)
if outContents == nil {
continue
}
@@ -186,21 +194,17 @@ import (
}
// GenerateForPackage generates DeepCopy and runtime.Object implementations for
// generateForPackage generates DeepCopy and runtime.Object implementations for
// types in the given package, writing the formatted result to given writer.
// May return nil if source could not be generated.
func (ctx *ObjectGenCtx) GenerateForPackage(root *loader.Package) []byte {
func (ctx *ObjectGenCtx) generateForPackage(root *loader.Package) []byte {
allTypes, err := enabledOnPackage(ctx.Collector, root)
if err != nil {
root.AddError(err)
return nil
}
ctx.Checker.Check(root, func(node ast.Node) bool {
// ignore interfaces
_, isIface := node.(*ast.InterfaceType)
return !isIface
})
ctx.Checker.Check(root)
root.NeedTypesInfo()

View File

@@ -197,7 +197,7 @@ func (n *namingInfo) Syntax(basePkg *loader.Package, imports *importsList) strin
(&namingInfo{typeInfo: typeInfo.Key()}).Syntax(basePkg, imports),
(&namingInfo{typeInfo: typeInfo.Elem()}).Syntax(basePkg, imports))
default:
basePkg.AddError(fmt.Errorf("name requested for invalid type %s", typeInfo))
basePkg.AddError(fmt.Errorf("name requested for invalid type: %s", typeInfo))
return typeInfo.String()
}
}
@@ -215,7 +215,7 @@ type copyMethodMaker struct {
func (c *copyMethodMaker) GenerateMethodsFor(root *loader.Package, info *markers.TypeInfo) {
typeInfo := root.TypesInfo.TypeOf(info.RawSpec.Name)
if typeInfo == types.Typ[types.Invalid] {
root.AddError(loader.ErrFromNode(fmt.Errorf("unknown type %s", info.Name), info.RawSpec))
root.AddError(loader.ErrFromNode(fmt.Errorf("unknown type: %s", info.Name), info.RawSpec))
}
// figure out if we need to use a pointer receiver -- most types get a pointer receiver,
@@ -293,13 +293,18 @@ func (c *copyMethodMaker) genDeepCopyIntoBlock(actualName *namingInfo, typeInfo
switch last := last.(type) {
case *types.Basic:
// basic types themselves can be "shallow" copied, so all we need
// to do is check if our *actual* type (not the underlying one) has
// a custom method implemented.
if hasMethod, _ := hasDeepCopyMethod(c.pkg, typeInfo); hasMethod {
c.Line("*out = in.DeepCopy()")
switch last.Kind() {
case types.Invalid, types.UnsafePointer:
c.pkg.AddError(fmt.Errorf("invalid type: %s", last))
default:
// basic types themselves can be "shallow" copied, so all we need
// to do is check if our *actual* type (not the underlying one) has
// a custom method implemented.
if hasMethod, _ := hasDeepCopyMethod(c.pkg, typeInfo); hasMethod {
c.Line("*out = in.DeepCopy()")
}
c.Line("*out = *in")
}
c.Line("*out = *in")
case *types.Map:
c.genMapDeepCopy(actualName, last)
case *types.Slice:
@@ -312,7 +317,7 @@ func (c *copyMethodMaker) genDeepCopyIntoBlock(actualName *namingInfo, typeInfo
// handled via the above loop, should never happen
c.pkg.AddError(fmt.Errorf("interface type %s encountered directly, invalid condition", last))
default:
c.pkg.AddError(fmt.Errorf("invalid type %s", last))
c.pkg.AddError(fmt.Errorf("invalid type: %s", last))
}
}
@@ -322,7 +327,7 @@ func (c *copyMethodMaker) genMapDeepCopy(actualName *namingInfo, mapType *types.
// maps *must* have shallow-copiable types, since we just iterate
// through the keys, only trying to deepcopy the values.
if !fineToShallowCopy(mapType.Key()) {
c.pkg.AddError(fmt.Errorf("invalid map key type %s", mapType.Key()))
c.pkg.AddError(fmt.Errorf("invalid map key type: %s", mapType.Key()))
return
}
@@ -383,7 +388,7 @@ func (c *copyMethodMaker) genMapDeepCopy(actualName *namingInfo, mapType *types.
// structs will have deepcopy generated for them, so use that
c.Line("(*out)[key] = *val.DeepCopy()")
default:
c.pkg.AddError(fmt.Errorf("invalid map value type %s", underlyingElem))
c.pkg.AddError(fmt.Errorf("invalid map value type: %s", underlyingElem))
return
}
}
@@ -425,7 +430,7 @@ func (c *copyMethodMaker) genSliceDeepCopy(actualName *namingInfo, sliceType *ty
// structs will always have deepcopy
c.Linef("(*in)[i].DeepCopyInto(&(*out)[i])")
default:
c.pkg.AddError(fmt.Errorf("invalid slice element type %s", underlyingElem))
c.pkg.AddError(fmt.Errorf("invalid slice element type: %s", underlyingElem))
}
})
}
@@ -483,7 +488,13 @@ func (c *copyMethodMaker) genStructDeepCopy(_ *namingInfo, structType *types.Str
// otherwise...
switch underlyingField := underlyingField.(type) {
case *types.Basic:
// nothing to do, initial assignment copied this
switch underlyingField.Kind() {
case types.Invalid, types.UnsafePointer:
c.pkg.AddError(loader.ErrFromNode(fmt.Errorf("invalid field type: %s", underlyingField), field))
return
default:
// nothing to do, initial assignment copied this
}
case *types.Struct:
if fineToShallowCopy(field.Type()) {
c.Linef("out.%[1]s = in.%[1]s", field.Name())
@@ -491,7 +502,7 @@ func (c *copyMethodMaker) genStructDeepCopy(_ *namingInfo, structType *types.Str
c.Linef("in.%[1]s.DeepCopyInto(&out.%[1]s)", field.Name())
}
default:
c.pkg.AddError(fmt.Errorf("invalid field type %s", underlyingField))
c.pkg.AddError(loader.ErrFromNode(fmt.Errorf("invalid field type: %s", underlyingField), field))
return
}
}
@@ -542,7 +553,7 @@ func (c *copyMethodMaker) genPointerDeepCopy(_ *namingInfo, pointerType *types.P
c.Linef("*out = new(%[1]s)", (&namingInfo{typeInfo: pointerType.Elem()}).Syntax(c.pkg, c.importsList))
c.Line("(*in).DeepCopyInto(*out)")
default:
c.pkg.AddError(fmt.Errorf("invalid pointer element type %s", underlyingElem))
c.pkg.AddError(fmt.Errorf("invalid pointer element type: %s", underlyingElem))
return
}
}
@@ -602,7 +613,7 @@ func shouldBeCopied(pkg *loader.Package, info *markers.TypeInfo) bool {
typeInfo := pkg.TypesInfo.TypeOf(info.RawSpec.Name)
if typeInfo == types.Typ[types.Invalid] {
pkg.AddError(loader.ErrFromNode(fmt.Errorf("unknown type %s", info.Name), info.RawSpec))
pkg.AddError(loader.ErrFromNode(fmt.Errorf("unknown type: %s", info.Name), info.RawSpec))
return false
}
@@ -735,8 +746,14 @@ func eventualUnderlyingType(typeInfo types.Type) types.Type {
func fineToShallowCopy(typeInfo types.Type) bool {
switch typeInfo := typeInfo.(type) {
case *types.Basic:
// basic types (int, string, etc) are always fine to shallow-copy
return true
// basic types (int, string, etc) are always fine to shallow-copy,
// except for Invalid and UnsafePointer, which can't be copied at all.
switch typeInfo.Kind() {
case types.Invalid, types.UnsafePointer:
return false
default:
return true
}
case *types.Named:
// aliases are fine to shallow-copy as long as they resolve to a shallow-copyable type
return fineToShallowCopy(typeInfo.Underlying())

View File

@@ -46,6 +46,32 @@ func (g Generators) RegisterMarkers(reg *markers.Registry) error {
return nil
}
// CheckFilters returns the set of NodeFilters for all Generators that
// implement NeedsTypeChecking.
func (g Generators) CheckFilters() []loader.NodeFilter {
var filters []loader.NodeFilter
for _, gen := range g {
withFilter, needsChecking := (*gen).(NeedsTypeChecking)
if !needsChecking {
continue
}
filters = append(filters, withFilter.CheckFilter())
}
return filters
}
// NeedsTypeChecking indicates that a particular generator needs & has opinions
// on typechecking. If this is not implemented, a generator will be given a
// context with a nil typechecker.
type NeedsTypeChecking interface {
// CheckFilter indicates the loader.NodeFilter (if any) that should be used
// to prune out unused types/packages when type-checking (nodes for which
// the filter returns true are considered "interesting"). This filter acts
// as a baseline -- all types the pass through this filter will be checked,
// but more than that may also be checked due to other generators' filters.
CheckFilter() loader.NodeFilter
}
// Generator knows how to register some set of markers, and then produce
// output artifacts based on loaded code containing those markers,
// sharing common loaded data.
@@ -144,7 +170,9 @@ func (g Generators) ForRoots(rootPaths ...string) (*Runtime, error) {
},
Roots: roots,
InputRule: InputFromFileSystem,
Checker: &loader.TypeChecker{},
Checker: &loader.TypeChecker{
NodeFilters: g.CheckFilters(),
},
},
OutputRules: OutputRules{Default: OutputToNothing},
}
@@ -165,14 +193,23 @@ func (r *Runtime) Run() bool {
return true
}
hadErrs := false
for _, gen := range r.Generators {
ctx := r.GenerationContext // make a shallow copy
ctx.OutputRule = r.OutputRules.ForGenerator(gen)
// don't pass a typechecker to generators that don't provide a filter
// to avoid accidents
if _, needsChecking := (*gen).(NeedsTypeChecking); !needsChecking {
ctx.Checker = nil
}
if err := (*gen).Generate(&ctx); err != nil {
fmt.Fprintln(os.Stderr, err)
hadErrs = true
}
}
// skip TypeErrors -- they're probably just from partial typechecking in crd-gen
return loader.PrintErrors(r.Roots, packages.TypeError)
return loader.PrintErrors(r.Roots, packages.TypeError) || hadErrs
}

View File

@@ -18,7 +18,6 @@ package loader
import (
"fmt"
"go/ast"
"go/token"
)
@@ -29,10 +28,15 @@ type PositionedError struct {
error
}
// Node is the intersection of go/ast.Node and go/types.Var.
type Node interface {
Pos() token.Pos // position of first character belonging to the node
}
// ErrFromNode returns the given error, with additional information
// attaching it to the given AST node. It will automatically map
// over error lists.
func ErrFromNode(err error, node ast.Node) error {
func ErrFromNode(err error, node Node) error {
if asList, isList := err.(ErrList); isList {
resList := make(ErrList, len(asList))
for i, baseErr := range asList {

View File

@@ -185,37 +185,51 @@ func allReferencedPackages(pkg *Package, filterNodes NodeFilter) []*Package {
// checking each package's types' externally-referenced types, and only
// type-checking those packages.
type TypeChecker struct {
// NodeFilters are used to filter the set of references that are followed
// when typechecking. If any of the filters returns true for a given node,
// its package will be added to the set of packages to check.
//
// If no filters are specified, all references are followed (this may be slow).
//
// Modifying this after the first call to check may yield strange/invalid
// results.
NodeFilters []NodeFilter
checkedPackages map[*Package]struct{}
filterNodes NodeFilter
sync.Mutex
}
// Check type-checks the given package and all packages referenced
// by types that pass through (have true returned by) filterNodes.
func (c *TypeChecker) Check(root *Package, filterNodes NodeFilter) {
// Check type-checks the given package and all packages referenced by types
// that pass through (have true returned by) any of the NodeFilters.
func (c *TypeChecker) Check(root *Package) {
c.init()
if filterNodes == nil {
filterNodes = c.filterNodes
}
// use a sub-checker with the appropriate settings
(&TypeChecker{
filterNodes: filterNodes,
NodeFilters: c.NodeFilters,
checkedPackages: c.checkedPackages,
}).check(root)
}
func (c *TypeChecker) isNodeInteresting(node ast.Node) bool {
// no filters --> everything is important
if len(c.NodeFilters) == 0 {
return true
}
// otherwise, passing through any one filter means this node is important
for _, filter := range c.NodeFilters {
if filter(node) {
return true
}
}
return false
}
func (c *TypeChecker) init() {
if c.checkedPackages == nil {
c.checkedPackages = make(map[*Package]struct{})
}
if c.filterNodes == nil {
// check every type by default
c.filterNodes = func(_ ast.Node) bool {
return true
}
}
}
// check recursively type-checks the given package, only loading packages that
@@ -232,7 +246,7 @@ func (c *TypeChecker) check(root *Package) {
return
}
refedPackages := allReferencedPackages(root, c.filterNodes)
refedPackages := allReferencedPackages(root, c.isNodeInteresting)
// first, resolve imports for all leaf packages...
var wg sync.WaitGroup

View File

@@ -340,8 +340,13 @@ func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
}
}
// then, integers...
if !probablyString {
if nextTok := subScanner.Scan(); nextTok == sc.Int {
nextTok := subScanner.Scan()
if nextTok == '-' {
nextTok = subScanner.Scan()
}
if nextTok == sc.Int {
return &Argument{Type: IntType}
}
}
@@ -481,11 +486,21 @@ func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inS
for tok := scanner.Scan(); tok != sc.EOF; tok = scanner.Scan() {
}
case IntType:
nextChar := scanner.Peek()
isNegative := false
if nextChar == '-' {
isNegative = true
scanner.Scan() // eat the '-'
}
if !expect(scanner, sc.Int, "integer") {
return
}
// TODO(directxman12): respect the size when parsing
val, err := strconv.Atoi(scanner.TokenText())
text := scanner.TokenText()
if isNegative {
text = "-" + text
}
val, err := strconv.Atoi(text)
if err != nil {
scanner.Error(scanner, fmt.Sprintf("unable to parse integer: %v", err))
return

View File

@@ -32,6 +32,7 @@ import (
crdgen "sigs.k8s.io/controller-tools/pkg/crd"
crdmarkers "sigs.k8s.io/controller-tools/pkg/crd/markers"
"sigs.k8s.io/controller-tools/pkg/genall"
"sigs.k8s.io/controller-tools/pkg/loader"
"sigs.k8s.io/controller-tools/pkg/markers"
yamlop "sigs.k8s.io/controller-tools/pkg/schemapatcher/internal/yaml"
)
@@ -87,6 +88,10 @@ type Generator struct {
var _ genall.Generator = &Generator{}
func (Generator) CheckFilter() loader.NodeFilter {
return crdgen.Generator{}.CheckFilter()
}
func (Generator) RegisterMarkers(into *markers.Registry) error {
return crdmarkers.Register(into)
}

View File

@@ -19,7 +19,7 @@ limitations under the License.
//
// The markers take the form:
//
// +kubebuilder:webhook:webhookVersions=<[]string>,failurePolicy=<string>,matchPolicy=<string>,groups=<[]string>,resources=<[]string>,verbs=<[]string>,versions=<[]string>,name=<string>,path=<string>,mutating=<bool>,sideEffects=<string>
// +kubebuilder:webhook:webhookVersions=<[]string>,failurePolicy=<string>,matchPolicy=<string>,groups=<[]string>,resources=<[]string>,verbs=<[]string>,versions=<[]string>,name=<string>,path=<string>,mutating=<bool>,sideEffects=<string>,admissionReviewVersions=<[]string>
package webhook
import (
@@ -107,6 +107,12 @@ type Config struct {
// WebhookVersions specifies the target API versions of the {Mutating,Validating}WebhookConfiguration objects
// itself to generate. Defaults to v1.
WebhookVersions []string `marker:"webhookVersions,optional"`
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects.
// For generating v1 {Mutating,Validating}WebhookConfiguration, this is mandatory.
// For generating v1beta1 {Mutating,Validating}WebhookConfiguration, this is optional, and default to v1beta1.
AdmissionReviewVersions []string `marker:"admissionReviewVersions,optional"`
}
// verbToAPIVariant converts a marker's verb to the proper value for the API.
@@ -140,15 +146,13 @@ func (c Config) ToMutatingWebhook() (admissionregv1.MutatingWebhook, error) {
}
return admissionregv1.MutatingWebhook{
Name: c.Name,
Rules: c.rules(),
FailurePolicy: c.failurePolicy(),
MatchPolicy: matchPolicy,
ClientConfig: c.clientConfig(),
SideEffects: c.sideEffects(),
// TODO(jiachengxu): AdmissionReviewVersions becomes required in admissionregistration/v1, here we default it
// to `v1` and `v1beta1`, and we should support to config the `AdmissionReviewVersions` as a marker.
AdmissionReviewVersions: []string{defaultWebhookVersion, "v1beta1"},
Name: c.Name,
Rules: c.rules(),
FailurePolicy: c.failurePolicy(),
MatchPolicy: matchPolicy,
ClientConfig: c.clientConfig(),
SideEffects: c.sideEffects(),
AdmissionReviewVersions: c.AdmissionReviewVersions,
}, nil
}
@@ -164,15 +168,13 @@ func (c Config) ToValidatingWebhook() (admissionregv1.ValidatingWebhook, error)
}
return admissionregv1.ValidatingWebhook{
Name: c.Name,
Rules: c.rules(),
FailurePolicy: c.failurePolicy(),
MatchPolicy: matchPolicy,
ClientConfig: c.clientConfig(),
SideEffects: c.sideEffects(),
// TODO(jiachengxu): AdmissionReviewVersions becomes required in admissionregistration/v1, here we default it
// to `v1` and `v1beta1`, and we should support to config the `AdmissionReviewVersions` as a marker.
AdmissionReviewVersions: []string{defaultWebhookVersion, "v1beta1"},
Name: c.Name,
Rules: c.rules(),
FailurePolicy: c.failurePolicy(),
MatchPolicy: matchPolicy,
ClientConfig: c.clientConfig(),
SideEffects: c.sideEffects(),
AdmissionReviewVersions: c.AdmissionReviewVersions,
}, nil
}
@@ -242,10 +244,6 @@ func (c Config) clientConfig() admissionregv1.WebhookClientConfig {
Namespace: "system",
Path: &path,
},
// OpenAPI marks the field as required before 1.13 because of a bug that got fixed in
// https://github.com/kubernetes/api/commit/e7d9121e9ffd63cea0288b36a82bcc87b073bd1b
// Put "\n" as an placeholder as a workaround til 1.13+ is almost everywhere.
CABundle: []byte("\n"),
}
}
@@ -344,29 +342,26 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
},
Webhooks: cfgs,
}
// SideEffects in required in admissionregistration/v1, if this is not set or set to `Some` or `Known`,
// we return an error
if version == defaultWebhookVersion {
for i := range objRaw.Webhooks {
// SideEffects is required in admissionregistration/v1, if this is not set or set to `Some` or `Known`,
// we return an error
if err := checkSideEffectsForV1(objRaw.Webhooks[i].SideEffects); err != nil {
return err
}
// AdmissionReviewVersions is required in admissionregistration/v1, if this is not set,
// we return an error
if len(objRaw.Webhooks[i].AdmissionReviewVersions) == 0 {
return fmt.Errorf("AdmissionReviewVersions is mandatory for v1 {Mutating,Validating}WebhookConfiguration")
}
}
}
// AdmissionReviewVersions is optional in admissionregistration/v1beta1, so let kubernetes to default it.
if version == "v1beta1" {
for i := range objRaw.Webhooks {
objRaw.Webhooks[i].AdmissionReviewVersions = nil
}
}
if version != defaultWebhookVersion {
versionedWebhooks[version] = append(versionedWebhooks[version], objRaw)
} else {
conv, err := MutatingWebhookConfigurationAsVersion(objRaw, schema.GroupVersion{Group: admissionregv1.SchemeGroupVersion.Group, Version: version})
versionedWebhooks[version] = append(versionedWebhooks[version], conv)
if err != nil {
return err
}
} else {
versionedWebhooks[version] = append(versionedWebhooks[version], objRaw)
versionedWebhooks[version] = append(versionedWebhooks[version], conv)
}
}
@@ -381,29 +376,26 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
},
Webhooks: cfgs,
}
// SideEffects in required in admissionregistration/v1, if this is not set or set to `Some` or `Known`,
// we return an error
if version == defaultWebhookVersion {
for i := range objRaw.Webhooks {
// SideEffects is required in admissionregistration/v1, if this is not set or set to `Some` or `Known`,
// we return an error
if err := checkSideEffectsForV1(objRaw.Webhooks[i].SideEffects); err != nil {
return err
}
// AdmissionReviewVersions is required in admissionregistration/v1, if this is not set,
// we return an error
if len(objRaw.Webhooks[i].AdmissionReviewVersions) == 0 {
return fmt.Errorf("AdmissionReviewVersions is mandatory for v1 {Mutating,Validating}WebhookConfiguration")
}
}
}
// AdmissionReviewVersions is optional in admissionregistration/v1beta1, so let kubernetes to default it.
if version == "v1beta1" {
for i := range objRaw.Webhooks {
objRaw.Webhooks[i].AdmissionReviewVersions = nil
}
}
if version != defaultWebhookVersion {
versionedWebhooks[version] = append(versionedWebhooks[version], objRaw)
} else {
conv, err := ValidatingWebhookConfigurationAsVersion(objRaw, schema.GroupVersion{Group: admissionregv1.SchemeGroupVersion.Group, Version: version})
versionedWebhooks[version] = append(versionedWebhooks[version], conv)
if err != nil {
return err
}
} else {
versionedWebhooks[version] = append(versionedWebhooks[version], objRaw)
versionedWebhooks[version] = append(versionedWebhooks[version], conv)
}
}
}

View File

@@ -76,6 +76,10 @@ func (Config) Help() *markers.DefinitionHelp {
Summary: "specifies the target API versions of the {Mutating,Validating}WebhookConfiguration objects itself to generate. Defaults to v1.",
Details: "",
},
"AdmissionReviewVersions": markers.DetailedHelp{
Summary: "is an ordered list of preferred `AdmissionReview` versions the Webhook expects. For generating v1 {Mutating,Validating}WebhookConfiguration, this is mandatory. For generating v1beta1 {Mutating,Validating}WebhookConfiguration, this is optional, and default to v1beta1.",
Details: "",
},
},
}
}