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