From bb109f8827811f6b21a359d3351836046edc2901 Mon Sep 17 00:00:00 2001 From: Paul Gier Date: Fri, 6 Dec 2019 17:07:18 -0600 Subject: [PATCH] update controller-gen to v0.2.4 This upgrade adds structural information to the generated CRDs for the IntOrString fields. The addition of the 'x-kubernetes-int-or-string: true' field to the CRD causes some additional validation to take place which in turn requires the godoc comment on the ObjectMeta field to be removed to prevent validation errors. See related controller-tools issue for more details: https://github.com/kubernetes-sigs/controller-tools/issues/373 --- go.mod | 2 +- go.sum | 2 + pkg/apis/monitoring/v1/types.go | 24 +- vendor/modules.txt | 2 +- .../controller-tools/pkg/crd/conv.go | 122 ++++++++ .../controller-tools/pkg/crd/desc_visitor.go | 2 +- .../controller-tools/pkg/crd/flatten.go | 2 +- .../controller-tools/pkg/crd/gen.go | 86 +++++- .../controller-tools/pkg/crd/known_types.go | 11 +- .../controller-tools/pkg/crd/markers/crd.go | 78 ++---- .../pkg/crd/markers/validation.go | 78 ++++-- .../crd/markers/zz_generated.markerhelp.go | 22 ++ .../controller-tools/pkg/crd/parser.go | 2 +- .../controller-tools/pkg/crd/schema.go | 58 ++-- .../pkg/crd/schema_visitor.go | 2 +- .../controller-tools/pkg/crd/spec.go | 89 +----- .../pkg/crd/zz_generated.markerhelp.go | 6 +- .../controller-tools/pkg/genall/genall.go | 12 +- .../controller-tools/pkg/genall/options.go | 10 +- .../controller-tools/pkg/genall/output.go | 11 +- .../controller-tools/pkg/schemapatcher/gen.go | 262 ++++++++++++++---- .../schemapatcher/zz_generated.markerhelp.go | 45 +++ .../pkg/webhook/zz_generated.markerhelp.go | 2 +- 23 files changed, 630 insertions(+), 300 deletions(-) create mode 100644 vendor/sigs.k8s.io/controller-tools/pkg/crd/conv.go create mode 100644 vendor/sigs.k8s.io/controller-tools/pkg/schemapatcher/zz_generated.markerhelp.go diff --git a/go.mod b/go.mod index 9cbd7dd6507..a388485d783 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( k8s.io/client-go v12.0.0+incompatible k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269 k8s.io/klog v0.4.0 - sigs.k8s.io/controller-tools v0.2.2 + sigs.k8s.io/controller-tools v0.2.4 ) replace ( diff --git a/go.sum b/go.sum index 2b2e3e8f47b..c2b74c418ba 100644 --- a/go.sum +++ b/go.sum @@ -626,6 +626,8 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/controller-tools v0.2.2 h1:tOXKme2gR7KoM6+7Y+nzjwjbXDgqLfTuX5r7+4dvlig= sigs.k8s.io/controller-tools v0.2.2/go.mod h1:8SNGuj163x/sMwydREj7ld5mIMJu1cDanIfnx6xsU70= +sigs.k8s.io/controller-tools v0.2.4 h1:la1h46EzElvWefWLqfsXrnsO3lZjpkI0asTpX6h8PLA= +sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA= sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/apis/monitoring/v1/types.go b/pkg/apis/monitoring/v1/types.go index fcea34b0939..3a1ff403cf9 100644 --- a/pkg/apis/monitoring/v1/types.go +++ b/pkg/apis/monitoring/v1/types.go @@ -49,10 +49,7 @@ const ( // +genclient // +k8s:openapi-gen=true type Prometheus struct { - metav1.TypeMeta `json:",inline"` - // Standard object’s metadata. More info: - // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata - // +k8s:openapi-gen=false + metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` // Specification of the desired behavior of the Prometheus cluster. More info: // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status @@ -536,10 +533,7 @@ type AlertmanagerEndpoints struct { // +genclient // +k8s:openapi-gen=true type ServiceMonitor struct { - metav1.TypeMeta `json:",inline"` - // Standard object’s metadata. More info: - // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata - // +k8s:openapi-gen=false + metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` // Specification of desired Service selection for target discovery by // Prometheus. @@ -610,10 +604,7 @@ type Endpoint struct { // +genclient // +k8s:openapi-gen=true type PodMonitor struct { - metav1.TypeMeta `json:",inline"` - // Standard object’s metadata. More info: - // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata - // +k8s:openapi-gen=false + metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` // Specification of desired Pod selection for target discovery by Prometheus. Spec PodMonitorSpec `json:"spec"` @@ -805,9 +796,7 @@ type PrometheusRuleList struct { // +genclient // +k8s:openapi-gen=true type PrometheusRule struct { - metav1.TypeMeta `json:",inline"` - // Standard object’s metadata. More info: - // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` // Specification of desired alerting rule definitions for Prometheus. Spec PrometheusRuleSpec `json:"spec"` @@ -846,10 +835,7 @@ type Rule struct { // +genclient // +k8s:openapi-gen=true type Alertmanager struct { - metav1.TypeMeta `json:",inline"` - // Standard object’s metadata. More info: - // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata - // +k8s:openapi-gen=false + metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` // Specification of the desired behavior of the Alertmanager cluster. More info: // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status diff --git a/vendor/modules.txt b/vendor/modules.txt index a1ca672592d..aaa1ef86786 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -402,7 +402,7 @@ k8s.io/utils/pointer k8s.io/utils/buffer k8s.io/utils/trace k8s.io/utils/integer -# sigs.k8s.io/controller-tools v0.2.2 +# sigs.k8s.io/controller-tools v0.2.4 sigs.k8s.io/controller-tools/cmd/controller-gen sigs.k8s.io/controller-tools/pkg/crd sigs.k8s.io/controller-tools/pkg/deepcopy diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/conv.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/conv.go new file mode 100644 index 00000000000..753b7d96986 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/conv.go @@ -0,0 +1,122 @@ +package crd + +import ( + "fmt" + + 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" +) + +var ( + conversionScheme = runtime.NewScheme() +) + +func init() { + if err := apiextinternal.AddToScheme(conversionScheme); err != nil { + panic("must be able to add internal apiextensions to the CRD conversion Scheme") + } + 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) { + // 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 + // questionable decision. + intVer, err := conversionScheme.ConvertToVersion(&original, apiextinternal.SchemeGroupVersion) + if err != nil { + return nil, fmt.Errorf("unable to convert to internal CRD version: %v", err) + } + + 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) + } +} diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/desc_visitor.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/desc_visitor.go index df424e4d63f..cba36c46cb4 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/desc_visitor.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/desc_visitor.go @@ -20,7 +20,7 @@ import ( "strings" "unicode" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) // TruncateDescription truncates the description of fields in given schema if it diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/flatten.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/flatten.go index a764a41fe51..00f4dc4c7d8 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/flatten.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/flatten.go @@ -23,7 +23,7 @@ import ( "strings" "sync" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "sigs.k8s.io/controller-tools/pkg/loader" ) diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/gen.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/gen.go index 26df3632d6d..4385a5fe600 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/gen.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/gen.go @@ -20,7 +20,8 @@ import ( "fmt" "go/types" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + 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" @@ -39,14 +40,35 @@ type Generator struct { // 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"` + // MaxDescLen specifies the maximum description length for fields in CRD's OpenAPI schema. // // 0 indicates drop the description for all fields completely. // n indicates limit the description to at most n characters and truncate the description to // closest sentence boundary if it exceeds n characters. MaxDescLen *int `marker:",optional"` + + // CRDVersions specifies the target API versions of the CRD type itself to + // generate. Defaults to v1beta1. + // + // The first version listed will be assumed to be the "default" version and + // will not get a version suffix in the output filename. + // + // You'll need to use "v1" to get support for features like defaulting, + // along with an API server that supports it (Kubernetes 1.16+). + CRDVersions []string `marker:"crdVersions,optional"` } func (Generator) RegisterMarkers(into *markers.Registry) error { @@ -76,16 +98,58 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error { return nil } + crdVersions := g.CRDVersions + + if len(crdVersions) == 0 { + crdVersions = []string{"v1beta1"} + } + for _, groupKind := range kubeKinds { parser.NeedCRDFor(groupKind, g.MaxDescLen) - crd := parser.CustomResourceDefinitions[groupKind] + crdRaw := parser.CustomResourceDefinitions[groupKind] + addAttribution(&crdRaw) + + versionedCRDs := make([]interface{}, len(crdVersions)) + for i, ver := range crdVersions { + conv, err := AsVersion(crdRaw, schema.GroupVersion{Group: apiext.SchemeGroupVersion.Group, Version: ver}) + if err != nil { + return err + } + versionedCRDs[i] = conv + } + if g.TrivialVersions { - toTrivialVersions(&crd) + for i, crd := range versionedCRDs { + if crdVersions[i] == "v1beta1" { + toTrivialVersions(crd.(*apiextlegacy.CustomResourceDefinition)) + } + } } - addAttribution(&crd) - fileName := fmt.Sprintf("%s_%s.yaml", crd.Spec.Group, crd.Spec.Names.Plural) - if err := ctx.WriteYAML(fileName, crd); err != nil { - return err + + // *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 { + 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 { + return err + } } } @@ -95,10 +159,10 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error { // 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 *apiext.CustomResourceDefinition) { - var canonicalSchema *apiext.CustomResourceValidation - var canonicalSubresources *apiext.CustomResourceSubresources - var canonicalColumns []apiext.CustomResourceColumnDefinition +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 diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/known_types.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/known_types.go index af912b39c96..fafd81b3682 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/known_types.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/known_types.go @@ -16,7 +16,7 @@ limitations under the License. package crd import ( - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "sigs.k8s.io/controller-tools/pkg/loader" ) @@ -77,9 +77,10 @@ var KnownPackages = map[string]PackageOverride{ "k8s.io/apimachinery/pkg/util/intstr": func(p *Parser, pkg *loader.Package) { p.Schemata[TypeIdent{Name: "IntOrString", Package: pkg}] = apiext.JSONSchemaProps{ + XIntOrString: true, AnyOf: []apiext.JSONSchemaProps{ - {Type: "string"}, {Type: "integer"}, + {Type: "string"}, }, } // No point in calling AddPackage, this is the sole inhabitant @@ -91,6 +92,12 @@ var KnownPackages = map[string]PackageOverride{ } p.AddPackage(pkg) // get the rest of the types }, + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1": func(p *Parser, pkg *loader.Package) { + p.Schemata[TypeIdent{Name: "JSON", Package: pkg}] = apiext.JSONSchemaProps{ + XPreserveUnknownFields: boolPtr(true), + } + p.AddPackage(pkg) // get the rest of the types + }, } func boolPtr(b bool) *bool { diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/crd.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/crd.go index 54cc688c7ce..1a82db76e48 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/crd.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/crd.go @@ -19,7 +19,7 @@ package markers import ( "fmt" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "sigs.k8s.io/controller-tools/pkg/markers" ) @@ -61,25 +61,16 @@ type SubresourceStatus struct{} func (s SubresourceStatus) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { var subresources *apiext.CustomResourceSubresources - if version == "" { - // single-version - if crd.Subresources == nil { - crd.Subresources = &apiext.CustomResourceSubresources{} + for i := range crd.Versions { + ver := &crd.Versions[i] + if ver.Name != version { + continue } - subresources = crd.Subresources - } else { - // multi-version - for i := range crd.Versions { - ver := &crd.Versions[i] - if ver.Name != version { - continue - } - if ver.Subresources == nil { - ver.Subresources = &apiext.CustomResourceSubresources{} - } - subresources = ver.Subresources - break + if ver.Subresources == nil { + ver.Subresources = &apiext.CustomResourceSubresources{} } + subresources = ver.Subresources + break } if subresources == nil { return fmt.Errorf("status subresource applied to version %q not in CRD", version) @@ -109,25 +100,16 @@ type SubresourceScale struct { func (s SubresourceScale) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { var subresources *apiext.CustomResourceSubresources - if version == "" { - // single-version - if crd.Subresources == nil { - crd.Subresources = &apiext.CustomResourceSubresources{} + for i := range crd.Versions { + ver := &crd.Versions[i] + if ver.Name != version { + continue } - subresources = crd.Subresources - } else { - // multi-version - for i := range crd.Versions { - ver := &crd.Versions[i] - if ver.Name != version { - continue - } - if ver.Subresources == nil { - ver.Subresources = &apiext.CustomResourceSubresources{} - } - subresources = ver.Subresources - break + if ver.Subresources == nil { + ver.Subresources = &apiext.CustomResourceSubresources{} } + subresources = ver.Subresources + break } if subresources == nil { return fmt.Errorf("scale subresource applied to version %q not in CRD", version) @@ -228,20 +210,16 @@ type PrintColumn struct { func (s PrintColumn) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error { var columns *[]apiext.CustomResourceColumnDefinition - if version == "" { - columns = &crd.AdditionalPrinterColumns - } else { - for i := range crd.Versions { - ver := &crd.Versions[i] - if ver.Name != version { - continue - } - if ver.Subresources == nil { - ver.Subresources = &apiext.CustomResourceSubresources{} - } - columns = &ver.AdditionalPrinterColumns - break + for i := range crd.Versions { + ver := &crd.Versions[i] + if ver.Name != version { + continue } + if ver.Subresources == nil { + ver.Subresources = &apiext.CustomResourceSubresources{} + } + columns = &ver.AdditionalPrinterColumns + break } if columns == nil { return fmt.Errorf("printer columns applied to version %q not in CRD", version) @@ -288,9 +266,9 @@ type Resource struct { // The singular form is otherwise defaulted off the plural (path). Singular string `marker:",optional"` - // Scope overrides the scope of the CRD (cluster vs namespaced). + // Scope overrides the scope of the CRD (Cluster vs Namespaced). // - // Scope defaults to "namespaced". Cluster-scoped ("cluster") resources + // Scope defaults to "Namespaced". Cluster-scoped ("Cluster") resources // don't exist in namespaces. Scope string `marker:",optional"` } diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/validation.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/validation.go index 7b0d48d3fe8..d765e6d921f 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/validation.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/validation.go @@ -21,7 +21,7 @@ import ( "encoding/json" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "sigs.k8s.io/controller-tools/pkg/markers" ) @@ -58,6 +58,8 @@ var ValidationMarkers = mustMakeAllWithPrefix("kubebuilder:validation", markers. Enum(nil), Format(""), Type(""), + XPreserveUnknownFields{}, + XEmbeddedResource{}, ) // FieldOnlyMarkers list field-specific validation markers (i.e. those markers that don't make @@ -75,6 +77,11 @@ 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()), } func init() { @@ -175,7 +182,27 @@ type Default struct { Value interface{} } -func (m Maximum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +// +controllertools:marker:generateHelp:category="CRD processing" +// PreserveUnknownFields stops the apiserver from pruning fields which are not specified. +// +// 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. +type XPreserveUnknownFields struct{} + +// +controllertools:marker:generateHelp:category="CRD validation" +// EmbeddedResource marks a fields as an embedded resource with apiVersion, kind and metadata fields. +// +// An embedded resource is a value that has apiVersion, kind and metadata fields. +// They are validated implicitly according to the semantics of the currently +// running apiserver. It is not necessary to add any additional schema for these +// field, yet it is possible. This can be combined with PreserveUnknownFields. +type XEmbeddedResource struct{} + +func (m Maximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "integer" { return fmt.Errorf("must apply maximum to an integer") } @@ -183,7 +210,7 @@ func (m Maximum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { schema.Maximum = &val return nil } -func (m Minimum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m Minimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "integer" { return fmt.Errorf("must apply minimum to an integer") } @@ -191,21 +218,21 @@ func (m Minimum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { schema.Minimum = &val return nil } -func (m ExclusiveMaximum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m ExclusiveMaximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "integer" { return fmt.Errorf("must apply exclusivemaximum to an integer") } schema.ExclusiveMaximum = bool(m) return nil } -func (m ExclusiveMinimum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m ExclusiveMinimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "integer" { return fmt.Errorf("must apply exclusiveminimum to an integer") } schema.ExclusiveMinimum = bool(m) return nil } -func (m MultipleOf) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m MultipleOf) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "integer" { return fmt.Errorf("must apply multipleof to an integer") } @@ -214,7 +241,7 @@ func (m MultipleOf) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { return nil } -func (m MaxLength) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m MaxLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "string" { return fmt.Errorf("must apply maxlength to a string") } @@ -222,7 +249,7 @@ func (m MaxLength) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { schema.MaxLength = &val return nil } -func (m MinLength) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m MinLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "string" { return fmt.Errorf("must apply minlength to a string") } @@ -230,7 +257,7 @@ func (m MinLength) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { schema.MinLength = &val return nil } -func (m Pattern) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m Pattern) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "string" { return fmt.Errorf("must apply pattern to a string") } @@ -238,7 +265,7 @@ func (m Pattern) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { return nil } -func (m MaxItems) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m MaxItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "array" { return fmt.Errorf("must apply maxitem to an array") } @@ -246,7 +273,7 @@ func (m MaxItems) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { schema.MaxItems = &val return nil } -func (m MinItems) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m MinItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "array" { return fmt.Errorf("must apply minitems to an array") } @@ -254,7 +281,7 @@ func (m MinItems) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { schema.MinItems = &val return nil } -func (m UniqueItems) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m UniqueItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error { if schema.Type != "array" { return fmt.Errorf("must apply uniqueitems to an array") } @@ -262,10 +289,10 @@ func (m UniqueItems) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { return nil } -func (m Enum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +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 - vals := make([]v1beta1.JSON, len(m)) + vals := make([]apiext.JSON, len(m)) for i, val := range m { // TODO(directxman12): check actual type with schema type? // if we're expecting a string, marshal the string properly... @@ -274,12 +301,12 @@ func (m Enum) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { if err != nil { return err } - vals[i] = v1beta1.JSON{Raw: valMarshalled} + vals[i] = apiext.JSON{Raw: valMarshalled} } schema.Enum = vals return nil } -func (m Format) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m Format) ApplyToSchema(schema *apiext.JSONSchemaProps) error { schema.Format = string(m) return nil } @@ -289,24 +316,35 @@ func (m Format) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { // TODO(directxman12): find a less hacky way to do this // (we could preserve ordering of markers, but that feels bad in its own right). -func (m Type) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m Type) ApplyToSchema(schema *apiext.JSONSchemaProps) error { schema.Type = string(m) return nil } func (m Type) ApplyFirst() {} -func (m Nullable) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m Nullable) ApplyToSchema(schema *apiext.JSONSchemaProps) error { schema.Nullable = true return nil } // Defaults are only valid CRDs created with the v1 API -func (m Default) ApplyToSchema(schema *v1beta1.JSONSchemaProps) error { +func (m Default) ApplyToSchema(schema *apiext.JSONSchemaProps) error { marshalledDefault, err := json.Marshal(m.Value) if err != nil { return err } - schema.Default = &v1beta1.JSON{Raw: marshalledDefault} + schema.Default = &apiext.JSON{Raw: marshalledDefault} + return nil +} + +func (m XPreserveUnknownFields) ApplyToSchema(schema *apiext.JSONSchemaProps) error { + defTrue := true + schema.XPreserveUnknownFields = &defTrue + return nil +} + +func (m XEmbeddedResource) ApplyToSchema(schema *apiext.JSONSchemaProps) error { + schema.XEmbeddedResource = true return nil } diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/zz_generated.markerhelp.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/zz_generated.markerhelp.go index fbf58ea301e..aeb8bec6427 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/zz_generated.markerhelp.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/markers/zz_generated.markerhelp.go @@ -329,3 +329,25 @@ func (UniqueItems) Help() *markers.DefinitionHelp { FieldHelp: map[string]markers.DetailedHelp{}, } } + +func (XEmbeddedResource) Help() *markers.DefinitionHelp { + return &markers.DefinitionHelp{ + Category: "CRD validation", + DetailedHelp: markers.DetailedHelp{ + Summary: "EmbeddedResource marks a fields as an embedded resource with apiVersion, kind and metadata fields. ", + Details: "An embedded resource is a value that has apiVersion, kind and metadata fields. They are validated implicitly according to the semantics of the currently running apiserver. It is not necessary to add any additional schema for these field, yet it is possible. This can be combined with PreserveUnknownFields.", + }, + FieldHelp: map[string]markers.DetailedHelp{}, + } +} + +func (XPreserveUnknownFields) Help() *markers.DefinitionHelp { + return &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.", + }, + FieldHelp: map[string]markers.DetailedHelp{}, + } +} diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/parser.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/parser.go index 2c27b241684..34398a8c725 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/parser.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/parser.go @@ -20,7 +20,7 @@ import ( "fmt" "go/ast" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-tools/pkg/loader" diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/schema.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/schema.go index e062eeb15ae..89237e6a76a 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/schema.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/schema.go @@ -22,7 +22,7 @@ import ( "go/types" "strings" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "sigs.k8s.io/controller-tools/pkg/loader" "sigs.k8s.io/controller-tools/pkg/markers" @@ -47,7 +47,7 @@ var ( type SchemaMarker interface { // ApplyToSchema is called after the rest of the schema for a given type // or field is generated, to modify the schema appropriately. - ApplyToSchema(*v1beta1.JSONSchemaProps) error + ApplyToSchema(*apiext.JSONSchemaProps) error } // applyFirstMarker is applied before any other markers. It's a bit of a hack. @@ -103,12 +103,12 @@ 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) *v1beta1.JSONSchemaProps { +func infoToSchema(ctx *schemaContext) *apiext.JSONSchemaProps { return typeToSchema(ctx, ctx.info.RawSpec.Type) } // applyMarkers applies schema markers to the given schema, respecting "apply first" markers. -func applyMarkers(ctx *schemaContext, markerSet markers.MarkerValues, props *v1beta1.JSONSchemaProps, node ast.Node) { +func applyMarkers(ctx *schemaContext, markerSet markers.MarkerValues, props *apiext.JSONSchemaProps, node ast.Node) { // apply "apply first" markers first... for _, markerValues := range markerSet { for _, markerValue := range markerValues { @@ -147,8 +147,8 @@ func applyMarkers(ctx *schemaContext, markerSet markers.MarkerValues, props *v1b } // typeToSchema creates a schema for the given AST type. -func typeToSchema(ctx *schemaContext, rawType ast.Expr) *v1beta1.JSONSchemaProps { - var props *v1beta1.JSONSchemaProps +func typeToSchema(ctx *schemaContext, rawType ast.Expr) *apiext.JSONSchemaProps { + var props *apiext.JSONSchemaProps switch expr := rawType.(type) { case *ast.Ident: props = localNamedToSchema(ctx, expr) @@ -165,7 +165,7 @@ func typeToSchema(ctx *schemaContext, rawType ast.Expr) *v1beta1.JSONSchemaProps default: ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("unsupported AST kind %T", expr), rawType)) // NB(directxman12): we explicitly don't handle interfaces - return &v1beta1.JSONSchemaProps{} + return &apiext.JSONSchemaProps{} } props.Description = ctx.info.Doc @@ -193,18 +193,18 @@ func TypeRefLink(pkgName, typeName string) string { // localNamedToSchema creates a schema (ref) for a *potentially* local type reference // (could be external from a dot-import). -func localNamedToSchema(ctx *schemaContext, ident *ast.Ident) *v1beta1.JSONSchemaProps { +func localNamedToSchema(ctx *schemaContext, ident *ast.Ident) *apiext.JSONSchemaProps { typeInfo := ctx.pkg.TypesInfo.TypeOf(ident) if typeInfo == types.Typ[types.Invalid] { ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("unknown type %s", ident.Name), ident)) - return &v1beta1.JSONSchemaProps{} + return &apiext.JSONSchemaProps{} } if basicInfo, isBasic := typeInfo.(*types.Basic); isBasic { typ, fmt, err := builtinToType(basicInfo) if err != nil { ctx.pkg.AddError(loader.ErrFromNode(err, ident)) } - return &v1beta1.JSONSchemaProps{ + return &apiext.JSONSchemaProps{ Type: typ, Format: fmt, } @@ -219,24 +219,24 @@ func localNamedToSchema(ctx *schemaContext, ident *ast.Ident) *v1beta1.JSONSchem } ctx.requestSchema(pkgPath, typeNameInfo.Name()) link := TypeRefLink(pkgPath, typeNameInfo.Name()) - return &v1beta1.JSONSchemaProps{ + return &apiext.JSONSchemaProps{ Ref: &link, } } // namedSchema creates a schema (ref) for an explicitly external type reference. -func namedToSchema(ctx *schemaContext, named *ast.SelectorExpr) *v1beta1.JSONSchemaProps { +func namedToSchema(ctx *schemaContext, named *ast.SelectorExpr) *apiext.JSONSchemaProps { typeInfoRaw := ctx.pkg.TypesInfo.TypeOf(named) if typeInfoRaw == types.Typ[types.Invalid] { ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("unknown type %v.%s", named.X, named.Sel.Name), named)) - return &v1beta1.JSONSchemaProps{} + return &apiext.JSONSchemaProps{} } typeInfo := typeInfoRaw.(*types.Named) typeNameInfo := typeInfo.Obj() nonVendorPath := loader.NonVendorPath(typeNameInfo.Pkg().Path()) ctx.requestSchema(nonVendorPath, typeNameInfo.Name()) link := TypeRefLink(nonVendorPath, typeNameInfo.Name()) - return &v1beta1.JSONSchemaProps{ + return &apiext.JSONSchemaProps{ Ref: &link, } // NB(directxman12): we special-case things like resource.Quantity during the "collapse" phase. @@ -244,12 +244,12 @@ func namedToSchema(ctx *schemaContext, named *ast.SelectorExpr) *v1beta1.JSONSch // arrayToSchema creates a schema for the items of the given array, dealing appropriately // with the special `[]byte` type (according to OpenAPI standards). -func arrayToSchema(ctx *schemaContext, array *ast.ArrayType) *v1beta1.JSONSchemaProps { +func arrayToSchema(ctx *schemaContext, array *ast.ArrayType) *apiext.JSONSchemaProps { eltType := ctx.pkg.TypesInfo.TypeOf(array.Elt) if eltType == byteType && array.Len == nil { // byte slices are represented as base64-encoded strings // (the format is defined in OpenAPI v3, but not JSON Schema) - return &v1beta1.JSONSchemaProps{ + return &apiext.JSONSchemaProps{ Type: "string", Format: "byte", } @@ -257,15 +257,15 @@ func arrayToSchema(ctx *schemaContext, array *ast.ArrayType) *v1beta1.JSONSchema // TODO(directxman12): backwards-compat would require access to markers from base info items := typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), array.Elt) - return &v1beta1.JSONSchemaProps{ + return &apiext.JSONSchemaProps{ Type: "array", - Items: &v1beta1.JSONSchemaPropsOrArray{Schema: items}, + Items: &apiext.JSONSchemaPropsOrArray{Schema: items}, } } // mapToSchema creates a schema for items of the given map. Key types must eventually resolve // to string (other types aren't allowed by JSON, and thus the kubernetes API standards). -func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *v1beta1.JSONSchemaProps { +func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *apiext.JSONSchemaProps { keyInfo := ctx.pkg.TypesInfo.TypeOf(mapType.Key) // check that we've got a type that actually corresponds to a string for keyInfo != nil { @@ -273,19 +273,19 @@ func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *v1beta1.JSONSchemaPr case *types.Basic: if typedKey.Info()&types.IsString == 0 { ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map keys must be strings, not %s", keyInfo.String()), mapType.Key)) - return &v1beta1.JSONSchemaProps{} + return &apiext.JSONSchemaProps{} } keyInfo = nil // stop iterating case *types.Named: keyInfo = typedKey.Underlying() default: ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map keys must be strings, not %s", keyInfo.String()), mapType.Key)) - return &v1beta1.JSONSchemaProps{} + return &apiext.JSONSchemaProps{} } } // TODO(directxman12): backwards-compat would require access to markers from base info - var valSchema *v1beta1.JSONSchemaProps + var valSchema *apiext.JSONSchemaProps switch val := mapType.Value.(type) { case *ast.Ident: valSchema = localNamedToSchema(ctx.ForInfo(&markers.TypeInfo{}), val) @@ -295,16 +295,16 @@ func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *v1beta1.JSONSchemaPr 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 &v1beta1.JSONSchemaProps{} + return &apiext.JSONSchemaProps{} } default: ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map values must be a named type, not %T", mapType.Value), mapType.Value)) - return &v1beta1.JSONSchemaProps{} + return &apiext.JSONSchemaProps{} } - return &v1beta1.JSONSchemaProps{ + return &apiext.JSONSchemaProps{ Type: "object", - AdditionalProperties: &v1beta1.JSONSchemaPropsOrBool{ + AdditionalProperties: &apiext.JSONSchemaPropsOrBool{ Schema: valSchema, Allows: true, /* set automatically by serialization, but useful for testing */ }, @@ -313,10 +313,10 @@ func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *v1beta1.JSONSchemaPr // structToSchema creates a schema for the given struct. Embedded fields are placed in AllOf, // and can be flattened later with a Flattener. -func structToSchema(ctx *schemaContext, structType *ast.StructType) *v1beta1.JSONSchemaProps { - props := &v1beta1.JSONSchemaProps{ +func structToSchema(ctx *schemaContext, structType *ast.StructType) *apiext.JSONSchemaProps { + props := &apiext.JSONSchemaProps{ Type: "object", - Properties: make(map[string]v1beta1.JSONSchemaProps), + Properties: make(map[string]apiext.JSONSchemaProps), } if ctx.info.RawSpec.Type != structType { diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/schema_visitor.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/schema_visitor.go index ab4b1889c44..fc12c528c5c 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/schema_visitor.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/schema_visitor.go @@ -17,7 +17,7 @@ limitations under the License. package crd import ( - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) // SchemaVisitor walks the nodes of a schema. diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/spec.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/spec.go index bc0ce06f34f..01bef20d49c 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/spec.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/spec.go @@ -22,8 +22,7 @@ import ( "github.com/gobuffalo/flect" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/api/equality" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -38,86 +37,6 @@ type SpecMarker interface { ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error } -// 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 *apiext.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 *apiext.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 *apiext.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 *apiext.CustomResourceDefinition) { - if len(crd.Spec.Versions) > 0 { - mergeIdenticalSubresources(crd) - mergeIdenticalSchemata(crd) - mergeIdenticalPrinterColumns(crd) - } -} - // 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. @@ -153,6 +72,7 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) { Plural: defaultPlural, Singular: strings.ToLower(groupKind.Kind), }, + Scope: apiext.NamespaceScoped, }, } @@ -211,7 +131,6 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) { // it is necessary to make sure the order of CRD versions in crd.Spec.Versions is stable and explicitly set crd.Spec.Version. // Otherwise, crd.Spec.Version may point to different CRD versions across different runs. sort.Slice(crd.Spec.Versions, func(i, j int) bool { return crd.Spec.Versions[i].Name < crd.Spec.Versions[j].Name }) - crd.Spec.Version = crd.Spec.Versions[0].Name // make sure we have *a* storage version // (default it if we only have one, otherwise, bail) @@ -238,9 +157,5 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) { crd.Status.Conditions = []apiext.CustomResourceDefinitionCondition{} crd.Status.StoredVersions = []string{} - // make sure we merge identical per-version parts, to avoid validation errors - // (see the reasoning near the top of the file). - MergeIdenticalVersionInfo(&crd) - p.CustomResourceDefinitions[groupKind] = crd } diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/crd/zz_generated.markerhelp.go b/vendor/sigs.k8s.io/controller-tools/pkg/crd/zz_generated.markerhelp.go index c36ea2a4765..605214a81e3 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/crd/zz_generated.markerhelp.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/crd/zz_generated.markerhelp.go @@ -34,12 +34,16 @@ func (Generator) Help() *markers.DefinitionHelp { FieldHelp: map[string]markers.DetailedHelp{ "TrivialVersions": markers.DetailedHelp{ 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.", + 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.", }, "MaxDescLen": markers.DetailedHelp{ Summary: "specifies the maximum description length for fields in CRD's OpenAPI schema. ", Details: "0 indicates drop the description for all fields completely. n indicates limit the description to at most n characters and truncate the description to closest sentence boundary if it exceeds n characters.", }, + "CRDVersions": markers.DetailedHelp{ + Summary: "specifies the target API versions of the CRD type itself to generate. Defaults to v1beta1. ", + 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+).", + }, }, } } diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/genall/genall.go b/vendor/sigs.k8s.io/controller-tools/pkg/genall/genall.go index 086b4df13e9..9fcc76236b4 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/genall/genall.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/genall/genall.go @@ -30,13 +30,16 @@ import ( ) // Generators are a list of Generators. -type Generators []Generator +// NB(directxman12): this is a pointer so that we can uniquely identify each +// instance of a generator, even if it's not hashable. Different *instances* +// of a generator are treated differently. +type Generators []*Generator // RegisterMarkers registers all markers defined by each of the Generators in // this list into the given registry. func (g Generators) RegisterMarkers(reg *markers.Registry) error { for _, gen := range g { - if err := gen.RegisterMarkers(reg); err != nil { + if err := (*gen).RegisterMarkers(reg); err != nil { return err } } @@ -90,7 +93,8 @@ type GenerationContext struct { } // WriteYAML writes the given objects out, serialized as YAML, using the -// context's OutputRule. +// context's OutputRule. Objects are written as separate documents, separated +// from each other by `---` (as per the YAML spec). func (g GenerationContext) WriteYAML(itemPath string, objs ...interface{}) error { out, err := g.Open(nil, itemPath) if err != nil { @@ -164,7 +168,7 @@ func (r *Runtime) Run() bool { for _, gen := range r.Generators { ctx := r.GenerationContext // make a shallow copy ctx.OutputRule = r.OutputRules.ForGenerator(gen) - if err := gen.Generate(&ctx); err != nil { + if err := (*gen).Generate(&ctx); err != nil { fmt.Fprintln(os.Stderr, err) } } diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/genall/options.go b/vendor/sigs.k8s.io/controller-tools/pkg/genall/options.go index 613d19e6bb0..31076a5cb73 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/genall/options.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/genall/options.go @@ -108,14 +108,14 @@ func FromOptions(optionsRegistry *markers.Registry, options []string) (*Runtime, func protoFromOptions(optionsRegistry *markers.Registry, options []string) (protoRuntime, error) { var gens Generators rules := OutputRules{ - ByGenerator: make(map[Generator]OutputRule), + ByGenerator: make(map[*Generator]OutputRule), } var paths []string // collect the generators first, so that we can key the output on the actual // generator, which matters if there's settings in the gen object and it's not a pointer. outputByGen := make(map[string]OutputRule) - gensByName := make(map[string]Generator) + gensByName := make(map[string]*Generator) for _, rawOpt := range options { if rawOpt[0] != '+' { @@ -133,8 +133,8 @@ func protoFromOptions(optionsRegistry *markers.Registry, options []string) (prot switch val := val.(type) { case Generator: - gens = append(gens, val) - gensByName[defn.Name] = val + gens = append(gens, &val) + gensByName[defn.Name] = &val case OutputRule: _, genName := splitOutputRuleOption(defn.Name) if genName == "" { @@ -176,7 +176,7 @@ type protoRuntime struct { Paths []string Generators Generators OutputRules OutputRules - GeneratorsByName map[string]Generator + GeneratorsByName map[string]*Generator } // splitOutputRuleOption splits a marker name of "output:rule:gen" or "output:rule" diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/genall/output.go b/vendor/sigs.k8s.io/controller-tools/pkg/genall/output.go index a61fe9d7e1a..b5a09e1692e 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/genall/output.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/genall/output.go @@ -39,10 +39,10 @@ func (n nopCloser) Close() error { // DirectoryPerGenerator produces output rules mapping output to a different subdirectory // of the given base directory for each generator (with each subdirectory specified as // the key in the input map). -func DirectoryPerGenerator(base string, generators map[string]Generator) OutputRules { +func DirectoryPerGenerator(base string, generators map[string]*Generator) OutputRules { rules := OutputRules{ Default: OutputArtifacts{Config: OutputToDirectory(base)}, - ByGenerator: make(map[Generator]OutputRule, len(generators)), + ByGenerator: make(map[*Generator]OutputRule, len(generators)), } for name, gen := range generators { @@ -59,12 +59,15 @@ type OutputRules struct { // Default is the output rule used when no specific per-generator overrides match. Default OutputRule // ByGenerator contains specific per-generator overrides. - ByGenerator map[Generator]OutputRule + // NB(directxman12): this is a pointer to avoid issues if a given Generator becomes unhashable + // (interface values compare by "dereferencing" their internal pointer first, whereas pointers + // compare by the actual pointer itself). + ByGenerator map[*Generator]OutputRule } // ForGenerator returns the output rule that should be used // by the given Generator. -func (o OutputRules) ForGenerator(gen Generator) OutputRule { +func (o OutputRules) ForGenerator(gen *Generator) OutputRule { if forGen, specific := o.ByGenerator[gen]; specific { return forGen } diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/schemapatcher/gen.go b/vendor/sigs.k8s.io/controller-tools/pkg/schemapatcher/gen.go index 21901da7b60..5217ee4b9dd 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/schemapatcher/gen.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/schemapatcher/gen.go @@ -22,7 +22,8 @@ import ( "path/filepath" "gopkg.in/yaml.v3" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextlegacy "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -51,16 +52,27 @@ import ( // tool that generates a patch, and a separate tool for applying stable YAML // patches. +var ( + legacyAPIExtVersion = apiextlegacy.SchemeGroupVersion.String() + currentAPIExtVersion = apiext.SchemeGroupVersion.String() +) + +// +controllertools:marker:generateHelp + // Generator patches existing CRDs with new schemata. // -// For single-version CRDs, it will simply replace the global schema. +// For legacy (v1beta1) single-version CRDs, it will simply replace the global schema. // -// For multi-version CRDs, it will replace schemata of existing versions -// and *clear the schema* from any versions not specified in the Go code. -// It will *not* add new versions, or remove old ones. +// For legacy (v1beta1) multi-version CRDs, and any v1 CRDs, it will replace +// schemata of existing versions and *clear the schema* from any versions not +// specified in the Go code. It will *not* add new versions, or remove old +// ones. // -// For multi-version CRDs with identical schemata, it will take care of +// For legacy multi-version CRDs with identical schemata, it will take care of // lifting the per-version schema up to the global schema. +// +// It will generate output for each "CRD Version" (API version of the CRD type +// itself) , e.g. apiextensions/v1beta1 and apiextensions/v1) available. type Generator struct { // ManifestsPath contains the CustomResourceDefinition YAML files. ManifestsPath string `marker:"manifests"` @@ -97,14 +109,14 @@ func (g Generator) Generate(ctx *genall.GenerationContext) (result error) { } // load existing CRD manifests with group-kind and versions - partialCRDs, err := crdsFromDirectory(ctx, g.ManifestsPath) + partialCRDSets, err := crdsFromDirectory(ctx, g.ManifestsPath) if err != nil { return err } // generate schemata for the types we care about, and save them to be written later. for _, groupKind := range crdgen.FindKubeKinds(parser, metav1Pkg) { - existingInfo, wanted := partialCRDs[groupKind] + existingSet, wanted := partialCRDSets[groupKind] if !wanted { continue } @@ -113,7 +125,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) (result error) { if gv.Group != groupKind.Group { continue } - if _, wantedVersion := existingInfo.Versions[gv.Version]; !wantedVersion { + if _, wantedVersion := existingSet.Versions[gv.Version]; !wantedVersion { continue } @@ -125,31 +137,31 @@ func (g Generator) Generate(ctx *genall.GenerationContext) (result error) { fullSchema = *fullSchema.DeepCopy() crdgen.TruncateDescription(&fullSchema, *g.MaxDescLen) } - existingInfo.NewSchemata[gv.Version] = fullSchema + existingSet.NewSchemata[gv.Version] = fullSchema } } // patch existing CRDs with new schemata - for _, existingInfo := range partialCRDs { + for _, existingSet := range partialCRDSets { // first, figure out if we need to merge schemata together if they're *all* // identical (meaning we also don't have any "unset" versions) - if len(existingInfo.NewSchemata) == 0 { + if len(existingSet.NewSchemata) == 0 { continue } // copy over the new versions that we have, keeping old versions so - // that we can tell if a schema would be nill + // that we can tell if a schema would be nil var someVer string - for ver := range existingInfo.NewSchemata { + for ver := range existingSet.NewSchemata { someVer = ver - existingInfo.Versions[ver] = struct{}{} + existingSet.Versions[ver] = struct{}{} } allSame := true - firstSchema := existingInfo.NewSchemata[someVer] - for ver := range existingInfo.Versions { - otherSchema, hasSchema := existingInfo.NewSchemata[ver] + firstSchema := existingSet.NewSchemata[someVer] + for ver := range existingSet.Versions { + otherSchema, hasSchema := existingSet.NewSchemata[ver] if !hasSchema || !equality.Semantic.DeepEqual(firstSchema, otherSchema) { allSame = false break @@ -157,60 +169,117 @@ func (g Generator) Generate(ctx *genall.GenerationContext) (result error) { } if allSame { - if err := existingInfo.setGlobalSchema(); err != nil { - return fmt.Errorf("failed to set global firstSchema for %s: %v", existingInfo.GroupKind, err) + if err := existingSet.setGlobalSchema(); err != nil { + return fmt.Errorf("failed to set global firstSchema for %s: %v", existingSet.GroupKind, err) } } else { - if err := existingInfo.setVersionedSchemata(); err != nil { - return fmt.Errorf("failed to set versioned schemas for %s: %v", existingInfo.GroupKind, err) + if err := existingSet.setVersionedSchemata(); err != nil { + return fmt.Errorf("failed to set versioned schemas for %s: %v", existingSet.GroupKind, err) } } } // write the final result out to the new location - for _, crd := range partialCRDs { - if err := func() error { - outWriter, err := ctx.OutputRule.Open(nil, crd.FileName) - if err != nil { + for _, set := range partialCRDSets { + // We assume all CRD versions came from different files, since this + // is how controller-gen works. If they came from the same file, + // it'd be non-sensical, since you couldn't reasonably use kubectl + // with them against older servers. + for _, crd := range set.CRDVersions { + if err := func() error { + outWriter, err := ctx.OutputRule.Open(nil, crd.FileName) + if err != nil { + return err + } + defer outWriter.Close() + + enc := yaml.NewEncoder(outWriter) + // yaml.v2 defaults to indent=2, yaml.v3 defaults to indent=4, + // so be compatible with everything else in k8s and choose 2. + enc.SetIndent(2) + + return enc.Encode(crd.Yaml) + }(); err != nil { return err } - defer outWriter.Close() - - enc := yaml.NewEncoder(outWriter) - // yaml.v2 defaults to indent=2, yaml.v3 defaults to indent=4, - // so be compatible with everything else in k8s and choose 2. - enc.SetIndent(2) - return enc.Encode(crd.Yaml) - }(); err != nil { - return err } } return nil } -// partialCRD tracks modifications to the schemata of a CRD. It contains the -// raw YAML representation of a CRD, plus some structured content (versions, -// filename, etc) for easy lookup, and any new schemata registered. -type partialCRD struct { +// partialCRDSet represents a set of CRDs of different apiext versions +// (v1beta1.CRD vs v1.CRD) that represent the same GroupKind. +// +// It tracks modifications to the schemata of those CRDs from this source file, +// plus some useful structured content, and keeps track of the raw YAML representation +// of the different apiext versions. +type partialCRDSet struct { + // GroupKind is the GroupKind represented by this CRD. GroupKind schema.GroupKind - Yaml *yaml.Node - Versions map[string]struct{} - FileName string - + // NewSchemata are the new schemata generated from Go IDL by controller-gen. NewSchemata map[string]apiext.JSONSchemaProps + // CRDVersions are the forms of this CRD across different apiextensions + // versions + CRDVersions []*partialCRD + // Versions are the versions of the given GroupKind in this set of CRDs. + Versions map[string]struct{} } -// setGlobalSchema sets the global schema to one of the schemata -// for this CRD. All schemata must be identical for this to be a valid operation. -func (e *partialCRD) setGlobalSchema() error { +// partialCRD represents the raw YAML encoding of a given CRD instance, plus +// the versions contained therein for easy lookup. +type partialCRD struct { + // Yaml is the raw YAML structure of the CRD. + Yaml *yaml.Node + // FileName is the source name of the file that this was read from. + // + // This isn't on partialCRDSet because we could have different CRD versions + // stored in the same file (like controller-tools does by default) or in + // different files. + FileName string + + // CRDVersion is the version of the CRD object itself, from + // apiextensions (currently apiextensions/v1 or apiextensions/v1beta1). + CRDVersion string +} + +// setGlobalSchema sets the global schema for the v1beta1 apiext version in +// this set (if present, as per partialCRD.setGlobalSchema), and sets the +// versioned schemas (as per setVersionedSchemata) for the v1 version. +func (e *partialCRDSet) setGlobalSchema() error { // there's no easy way to get a "random" key from a go map :-/ var schema apiext.JSONSchemaProps for ver := range e.NewSchemata { schema = e.NewSchemata[ver] break } + for _, crdInfo := range e.CRDVersions { + switch crdInfo.CRDVersion { + case legacyAPIExtVersion: + if err := crdInfo.setGlobalSchema(schema); err != nil { + return err + } + case currentAPIExtVersion: + // just set the schemata as normal for non-legacy versions + if err := crdInfo.setVersionedSchemata(e.NewSchemata); err != nil { + return err + } + } + } + return nil +} +// setGlobalSchema sets the global schema to one of the schemata +// for this CRD. All schemata must be identical for this to be a valid operation. +func (e *partialCRD) setGlobalSchema(newSchema apiext.JSONSchemaProps) error { + if e.CRDVersion != legacyAPIExtVersion { + // no global schema, nothing to do + return fmt.Errorf("cannot set global schema on non-legacy CRD versions") + } + schema, err := legacySchema(newSchema) + if err != nil { + return fmt.Errorf("failed to convert schema to legacy form: %v", err) + } schemaNodeTree, err := yamlop.ToYAML(schema) if err != nil { return err @@ -254,10 +323,21 @@ func (e *partialCRD) getVersionsNode() (*yaml.Node, bool, error) { return versions, found, nil } +// setVersionedSchemata sets the versioned schemata on each encoding in this set as per +// setVersionedSchemata on partialCRD. +func (e *partialCRDSet) setVersionedSchemata() error { + for _, crdInfo := range e.CRDVersions { + if err := crdInfo.setVersionedSchemata(e.NewSchemata); err != nil { + return err + } + } + return nil +} + // setVersionedSchemata populates all existing versions with new schemata, // wiping the schema of any version that doesn't have a listed schema. // Any "unknown" versions are ignored. -func (e *partialCRD) setVersionedSchemata() error { +func (e *partialCRD) setVersionedSchemata(newSchemata map[string]apiext.JSONSchemaProps) error { var err error if err := yamlop.DeleteNode(e.Yaml, "spec", "validation"); err != nil { return err @@ -280,13 +360,22 @@ func (e *partialCRD) setVersionedSchemata() error { if name == "" { return fmt.Errorf("unexpected empty name at spec.versions[%d]", i) } - newSchema, found := e.NewSchemata[name] + newSchema, found := newSchemata[name] if !found { if err := yamlop.DeleteNode(verNode, "schema"); err != nil { return fmt.Errorf("spec.versions[%d]: %v", i, err) } } else { - schemaNodeTree, err := yamlop.ToYAML(newSchema) + // TODO(directxman12): if this gets to be more than 2 versions, use polymorphism to clean this up + var verSchema interface{} = newSchema + if e.CRDVersion == legacyAPIExtVersion { + verSchema, err = legacySchema(newSchema) + if err != nil { + return fmt.Errorf("failed to convert schema to legacy form: %v", err) + } + } + + schemaNodeTree, err := yamlop.ToYAML(verSchema) if err != nil { return fmt.Errorf("failed to convert schema to YAML: %v", err) } @@ -303,10 +392,8 @@ func (e *partialCRD) setVersionedSchemata() error { // crdsFromDirectory returns loads all CRDs from the given directory in a // manner that preserves ordering, comments, etc in order to make patching // minimally invasive. Returned CRDs are mapped by group-kind. -func crdsFromDirectory(ctx *genall.GenerationContext, dir string) (map[schema.GroupKind]*partialCRD, error) { - apiextAPIVersion := apiext.SchemeGroupVersion.String() - - res := map[schema.GroupKind]*partialCRD{} +func crdsFromDirectory(ctx *genall.GenerationContext, dir string) (map[schema.GroupKind]*partialCRDSet, error) { + res := map[schema.GroupKind]*partialCRDSet{} dirEntries, err := ioutil.ReadDir(dir) if err != nil { return nil, err @@ -330,12 +417,12 @@ func crdsFromDirectory(ctx *genall.GenerationContext, dir string) (map[schema.Gr if err := kyaml.Unmarshal(rawContent, &typeMeta); err != nil { continue } - if typeMeta.APIVersion != apiextAPIVersion || typeMeta.Kind != "CustomResourceDefinition" { + if !isSupportedAPIExtGroupVer(typeMeta.APIVersion) || typeMeta.Kind != "CustomResourceDefinition" { continue } // collect the group-kind and versions from the actual structured form - var actualCRD apiext.CustomResourceDefinition + var actualCRD crdIsh if err := kyaml.Unmarshal(rawContent, &actualCRD); err != nil { continue } @@ -356,13 +443,66 @@ func crdsFromDirectory(ctx *genall.GenerationContext, dir string) (map[schema.Gr continue } - res[groupKind] = &partialCRD{ - GroupKind: groupKind, - Yaml: &yamlNodeTree, - Versions: versions, - FileName: fileInfo.Name(), - NewSchemata: make(map[string]apiext.JSONSchemaProps), + // then store this CRDVersion of the CRD in a set, populating the set if necessary + if res[groupKind] == nil { + res[groupKind] = &partialCRDSet{ + GroupKind: groupKind, + NewSchemata: make(map[string]apiext.JSONSchemaProps), + Versions: make(map[string]struct{}), + } + } + for ver := range versions { + res[groupKind].Versions[ver] = struct{}{} } + res[groupKind].CRDVersions = append(res[groupKind].CRDVersions, &partialCRD{ + Yaml: &yamlNodeTree, + FileName: fileInfo.Name(), + CRDVersion: typeMeta.APIVersion, + }) } return res, nil } + +// isSupportedAPIExtGroupVer checks if the given string-form group-version +// is one of the known apiextensions versions (v1, v1beta1). +func isSupportedAPIExtGroupVer(groupVer string) bool { + return groupVer == currentAPIExtVersion || groupVer == legacyAPIExtVersion +} + +// crdIsh is a merged blob of CRD fields that looks enough like all versions of +// CRD to extract the relevant information for partialCRDSet and partialCRD. +// +// We keep this separate so it's clear what info we need, and so we don't break +// when we switch canonical internal versions and lose old fields while gaining +// new ones (like in v1beta1 --> v1). +// +// Its use is tied directly to crdsFromDirectory, and is mostly an implementation detail of that. +type crdIsh struct { + Spec struct { + Group string `json:"group"` + Names struct { + Kind string `json:"kind"` + } `json:"names"` + Versions []struct { + Name string `json:"name"` + } `json:"versions"` + Version string `json:"version"` + } `json:"spec"` +} + +// legacySchema jumps through some hoops to convert a v1 schema to a v1beta1 schema. +func legacySchema(origSchema apiext.JSONSchemaProps) (apiextlegacy.JSONSchemaProps, error) { + shellCRD := apiext.CustomResourceDefinition{} + shellCRD.APIVersion = currentAPIExtVersion + shellCRD.Kind = "CustomResourceDefinition" + shellCRD.Spec.Versions = []apiext.CustomResourceDefinitionVersion{ + {Schema: &apiext.CustomResourceValidation{OpenAPIV3Schema: origSchema.DeepCopy()}}, + } + + legacyCRD, err := crdgen.AsVersion(shellCRD, apiextlegacy.SchemeGroupVersion) + if err != nil { + return apiextlegacy.JSONSchemaProps{}, err + } + + return *legacyCRD.(*apiextlegacy.CustomResourceDefinition).Spec.Validation.OpenAPIV3Schema, nil +} diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/schemapatcher/zz_generated.markerhelp.go b/vendor/sigs.k8s.io/controller-tools/pkg/schemapatcher/zz_generated.markerhelp.go new file mode 100644 index 00000000000..230cb9c0669 --- /dev/null +++ b/vendor/sigs.k8s.io/controller-tools/pkg/schemapatcher/zz_generated.markerhelp.go @@ -0,0 +1,45 @@ +// +build !ignore_autogenerated + +/* +Copyright2019 The Kubernetes Authors. + +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 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by helpgen. DO NOT EDIT. + +package schemapatcher + +import ( + "sigs.k8s.io/controller-tools/pkg/markers" +) + +func (Generator) Help() *markers.DefinitionHelp { + return &markers.DefinitionHelp{ + Category: "", + DetailedHelp: markers.DetailedHelp{ + Summary: "patches existing CRDs with new schemata. ", + Details: "For legacy (v1beta1) single-version CRDs, it will simply replace the global schema. \n For legacy (v1beta1) multi-version CRDs, and any v1 CRDs, it will replace schemata of existing versions and *clear the schema* from any versions not specified in the Go code. It will *not* add new versions, or remove old ones. \n For legacy multi-version CRDs with identical schemata, it will take care of lifting the per-version schema up to the global schema. \n It will generate output for each \"CRD Version\" (API version of the CRD type itself) , e.g. apiextensions/v1beta1 and apiextensions/v1) available.", + }, + FieldHelp: map[string]markers.DetailedHelp{ + "ManifestsPath": markers.DetailedHelp{ + Summary: "contains the CustomResourceDefinition YAML files.", + Details: "", + }, + "MaxDescLen": markers.DetailedHelp{ + Summary: "specifies the maximum description length for fields in CRD's OpenAPI schema. ", + Details: "0 indicates drop the description for all fields completely. n indicates limit the description to at most n characters and truncate the description to closest sentence boundary if it exceeds n characters.", + }, + }, + } +} diff --git a/vendor/sigs.k8s.io/controller-tools/pkg/webhook/zz_generated.markerhelp.go b/vendor/sigs.k8s.io/controller-tools/pkg/webhook/zz_generated.markerhelp.go index 788212d0a0d..81d9437f40b 100644 --- a/vendor/sigs.k8s.io/controller-tools/pkg/webhook/zz_generated.markerhelp.go +++ b/vendor/sigs.k8s.io/controller-tools/pkg/webhook/zz_generated.markerhelp.go @@ -34,7 +34,7 @@ func (Config) Help() *markers.DefinitionHelp { FieldHelp: map[string]markers.DetailedHelp{ "Mutating": markers.DetailedHelp{ Summary: "marks this as a mutating webhook (it's validating only if false) ", - Details: "Mutating webhooks are allowed to change the object in their response, and are called *after* all validating webhooks. Mutating webhooks may choose to reject an object, similarly to a validating webhook.", + Details: "Mutating webhooks are allowed to change the object in their response, and are called *before* all validating webhooks. Mutating webhooks may choose to reject an object, similarly to a validating webhook.", }, "FailurePolicy": markers.DetailedHelp{ Summary: "specifies what should happen if the API server cannot reach the webhook. ",