Skip to content

Commit

Permalink
✨ crd: allow specifying spec.preserveUnknownFields (#912)
Browse files Browse the repository at this point in the history
* crd: allow specifying spec.preserveUnknownFields

Reinstate crd:preserveUnknownFields option removed by #607
to allow specifying the value of deprecated spec.preserveUnknownFields
CRD field.

This is useful for updating CRDs that were automatically converted from
v1beta1 version which had true as a default value and resulted in:
```
$ kubectl get crd foo.bar -o yaml | grep preserveUnknownFields
  preserveUnknownFields: true
    message: 'spec.preserveUnknownFields: Invalid value: true: must be false'
```

For #476

* crd: rename marker to DeprecatedV1beta1CompatibilityPreserveUnknownFields
  • Loading branch information
AlexanderYastrebov committed Apr 19, 2024
1 parent 8cc57e3 commit e159968
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 1 deletion.
34 changes: 33 additions & 1 deletion pkg/crd/gen.go
Expand Up @@ -85,6 +85,20 @@ type Generator struct {

// Year specifies the year to substitute for " YEAR" in the header file.
Year string `marker:",optional"`

// DeprecatedV1beta1CompatibilityPreserveUnknownFields indicates whether
// or not we should turn off field pruning for this resource.
//
// Specifies spec.preserveUnknownFields value that is false and omitted by default.
// This value can only be specified for CustomResourceDefinitions that were created with
// `apiextensions.k8s.io/v1beta1`.
//
// The field can be set for compatiblity reasons, although strongly discouraged, resource
// authors should move to a structural OpenAPI schema instead.
//
// See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning
// for more information about field pruning and v1beta1 resources compatibility.
DeprecatedV1beta1CompatibilityPreserveUnknownFields *bool `marker:",optional"`
}

func (Generator) CheckFilter() loader.NodeFilter {
Expand All @@ -100,6 +114,16 @@ func transformRemoveCRDStatus(obj map[string]interface{}) error {
return nil
}

// transformPreserveUnknownFields adds spec.preserveUnknownFields=value.
func transformPreserveUnknownFields(value bool) func(map[string]interface{}) error {
return func(obj map[string]interface{}) error {
if spec, ok := obj["spec"].(map[interface{}]interface{}); ok {
spec["preserveUnknownFields"] = value
}
return nil
}
}

func (g Generator) Generate(ctx *genall.GenerationContext) error {
parser := &Parser{
Collector: ctx.Collector,
Expand Down Expand Up @@ -146,6 +170,14 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
}
headerText = strings.ReplaceAll(headerText, " YEAR", " "+g.Year)

yamlOpts := []*genall.WriteYAMLOptions{
genall.WithTransform(transformRemoveCRDStatus),
genall.WithTransform(genall.TransformRemoveCreationTimestamp),
}
if g.DeprecatedV1beta1CompatibilityPreserveUnknownFields != nil {
yamlOpts = append(yamlOpts, genall.WithTransform(transformPreserveUnknownFields(*g.DeprecatedV1beta1CompatibilityPreserveUnknownFields)))
}

for _, groupKind := range kubeKinds {
parser.NeedCRDFor(groupKind, g.MaxDescLen)
crdRaw := parser.CustomResourceDefinitions[groupKind]
Expand All @@ -171,7 +203,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
} else {
fileName = fmt.Sprintf("%s_%s.%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural, crdVersions[i])
}
if err := ctx.WriteYAML(fileName, headerText, []interface{}{crd}, genall.WithTransform(transformRemoveCRDStatus), genall.WithTransform(genall.TransformRemoveCreationTimestamp)); err != nil {
if err := ctx.WriteYAML(fileName, headerText, []interface{}{crd}, yamlOpts...); err != nil {
return err
}
}
Expand Down
37 changes: 37 additions & 0 deletions pkg/crd/gen_integration_test.go
Expand Up @@ -119,6 +119,43 @@ var _ = Describe("CRD Generation proper defaulting", func() {
expectedOut := string(expectedFileFoos) + string(expectedFileZoos)
Expect(out.buf.String()).To(Equal(expectedOut), cmp.Diff(out.buf.String(), expectedOut))
})

It("should add preserveUnknownFields=false when specified", func() {
By("calling Generate")
no := false
gen := &crd.Generator{
CRDVersions: []string{"v1"},
DeprecatedV1beta1CompatibilityPreserveUnknownFields: &no,
}
Expect(gen.Generate(ctx)).NotTo(HaveOccurred())

By("searching preserveUnknownFields")
Expect(out.buf.String()).To(ContainSubstring("preserveUnknownFields: false"))
})

It("should add preserveUnknownFields=true when specified", func() {
By("calling Generate")
yes := true
gen := &crd.Generator{
CRDVersions: []string{"v1"},
DeprecatedV1beta1CompatibilityPreserveUnknownFields: &yes,
}
Expect(gen.Generate(ctx)).NotTo(HaveOccurred())

By("searching preserveUnknownFields")
Expect(out.buf.String()).To(ContainSubstring("preserveUnknownFields: true"))
})

It("should not add preserveUnknownFields when not specified", func() {
By("calling Generate")
gen := &crd.Generator{
CRDVersions: []string{"v1"},
}
Expect(gen.Generate(ctx)).NotTo(HaveOccurred())

By("searching preserveUnknownFields")
Expect(out.buf.String()).NotTo(ContainSubstring("preserveUnknownFields"))
})
})

// fixAnnotations fixes the attribution annotation for tests.
Expand Down

0 comments on commit e159968

Please sign in to comment.