Bump sigs.k8s.io/controller-tools from 0.6.2 to 0.11.1 (#5432)

This commit is contained in:
hongming
2022-12-28 10:12:00 +08:00
committed by GitHub
parent 1e2a8c1699
commit 06978f123d
52 changed files with 1335 additions and 964 deletions

View File

@@ -6,7 +6,6 @@ import (
apiextinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
@@ -22,13 +21,15 @@ func init() {
if err := apiext.AddToScheme(conversionScheme); err != nil {
panic("must be able to add apiextensions/v1 to the CRD conversion Scheme")
}
if err := apiextv1beta1.AddToScheme(conversionScheme); err != nil {
panic("must be able to add apiextensions/v1beta1 to the CRD conversion Scheme")
}
}
// AsVersion converts a CRD from the canonical internal form (currently v1) to some external form.
func AsVersion(original apiext.CustomResourceDefinition, gv schema.GroupVersion) (runtime.Object, error) {
// TODO: Do we need to keep maintaining this conversion function
// post 1.22 when only CRDv1 is served by the apiserver?
if gv == apiextv1beta1.SchemeGroupVersion {
return nil, fmt.Errorf("apiVersion %q is not supported", gv.String())
}
// We can use the internal versions an existing conversions from kubernetes, since they're not in k/k itself.
// This punts the problem of conversion down the road for a future maintainer (or future instance of @directxman12)
// when we have to support older versions that get removed, or when API machinery decides to yell at us for this
@@ -40,83 +41,3 @@ func AsVersion(original apiext.CustomResourceDefinition, gv schema.GroupVersion)
return conversionScheme.ConvertToVersion(intVer, gv)
}
// mergeIdenticalSubresources checks to see if subresources are identical across
// all versions, and if so, merges them into a top-level version.
//
// This assumes you're not using trivial versions.
func mergeIdenticalSubresources(crd *apiextv1beta1.CustomResourceDefinition) {
subres := crd.Spec.Versions[0].Subresources
for _, ver := range crd.Spec.Versions {
if ver.Subresources == nil || !equality.Semantic.DeepEqual(subres, ver.Subresources) {
// either all nil, or not identical
return
}
}
// things are identical if we've gotten this far, so move the subresources up
// and discard the identical per-version ones
crd.Spec.Subresources = subres
for i := range crd.Spec.Versions {
crd.Spec.Versions[i].Subresources = nil
}
}
// mergeIdenticalSchemata checks to see if schemata are identical across
// all versions, and if so, merges them into a top-level version.
//
// This assumes you're not using trivial versions.
func mergeIdenticalSchemata(crd *apiextv1beta1.CustomResourceDefinition) {
schema := crd.Spec.Versions[0].Schema
for _, ver := range crd.Spec.Versions {
if ver.Schema == nil || !equality.Semantic.DeepEqual(schema, ver.Schema) {
// either all nil, or not identical
return
}
}
// things are identical if we've gotten this far, so move the schemata up
// to a single schema and discard the identical per-version ones
crd.Spec.Validation = schema
for i := range crd.Spec.Versions {
crd.Spec.Versions[i].Schema = nil
}
}
// mergeIdenticalPrinterColumns checks to see if schemata are identical across
// all versions, and if so, merges them into a top-level version.
//
// This assumes you're not using trivial versions.
func mergeIdenticalPrinterColumns(crd *apiextv1beta1.CustomResourceDefinition) {
cols := crd.Spec.Versions[0].AdditionalPrinterColumns
for _, ver := range crd.Spec.Versions {
if len(ver.AdditionalPrinterColumns) == 0 || !equality.Semantic.DeepEqual(cols, ver.AdditionalPrinterColumns) {
// either all nil, or not identical
return
}
}
// things are identical if we've gotten this far, so move the printer columns up
// and discard the identical per-version ones
crd.Spec.AdditionalPrinterColumns = cols
for i := range crd.Spec.Versions {
crd.Spec.Versions[i].AdditionalPrinterColumns = nil
}
}
// MergeIdenticalVersionInfo makes sure that components of the Versions field that are identical
// across all versions get merged into the top-level fields in v1beta1.
//
// This is required by the Kubernetes API server validation.
//
// The reason is that a v1beta1 -> v1 -> v1beta1 conversion cycle would need to
// round-trip identically, v1 doesn't have top-level subresources, and without
// this restriction it would be ambiguous how a v1-with-identical-subresources
// converts into a v1beta1).
func MergeIdenticalVersionInfo(crd *apiextv1beta1.CustomResourceDefinition) {
if len(crd.Spec.Versions) > 0 {
mergeIdenticalSubresources(crd)
mergeIdenticalSchemata(crd)
mergeIdenticalPrinterColumns(crd)
}
}

View File

@@ -17,14 +17,14 @@ limitations under the License.
// Package crd contains utilities for generating CustomResourceDefinitions and
// their corresponding OpenAPI validation schemata.
//
// Markers
// # Markers
//
// Markers live under the markers subpackage. Two types of markers exist:
// those that modify schema generation (for validation), and those that modify
// the rest of the CRD. See the subpackage for more information and all
// supported markers.
//
// Collecting Types and Generating CRDs
// # Collecting Types and Generating CRDs
//
// The Parser is the entrypoint for collecting the information required to
// generate CRDs. Like loader and collector, its methods are idemptotent, not
@@ -40,13 +40,13 @@ limitations under the License.
// Errors are generally attached directly to the relevant Package with
// AddError.
//
// Known Packages
// # Known Packages
//
// There are a few types from Kubernetes that have special meaning, but don't
// have validation markers attached. Those specific types have overrides
// listed in KnownPackages that can be added as overrides to any parser.
//
// Flattening
// # Flattening
//
// Once schemata are generated, they can be used directly by external tooling
// (like JSONSchema validators), but must first be "flattened" to not contain

View File

@@ -143,6 +143,10 @@ func flattenAllOfInto(dst *apiext.JSONSchemaProps, src apiext.JSONSchemaProps, e
dstProps.Schema = &apiext.JSONSchemaProps{}
}
flattenAllOfInto(dstProps.Schema, *srcProps.Schema, errRec)
case "XPreserveUnknownFields":
dstField.Set(srcField)
case "XMapType":
dstField.Set(srcField)
// NB(directxman12): no need to explicitly handle nullable -- false is considered to be the zero value
// TODO(directxman12): src isn't necessarily the field value -- it's just the most recent allOf entry
default:

View File

@@ -20,10 +20,9 @@ import (
"fmt"
"go/ast"
"go/types"
"os"
"sort"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextlegacy "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
crdmarkers "sigs.k8s.io/controller-tools/pkg/crd/markers"
@@ -33,30 +32,20 @@ import (
"sigs.k8s.io/controller-tools/pkg/version"
)
// The identifier for v1 CustomResourceDefinitions.
const v1 = "v1"
// The default CustomResourceDefinition version to generate.
const defaultVersion = "v1"
const defaultVersion = v1
// +controllertools:marker:generateHelp
// Generator generates CustomResourceDefinition objects.
type Generator struct {
// TrivialVersions indicates that we should produce a single-version CRD.
// IgnoreUnexportedFields indicates that we should skip unexported fields.
//
// Single "trivial-version" CRDs are compatible with older (pre 1.13)
// Kubernetes API servers. The storage version's schema will be used as
// the CRD's schema.
//
// Only works with the v1beta1 CRD version.
TrivialVersions bool `marker:",optional"`
// PreserveUnknownFields indicates whether or not we should turn off pruning.
//
// Left unspecified, it'll default to true when only a v1beta1 CRD is
// generated (to preserve compatibility with older versions of this tool),
// or false otherwise.
//
// It's required to be false for v1 CRDs.
PreserveUnknownFields *bool `marker:",optional"`
// Left unspecified, the default is false.
IgnoreUnexportedFields *bool `marker:",optional"`
// AllowDangerousTypes allows types which are usually omitted from CRD generation
// because they are not recommended.
@@ -78,6 +67,8 @@ type Generator struct {
// CRDVersions specifies the target API versions of the CRD type itself to
// generate. Defaults to v1.
//
// Currently, the only supported value is v1.
//
// The first version listed will be assumed to be the "default" version and
// will not get a version suffix in the output filename.
//
@@ -95,12 +86,20 @@ func (Generator) CheckFilter() loader.NodeFilter {
func (Generator) RegisterMarkers(into *markers.Registry) error {
return crdmarkers.Register(into)
}
// transformRemoveCRDStatus ensures we do not write the CRD status field.
func transformRemoveCRDStatus(obj map[string]interface{}) error {
delete(obj, "status")
return nil
}
func (g Generator) Generate(ctx *genall.GenerationContext) error {
parser := &Parser{
Collector: ctx.Collector,
Checker: ctx.Checker,
// Perform defaulting here to avoid ambiguity later
AllowDangerousTypes: g.AllowDangerousTypes != nil && *g.AllowDangerousTypes == true,
IgnoreUnexportedFields: g.IgnoreUnexportedFields != nil && *g.IgnoreUnexportedFields == true,
AllowDangerousTypes: g.AllowDangerousTypes != nil && *g.AllowDangerousTypes == true,
// Indicates the parser on whether to register the ObjectMeta type or not
GenerateEmbeddedObjectMeta: g.GenerateEmbeddedObjectMeta != nil && *g.GenerateEmbeddedObjectMeta == true,
}
@@ -129,7 +128,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
crdVersions = []string{defaultVersion}
}
for groupKind := range kubeKinds {
for _, groupKind := range kubeKinds {
parser.NeedCRDFor(groupKind, g.MaxDescLen)
crdRaw := parser.CustomResourceDefinitions[groupKind]
addAttribution(&crdRaw)
@@ -146,45 +145,15 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
versionedCRDs[i] = conv
}
if g.TrivialVersions {
for i, crd := range versionedCRDs {
if crdVersions[i] == "v1beta1" {
toTrivialVersions(crd.(*apiextlegacy.CustomResourceDefinition))
}
}
}
// *If* we're only generating v1beta1 CRDs, default to `preserveUnknownFields: (unset)`
// for compatibility purposes. In any other case, default to false, since that's
// the sensible default and is required for v1.
v1beta1Only := len(crdVersions) == 1 && crdVersions[0] == "v1beta1"
switch {
case (g.PreserveUnknownFields == nil || *g.PreserveUnknownFields) && v1beta1Only:
crd := versionedCRDs[0].(*apiextlegacy.CustomResourceDefinition)
crd.Spec.PreserveUnknownFields = nil
case g.PreserveUnknownFields == nil, g.PreserveUnknownFields != nil && !*g.PreserveUnknownFields:
// it'll be false here (coming from v1) -- leave it as such
default:
return fmt.Errorf("you may only set PreserveUnknownFields to true with v1beta1 CRDs")
}
for i, crd := range versionedCRDs {
// defaults are not allowed to be specified in v1beta1 CRDs and
// decriptions are not allowed on the metadata regardless of version
// strip them before writing to a file
if crdVersions[i] == "v1beta1" {
removeDefaultsFromSchemas(crd.(*apiextlegacy.CustomResourceDefinition))
removeDescriptionFromMetadataLegacy(crd.(*apiextlegacy.CustomResourceDefinition))
} else {
removeDescriptionFromMetadata(crd.(*apiext.CustomResourceDefinition))
}
removeDescriptionFromMetadata(crd.(*apiext.CustomResourceDefinition))
var fileName string
if i == 0 {
fileName = fmt.Sprintf("%s_%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural)
} else {
fileName = fmt.Sprintf("%s_%s.%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural, crdVersions[i])
}
if err := ctx.WriteYAML(fileName, crd); err != nil {
if err := ctx.WriteYAML(fileName, []interface{}{crd}, genall.WithTransform(transformRemoveCRDStatus)); err != nil {
return err
}
}
@@ -212,71 +181,6 @@ func removeDescriptionFromMetadataProps(v *apiext.JSONSchemaProps) {
}
}
func removeDescriptionFromMetadataLegacy(crd *apiextlegacy.CustomResourceDefinition) {
if crd.Spec.Validation != nil {
removeDescriptionFromMetadataPropsLegacy(crd.Spec.Validation.OpenAPIV3Schema)
}
for _, versionSpec := range crd.Spec.Versions {
if versionSpec.Schema != nil {
removeDescriptionFromMetadataPropsLegacy(versionSpec.Schema.OpenAPIV3Schema)
}
}
}
func removeDescriptionFromMetadataPropsLegacy(v *apiextlegacy.JSONSchemaProps) {
if m, ok := v.Properties["metadata"]; ok {
meta := &m
if meta.Description != "" {
meta.Description = ""
v.Properties["metadata"] = m
}
}
}
// 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
}
}
}
// FixTopLevelMetadata resets the schema for the top-level metadata field which is needed for CRD validation
func FixTopLevelMetadata(crd apiext.CustomResourceDefinition) {
for _, v := range crd.Spec.Versions {
@@ -289,32 +193,6 @@ func FixTopLevelMetadata(crd apiext.CustomResourceDefinition) {
}
}
// 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.
func toTrivialVersions(crd *apiextlegacy.CustomResourceDefinition) {
var canonicalSchema *apiextlegacy.CustomResourceValidation
var canonicalSubresources *apiextlegacy.CustomResourceSubresources
var canonicalColumns []apiextlegacy.CustomResourceColumnDefinition
for i, ver := range crd.Spec.Versions {
if ver.Storage == true {
canonicalSchema = ver.Schema
canonicalSubresources = ver.Subresources
canonicalColumns = ver.AdditionalPrinterColumns
}
crd.Spec.Versions[i].Schema = nil
crd.Spec.Versions[i].Subresources = nil
crd.Spec.Versions[i].AdditionalPrinterColumns = nil
}
if canonicalSchema == nil {
return
}
crd.Spec.Validation = canonicalSchema
crd.Spec.Subresources = canonicalSubresources
crd.Spec.AdditionalPrinterColumns = canonicalColumns
}
// addAttribution adds attribution info to indicate controller-gen tool was used
// to generate this CRD definition along with the version info.
func addAttribution(crd *apiext.CustomResourceDefinition) {
@@ -339,7 +217,7 @@ func FindMetav1(roots []*loader.Package) *loader.Package {
// FindKubeKinds locates all types that contain TypeMeta and ObjectMeta
// (and thus may be a Kubernetes object), and returns the corresponding
// group-kinds.
func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) map[schema.GroupKind]struct{} {
func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) []schema.GroupKind {
// TODO(directxman12): technically, we should be finding metav1 per-package
kubeKinds := map[schema.GroupKind]struct{}{}
for typeIdent, info := range parser.Types {
@@ -370,7 +248,12 @@ func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) map[schema.GroupKi
}
fieldPkgPath := loader.NonVendorPath(namedField.Obj().Pkg().Path())
fieldPkg := pkg.Imports()[fieldPkgPath]
if fieldPkg != metav1Pkg {
// Compare the metav1 package by ID and not by the actual instance
// of the object. The objects in memory could be different due to
// loading from different root paths, even when they both refer to
// the same metav1 package.
if fieldPkg == nil || fieldPkg.ID != metav1Pkg.ID {
continue
}
@@ -393,7 +276,15 @@ func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) map[schema.GroupKi
kubeKinds[groupKind] = struct{}{}
}
return kubeKinds
groupKindList := make([]schema.GroupKind, 0, len(kubeKinds))
for groupKind := range kubeKinds {
groupKindList = append(groupKindList, groupKind)
}
sort.Slice(groupKindList, func(i, j int) bool {
return groupKindList[i].String() < groupKindList[j].String()
})
return groupKindList
}
// filterTypesForCRDs filters out all nodes that aren't used in CRD generation,

View File

@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@@ -74,7 +74,8 @@ var KnownPackages = map[string]PackageOverride{
"k8s.io/apimachinery/pkg/runtime": func(p *Parser, pkg *loader.Package) {
p.Schemata[TypeIdent{Name: "RawExtension", Package: pkg}] = apiext.JSONSchemaProps{
// TODO(directxman12): regexp validation for this (or get kube to support it as a format value)
Type: "object",
Type: "object",
XPreserveUnknownFields: boolPtr(true),
}
p.AddPackage(pkg) // get the rest of the types
},

View File

@@ -18,6 +18,7 @@ package markers
import (
"fmt"
"strings"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -51,6 +52,9 @@ var CRDMarkers = []*definitionWithHelp{
must(markers.MakeDefinition("kubebuilder:deprecatedversion", markers.DescribesType, DeprecatedVersion{})).
WithHelp(DeprecatedVersion{}.Help()),
must(markers.MakeDefinition("kubebuilder:metadata", markers.DescribesType, Metadata{})).
WithHelp(Metadata{}.Help()),
}
// TODO: categories and singular used to be annotations types
@@ -345,3 +349,39 @@ func (s DeprecatedVersion) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec,
}
return nil
}
// +controllertools:marker:generateHelp:category=CRD
// Metadata configures the additional annotations or labels for this CRD.
// For example adding annotation "api-approved.kubernetes.io" for a CRD with Kubernetes groups,
// or annotation "cert-manager.io/inject-ca-from-secret" for a CRD that needs CA injection.
type Metadata struct {
// Annotations will be added into the annotations of this CRD.
Annotations []string `marker:",optional"`
// Labels will be added into the labels of this CRD.
Labels []string `marker:",optional"`
}
func (s Metadata) ApplyToCRD(crd *apiext.CustomResourceDefinition, version string) error {
if len(s.Annotations) > 0 {
if crd.Annotations == nil {
crd.Annotations = map[string]string{}
}
for _, str := range s.Annotations {
kv := strings.SplitN(str, "=", 2)
crd.Annotations[kv[0]] = kv[1]
}
}
if len(s.Labels) > 0 {
if crd.Labels == nil {
crd.Labels = map[string]string{}
}
for _, str := range s.Labels {
kv := strings.SplitN(str, "=", 2)
crd.Labels[kv[0]] = kv[1]
}
}
return nil
}

View File

@@ -19,7 +19,7 @@ limitations under the License.
//
// All markers related to CRD generation live in AllDefinitions.
//
// Validation Markers
// # Validation Markers
//
// Validation markers have values that implement ApplyToSchema
// (crd.SchemaMarker). Any marker implementing this will automatically
@@ -31,7 +31,7 @@ limitations under the License.
// All validation markers start with "+kubebuilder:validation", and
// have the same name as their type name.
//
// CRD Markers
// # CRD Markers
//
// Markers that modify anything in the CRD itself *except* for the schema
// implement ApplyToCRD (crd.CRDMarker). They are expected to detect whether
@@ -39,7 +39,7 @@ limitations under the License.
// them), or to the root-level CRD for legacy cases. They are applied *after*
// the rest of the CRD is computed.
//
// Misc
// # Misc
//
// This package also defines the "+groupName" and "+versionName" package-level
// markers, for defining package<->group-version mappings.

View File

@@ -28,12 +28,20 @@ import (
var TopologyMarkers = []*definitionWithHelp{
must(markers.MakeDefinition("listMapKey", markers.DescribesField, ListMapKey(""))).
WithHelp(ListMapKey("").Help()),
must(markers.MakeDefinition("listMapKey", markers.DescribesType, ListMapKey(""))).
WithHelp(ListMapKey("").Help()),
must(markers.MakeDefinition("listType", markers.DescribesField, ListType(""))).
WithHelp(ListType("").Help()),
must(markers.MakeDefinition("listType", markers.DescribesType, ListType(""))).
WithHelp(ListType("").Help()),
must(markers.MakeDefinition("mapType", markers.DescribesField, MapType(""))).
WithHelp(MapType("").Help()),
must(markers.MakeDefinition("mapType", markers.DescribesType, MapType(""))).
WithHelp(MapType("").Help()),
must(markers.MakeDefinition("structType", markers.DescribesField, StructType(""))).
WithHelp(StructType("").Help()),
must(markers.MakeDefinition("structType", markers.DescribesType, StructType(""))).
WithHelp(StructType("").Help()),
}
func init() {
@@ -47,15 +55,15 @@ func init() {
//
// Possible data-structure types of a list are:
//
// - "map": it needs to have a key field, which will be used to build an
// associative list. A typical example is a the pod container list,
// which is indexed by the container name.
// - "map": it needs to have a key field, which will be used to build an
// associative list. A typical example is a the pod container list,
// which is indexed by the container name.
//
// - "set": Fields need to be "scalar", and there can be only one
// occurrence of each.
// - "set": Fields need to be "scalar", and there can be only one
// occurrence of each.
//
// - "atomic": All the fields in the list are treated as a single value,
// are typically manipulated together by the same actor.
// - "atomic": All the fields in the list are treated as a single value,
// are typically manipulated together by the same actor.
type ListType string
// +controllertools:marker:generateHelp:category="CRD processing"
@@ -75,12 +83,12 @@ type ListMapKey string
//
// Possible values:
//
// - "granular": items in the map are independent of each other,
// and can be manipulated by different actors.
// This is the default behavior.
// - "granular": items in the map are independent of each other,
// and can be manipulated by different actors.
// This is the default behavior.
//
// - "atomic": all fields are treated as one unit.
// Any changes have to replace the entire map.
// - "atomic": all fields are treated as one unit.
// Any changes have to replace the entire map.
type MapType string
// +controllertools:marker:generateHelp:category="CRD processing"
@@ -91,17 +99,17 @@ type MapType string
//
// Possible values:
//
// - "granular": fields in the struct are independent of each other,
// and can be manipulated by different actors.
// This is the default behavior.
// - "granular": fields in the struct are independent of each other,
// and can be manipulated by different actors.
// This is the default behavior.
//
// - "atomic": all fields are treated as one unit.
// Any changes have to replace the entire struct.
// - "atomic": all fields are treated as one unit.
// Any changes have to replace the entire struct.
type StructType string
func (l ListType) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "array" {
return fmt.Errorf("must apply listType to an array")
return fmt.Errorf("must apply listType to an array, found %s", schema.Type)
}
if l != "map" && l != "atomic" && l != "set" {
return fmt.Errorf(`ListType must be either "map", "set" or "atomic"`)
@@ -115,7 +123,7 @@ func (l ListType) ApplyFirst() {}
func (l ListMapKey) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "array" {
return fmt.Errorf("must apply listMapKey to an array")
return fmt.Errorf("must apply listMapKey to an array, found %s", schema.Type)
}
if schema.XListType == nil || *schema.XListType != "map" {
return fmt.Errorf("must apply listMapKey to an associative-list")

View File

@@ -17,9 +17,9 @@ limitations under the License.
package markers
import (
"fmt"
"encoding/json"
"fmt"
"math"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -37,7 +37,7 @@ const (
// reusable and writing complex validations on slice items.
var ValidationMarkers = mustMakeAllWithPrefix("kubebuilder:validation", markers.DescribesField,
// integer markers
// numeric markers
Maximum(0),
Minimum(0),
@@ -66,6 +66,8 @@ var ValidationMarkers = mustMakeAllWithPrefix("kubebuilder:validation", markers.
Type(""),
XPreserveUnknownFields{},
XEmbeddedResource{},
XIntOrString{},
XValidation{},
)
// FieldOnlyMarkers list field-specific validation markers (i.e. those markers that don't make
@@ -121,11 +123,19 @@ func init() {
// +controllertools:marker:generateHelp:category="CRD validation"
// Maximum specifies the maximum numeric value that this field can have.
type Maximum int
type Maximum float64
func (m Maximum) Value() float64 {
return float64(m)
}
// +controllertools:marker:generateHelp:category="CRD validation"
// Minimum specifies the minimum numeric value that this field can have. Negative integers are supported.
type Minimum int
// Minimum specifies the minimum numeric value that this field can have. Negative numbers are supported.
type Minimum float64
func (m Minimum) Value() float64 {
return float64(m)
}
// +controllertools:marker:generateHelp:category="CRD validation"
// ExclusiveMinimum indicates that the minimum is "up to" but not including that value.
@@ -137,7 +147,11 @@ type ExclusiveMaximum bool
// +controllertools:marker:generateHelp:category="CRD validation"
// MultipleOf specifies that this field must have a numeric value that's a multiple of this one.
type MultipleOf int
type MultipleOf float64
func (m MultipleOf) Value() float64 {
return float64(m)
}
// +controllertools:marker:generateHelp:category="CRD validation"
// MaxLength specifies the maximum length for this string.
@@ -232,6 +246,14 @@ type XPreserveUnknownFields struct{}
// field, yet it is possible. This can be combined with PreserveUnknownFields.
type XEmbeddedResource struct{}
// +controllertools:marker:generateHelp:category="CRD validation"
// IntOrString marks a fields as an IntOrString.
//
// This is required when applying patterns or other validations to an IntOrString
// field. Knwon information about the type is applied during the collapse phase
// and as such is not normally available during marker application.
type XIntOrString struct{}
// +controllertools:marker:generateHelp:category="CRD validation"
// Schemaless marks a field as being a schemaless object.
//
@@ -242,41 +264,80 @@ type XEmbeddedResource struct{}
// to be used only as a last resort.
type Schemaless struct{}
func hasNumericType(schema *apiext.JSONSchemaProps) bool {
return schema.Type == "integer" || schema.Type == "number"
}
func isIntegral(value float64) bool {
return value == math.Trunc(value) && !math.IsNaN(value) && !math.IsInf(value, 0)
}
// +controllertools:marker:generateHelp:category="CRD validation"
// XValidation marks a field as requiring a value for which a given
// expression evaluates to true.
//
// This marker may be repeated to specify multiple expressions, all of
// which must evaluate to true.
type XValidation struct {
Rule string
Message string `marker:",optional"`
}
func (m Maximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply maximum to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply maximum to a numeric value, found %s", schema.Type)
}
val := float64(m)
if schema.Type == "integer" && !isIntegral(m.Value()) {
return fmt.Errorf("cannot apply non-integral maximum validation (%v) to integer value", m.Value())
}
val := m.Value()
schema.Maximum = &val
return nil
}
func (m Minimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply minimum to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply minimum to a numeric value, found %s", schema.Type)
}
val := float64(m)
if schema.Type == "integer" && !isIntegral(m.Value()) {
return fmt.Errorf("cannot apply non-integral minimum validation (%v) to integer value", m.Value())
}
val := m.Value()
schema.Minimum = &val
return nil
}
func (m ExclusiveMaximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply exclusivemaximum to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply exclusivemaximum to a numeric value, found %s", schema.Type)
}
schema.ExclusiveMaximum = bool(m)
return nil
}
func (m ExclusiveMinimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply exclusiveminimum to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply exclusiveminimum to a numeric value, found %s", schema.Type)
}
schema.ExclusiveMinimum = bool(m)
return nil
}
func (m MultipleOf) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply multipleof to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply multipleof to a numeric value, found %s", schema.Type)
}
val := float64(m)
if schema.Type == "integer" && !isIntegral(m.Value()) {
return fmt.Errorf("cannot apply non-integral multipleof validation (%v) to integer value", m.Value())
}
val := m.Value()
schema.MultipleOf = &val
return nil
}
@@ -289,6 +350,7 @@ func (m MaxLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.MaxLength = &val
return nil
}
func (m MinLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "string" {
return fmt.Errorf("must apply minlength to a string")
@@ -297,9 +359,13 @@ func (m MinLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.MinLength = &val
return nil
}
func (m Pattern) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "string" {
return fmt.Errorf("must apply pattern to a string")
// Allow string types or IntOrStrings. An IntOrString will still
// apply the pattern validation when a string is detected, the pattern
// will not apply to ints though.
if schema.Type != "string" && !schema.XIntOrString {
return fmt.Errorf("must apply pattern to a `string` or `IntOrString`")
}
schema.Pattern = string(m)
return nil
@@ -313,6 +379,7 @@ func (m MaxItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.MaxItems = &val
return nil
}
func (m MinItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "array" {
return fmt.Errorf("must apply minitems to an array")
@@ -321,6 +388,7 @@ func (m MinItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.MinItems = &val
return nil
}
func (m UniqueItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "array" {
return fmt.Errorf("must apply uniqueitems to an array")
@@ -364,6 +432,7 @@ func (m Enum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.Enum = vals
return nil
}
func (m Format) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.Format = string(m)
return nil
@@ -406,3 +475,21 @@ func (m XEmbeddedResource) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.XEmbeddedResource = true
return nil
}
// NB(JoelSpeed): we use this property in other markers here,
// which means the "XIntOrString" marker *must* be applied first.
func (m XIntOrString) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.XIntOrString = true
return nil
}
func (m XIntOrString) ApplyFirst() {}
func (m XValidation) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.XValidations = append(schema.XValidations, apiext.ValidationRule{
Rule: m.Rule,
Message: m.Message,
})
return nil
}

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
@@ -48,7 +49,7 @@ func (DeprecatedVersion) Help() *markers.DefinitionHelp {
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{
"Warning": markers.DetailedHelp{
"Warning": {
Summary: "message to be shown on the deprecated version",
Details: "",
},
@@ -116,7 +117,7 @@ func (ListType) Help() *markers.DefinitionHelp {
Category: "CRD processing",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the type of data-structure that the list represents (map, set, atomic). ",
Details: "Possible data-structure types of a list are: \n - \"map\": it needs to have a key field, which will be used to build an associative list. A typical example is a the pod container list, which is indexed by the container name. \n - \"set\": Fields need to be \"scalar\", and there can be only one occurrence of each. \n - \"atomic\": All the fields in the list are treated as a single value, are typically manipulated together by the same actor.",
Details: "Possible data-structure types of a list are: \n - \"map\": it needs to have a key field, which will be used to build an associative list. A typical example is a the pod container list, which is indexed by the container name. \n - \"set\": Fields need to be \"scalar\", and there can be only one occurrence of each. \n - \"atomic\": All the fields in the list are treated as a single value, are typically manipulated together by the same actor.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
@@ -127,7 +128,7 @@ func (MapType) Help() *markers.DefinitionHelp {
Category: "CRD processing",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the level of atomicity of the map; i.e. whether each item in the map is independent of the others, or all fields are treated as a single unit. ",
Details: "Possible values: \n - \"granular\": items in the map are independent of each other, and can be manipulated by different actors. This is the default behavior. \n - \"atomic\": all fields are treated as one unit. Any changes have to replace the entire map.",
Details: "Possible values: \n - \"granular\": items in the map are independent of each other, and can be manipulated by different actors. This is the default behavior. \n - \"atomic\": all fields are treated as one unit. Any changes have to replace the entire map.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
@@ -177,6 +178,26 @@ func (Maximum) Help() *markers.DefinitionHelp {
}
}
func (Metadata) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD",
DetailedHelp: markers.DetailedHelp{
Summary: "configures the additional annotations or labels for this CRD. For example adding annotation \"api-approved.kubernetes.io\" for a CRD with Kubernetes groups, or annotation \"cert-manager.io/inject-ca-from-secret\" for a CRD that needs CA injection.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{
"Annotations": {
Summary: "will be added into the annotations of this CRD.",
Details: "",
},
"Labels": {
Summary: "will be added into the labels of this CRD.",
Details: "",
},
},
}
}
func (MinItems) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
@@ -214,7 +235,7 @@ 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. Negative integers are supported.",
Summary: "specifies the minimum numeric value that this field can have. Negative numbers are supported.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
@@ -360,7 +381,7 @@ func (StructType) Help() *markers.DefinitionHelp {
Category: "CRD processing",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the level of atomicity of the struct; i.e. whether each field in the struct is independent of the others, or all fields are treated as a single unit. ",
Details: "Possible values: \n - \"granular\": fields in the struct are independent of each other, and can be manipulated by different actors. This is the default behavior. \n - \"atomic\": all fields are treated as one unit. Any changes have to replace the entire struct.",
Details: "Possible values: \n - \"granular\": fields in the struct are independent of each other, and can be manipulated by different actors. This is the default behavior. \n - \"atomic\": all fields are treated as one unit. Any changes have to replace the entire struct.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
@@ -445,6 +466,17 @@ func (XEmbeddedResource) Help() *markers.DefinitionHelp {
}
}
func (XIntOrString) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "IntOrString marks a fields as an IntOrString. ",
Details: "This is required when applying patterns or other validations to an IntOrString field. Knwon information about the type is applied during the collapse phase and as such is not normally available during marker application.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (XPreserveUnknownFields) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD processing",
@@ -455,3 +487,23 @@ func (XPreserveUnknownFields) Help() *markers.DefinitionHelp {
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (XValidation) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "marks a field as requiring a value for which a given expression evaluates to true. ",
Details: "This marker may be repeated to specify multiple expressions, all of which must evaluate to true.",
},
FieldHelp: map[string]markers.DetailedHelp{
"Rule": {
Summary: "",
Details: "",
},
"Message": {
Summary: "",
Details: "",
},
},
}
}

View File

@@ -87,6 +87,9 @@ type Parser struct {
// TODO: Should we have a more formal mechanism for putting "type patterns" in each of the above categories?
AllowDangerousTypes bool
// IgnoreUnexportedFields specifies if unexported fields on the struct should be skipped
IgnoreUnexportedFields bool
// GenerateEmbeddedObjectMeta specifies if any embedded ObjectMeta should be generated
GenerateEmbeddedObjectMeta bool
}
@@ -178,7 +181,7 @@ func (p *Parser) NeedSchemaFor(typ TypeIdent) {
// avoid tripping recursive schemata, like ManagedFields, by adding an empty WIP schema
p.Schemata[typ] = apiext.JSONSchemaProps{}
schemaCtx := newSchemaContext(typ.Package, p, p.AllowDangerousTypes)
schemaCtx := newSchemaContext(typ.Package, p, p.AllowDangerousTypes, p.IgnoreUnexportedFields)
ctxForInfo := schemaCtx.ForInfo(info)
pkgMarkers, err := markers.PackageMarkers(p.Collector, typ.Package)

View File

@@ -17,8 +17,10 @@ limitations under the License.
package crd
import (
"errors"
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
@@ -37,12 +39,10 @@ const (
defPrefix = "#/definitions/"
)
var (
// byteType is the types.Type for byte (see the types documention
// for why we need to look this up in the Universe), saved
// for quick comparison.
byteType = types.Universe.Lookup("byte").Type()
)
// byteType is the types.Type for byte (see the types documention
// for why we need to look this up in the Universe), saved
// for quick comparison.
var byteType = types.Universe.Lookup("byte").Type()
// SchemaMarker is any marker that needs to modify the schema of the underlying type or field.
type SchemaMarker interface {
@@ -69,17 +69,19 @@ type schemaContext struct {
schemaRequester schemaRequester
PackageMarkers markers.MarkerValues
allowDangerousTypes bool
allowDangerousTypes bool
ignoreUnexportedFields bool
}
// newSchemaContext constructs a new schemaContext for the given package and schema requester.
// It must have type info added before use via ForInfo.
func newSchemaContext(pkg *loader.Package, req schemaRequester, allowDangerousTypes bool) *schemaContext {
func newSchemaContext(pkg *loader.Package, req schemaRequester, allowDangerousTypes, ignoreUnexportedFields bool) *schemaContext {
pkg.NeedTypesInfo()
return &schemaContext{
pkg: pkg,
schemaRequester: req,
allowDangerousTypes: allowDangerousTypes,
pkg: pkg,
schemaRequester: req,
allowDangerousTypes: allowDangerousTypes,
ignoreUnexportedFields: ignoreUnexportedFields,
}
}
@@ -87,10 +89,11 @@ func newSchemaContext(pkg *loader.Package, req schemaRequester, allowDangerousTy
// as this one, except with the given type information.
func (c *schemaContext) ForInfo(info *markers.TypeInfo) *schemaContext {
return &schemaContext{
pkg: c.pkg,
info: info,
schemaRequester: c.schemaRequester,
allowDangerousTypes: c.allowDangerousTypes,
pkg: c.pkg,
info: info,
schemaRequester: c.schemaRequester,
allowDangerousTypes: c.allowDangerousTypes,
ignoreUnexportedFields: c.ignoreUnexportedFields,
}
}
@@ -109,6 +112,15 @@ 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 the obj implements a JSON marshaler and has a marker, use the markers value and do not traverse as
// the marshaler could be doing anything. If there is no marker, fall back to traversing.
if obj := ctx.pkg.Types.Scope().Lookup(ctx.info.Name); obj != nil && implementsJSONMarshaler(obj.Type()) {
schema := &apiext.JSONSchemaProps{}
applyMarkers(ctx, ctx.info.Markers, schema, ctx.info.RawSpec.Type)
if schema.Type != "" {
return schema
}
}
return typeToSchema(ctx, ctx.info.RawSpec.Type)
}
@@ -298,14 +310,12 @@ func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *apiext.JSONSchemaPro
valSchema = namedToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
case *ast.ArrayType:
valSchema = arrayToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
if valSchema.Type == "array" && valSchema.Items.Schema.Type != "string" {
ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map values must be a named type, not %T", mapType.Value), mapType.Value))
return &apiext.JSONSchemaProps{}
}
case *ast.StarExpr:
valSchema = typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
case *ast.MapType:
valSchema = typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
default:
ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map values must be a named type, not %T", mapType.Value), mapType.Value))
ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("not a supported map value type: %T", mapType.Value), mapType.Value))
return &apiext.JSONSchemaProps{}
}
@@ -332,6 +342,11 @@ func structToSchema(ctx *schemaContext, structType *ast.StructType) *apiext.JSON
}
for _, field := range ctx.info.Fields {
// Skip if the field is not an inline field, ignoreUnexportedFields is true, and the field is not exported
if field.Name != "" && ctx.ignoreUnexportedFields && !ast.IsExported(field.Name) {
continue
}
jsonTag, hasTag := field.Tag.Lookup("json")
if !hasTag {
// if the field doesn't have a JSON tag, it doesn't belong in output (and shouldn't exist in a serialized type)
@@ -415,10 +430,13 @@ func builtinToType(basic *types.Basic, allowDangerousTypes bool) (typ string, fo
typ = "string"
case basicInfo&types.IsInteger != 0:
typ = "integer"
case basicInfo&types.IsFloat != 0 && allowDangerousTypes:
typ = "number"
case basicInfo&types.IsFloat != 0:
if allowDangerousTypes {
typ = "number"
} else {
return "", "", errors.New("found float, the usage of which is highly discouraged, as support for them varies across languages. Please consider serializing your float as string instead. If you are really sure you want to use them, re-run with crd:allowDangerousTypes=true")
}
default:
// NB(directxman12): floats are *NOT* allowed in kubernetes APIs
return "", "", fmt.Errorf("unsupported type %q", basic.String())
}
@@ -431,3 +449,16 @@ 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

@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@@ -30,13 +30,21 @@ import (
)
// SpecMarker is a marker that knows how to apply itself to a particular
// version in a CRD.
// version in a CRD Spec.
type SpecMarker interface {
// ApplyToCRD applies this marker to the given CRD, in the given version
// within that CRD. It's called after everything else in the CRD is populated.
ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error
}
// Marker is a marker that knows how to apply itself to a particular
// version in a CRD.
type Marker interface {
// ApplyToCRD applies this marker to the given CRD, in the given version
// within that CRD. It's called after everything else in the CRD is populated.
ApplyToCRD(crd *apiext.CustomResourceDefinition, version string) error
}
// NeedCRDFor requests the full CRD for the given group-kind. It requires
// that the packages containing the Go structs for that CRD have already
// been loaded with NeedPackage.
@@ -109,12 +117,14 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) {
for _, markerVals := range typeInfo.Markers {
for _, val := range markerVals {
crdMarker, isCrdMarker := val.(SpecMarker)
if !isCrdMarker {
continue
}
if err := crdMarker.ApplyToCRD(&crd.Spec, ver); err != nil {
pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
if specMarker, isSpecMarker := val.(SpecMarker); isSpecMarker {
if err := specMarker.ApplyToCRD(&crd.Spec, ver); err != nil {
pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
}
} else if crdMarker, isCRDMarker := val.(Marker); isCRDMarker {
if err := crdMarker.ApplyToCRD(&crd, ver); err != nil {
pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
}
}
}
}
@@ -164,11 +174,5 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) {
packages[0].AddError(fmt.Errorf("CRD for %s with version(s) %v does not serve any version", groupKind, crd.Spec.Versions))
}
// NB(directxman12): CRD's status doesn't have omitempty markers, which means things
// get serialized as null, which causes the validator to freak out. Manually set
// these to empty till we get a better solution.
crd.Status.Conditions = []apiext.CustomResourceDefinitionCondition{}
crd.Status.StoredVersions = []string{}
p.CustomResourceDefinitions[groupKind] = crd
}

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
@@ -32,13 +33,9 @@ func (Generator) Help() *markers.DefinitionHelp {
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{
"TrivialVersions": {
Summary: "indicates that we should produce a single-version CRD. ",
Details: "Single \"trivial-version\" CRDs are compatible with older (pre 1.13) Kubernetes API servers. The storage version's schema will be used as the CRD's schema. \n Only works with the v1beta1 CRD version.",
},
"PreserveUnknownFields": {
Summary: "indicates whether or not we should turn off pruning. ",
Details: "Left unspecified, it'll default to true when only a v1beta1 CRD is generated (to preserve compatibility with older versions of this tool), or false otherwise. \n It's required to be false for v1 CRDs.",
"IgnoreUnexportedFields": {
Summary: "indicates that we should skip unexported fields. ",
Details: "Left unspecified, the default is false.",
},
"AllowDangerousTypes": {
Summary: "allows types which are usually omitted from CRD generation because they are not recommended. ",
@@ -50,7 +47,7 @@ func (Generator) Help() *markers.DefinitionHelp {
},
"CRDVersions": {
Summary: "specifies the target API versions of the CRD type itself to generate. Defaults to v1. ",
Details: "The first version listed will be assumed to be the \"default\" version and will not get a version suffix in the output filename. \n You'll need to use \"v1\" to get support for features like defaulting, along with an API server that supports it (Kubernetes 1.16+).",
Details: "Currently, the only supported value is v1. \n The first version listed will be assumed to be the \"default\" version and will not get a version suffix in the output filename. \n You'll need to use \"v1\" to get support for features like defaulting, along with an API server that supports it (Kubernetes 1.16+).",
},
"GenerateEmbeddedObjectMeta": {
Summary: "specifies if any embedded ObjectMeta in the CRD should be generated",