From 868776ff5b1b53cc18f02057567c86c0b2b7b66a Mon Sep 17 00:00:00 2001 From: everettraven Date: Mon, 29 Apr 2024 15:42:36 -0400 Subject: [PATCH] add validation for changes to minimum constraints for CRD fields Signed-off-by: everettraven --- go.mod | 2 +- pkg/kapp/crdupgradesafety/change_validator.go | 92 +++++ .../crdupgradesafety/change_validator_test.go | 357 ++++++++++++++++++ pkg/kapp/crdupgradesafety/preflight.go | 4 + ...invalid_field_change_minimum_added_test.go | 115 ++++++ ...lid_field_change_minimum_increased_test.go | 116 ++++++ ...nvalid_field_change_minitems_added_test.go | 119 ++++++ ...id_field_change_minitems_increased_test.go | 120 ++++++ ...valid_field_change_minlength_added_test.go | 115 ++++++ ...d_field_change_minlength_increased_test.go | 116 ++++++ ...d_field_change_minproperties_added_test.go | 115 ++++++ ...eld_change_minproperties_increased_test.go | 116 ++++++ ...lid_field_change_minimum_decreased_test.go | 115 ++++++ ...id_field_change_minitems_decreased_test.go | 119 ++++++ ...d_field_change_minlength_decreased_test.go | 115 ++++++ ...eld_change_minproperties_decreased_test.go | 115 ++++++ 16 files changed, 1850 insertions(+), 1 deletion(-) create mode 100644 test/e2e/preflight_crdupgradesafety_invalid_field_change_minimum_added_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_invalid_field_change_minimum_increased_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_invalid_field_change_minitems_added_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_invalid_field_change_minitems_increased_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_invalid_field_change_minlength_added_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_invalid_field_change_minlength_increased_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_invalid_field_change_minproperties_added_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_invalid_field_change_minproperties_increased_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_valid_field_change_minimum_decreased_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_valid_field_change_minitems_decreased_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_valid_field_change_minlength_decreased_test.go create mode 100644 test/e2e/preflight_crdupgradesafety_valid_field_change_minproperties_decreased_test.go diff --git a/go.mod b/go.mod index b6b3dc26b..bb0376bc6 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( k8s.io/apimachinery v0.29.3 k8s.io/client-go v0.29.3 k8s.io/component-helpers v0.29.3 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/yaml v1.4.0 ) @@ -65,7 +66,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/pkg/kapp/crdupgradesafety/change_validator.go b/pkg/kapp/crdupgradesafety/change_validator.go index 4ae3ac936..d3c88606d 100644 --- a/pkg/kapp/crdupgradesafety/change_validator.go +++ b/pkg/kapp/crdupgradesafety/change_validator.go @@ -71,6 +71,98 @@ func EnumChangeValidation(diff FieldDiff) (bool, error) { return handled(), nil } +func MinimumChangeValidation(diff FieldDiff) (bool, error) { + handled := func() bool { + diff.Old.Minimum = nil + diff.New.Minimum = nil + return reflect.DeepEqual(diff.Old, diff.New) + } + + switch { + case diff.Old.Minimum == nil && diff.New.Minimum != nil: + m := *diff.New.Minimum + return handled(), fmt.Errorf("minimum constraint added when one did not exist previously: %+v", m) + case diff.Old.Minimum != nil && diff.New.Minimum != nil: + oldMin := *diff.Old.Minimum + newMin := *diff.New.Minimum + if oldMin < newMin { + return handled(), fmt.Errorf("minimum constraint increased from %+v to %+v", oldMin, newMin) + } + fallthrough + default: + return handled(), nil + } +} + +func MinimumLengthChangeValidation(diff FieldDiff) (bool, error) { + handled := func() bool { + diff.Old.MinLength = nil + diff.New.MinLength = nil + return reflect.DeepEqual(diff.Old, diff.New) + } + + switch { + case diff.Old.MinLength == nil && diff.New.MinLength != nil: + m := *diff.New.MinLength + return handled(), fmt.Errorf("minimum length constraint added when one did not exist previously: %+v", m) + case diff.Old.MinLength != nil && diff.New.MinLength != nil: + oldMin := *diff.Old.MinLength + newMin := *diff.New.MinLength + if oldMin < newMin { + return handled(), fmt.Errorf("minimum length constraint increased from %+v to %+v", oldMin, newMin) + } + fallthrough + default: + return handled(), nil + } +} + +func MinimumItemsChangeValidation(diff FieldDiff) (bool, error) { + handled := func() bool { + diff.Old.MinItems = nil + diff.New.MinItems = nil + return reflect.DeepEqual(diff.Old, diff.New) + } + + switch { + case diff.Old.MinItems == nil && diff.New.MinItems != nil: + m := *diff.New.MinItems + return handled(), fmt.Errorf("minimum items constraint added when one did not exist previously: %+v", m) + case diff.Old.MinItems != nil && diff.New.MinItems != nil: + oldMin := *diff.Old.MinItems + newMin := *diff.New.MinItems + if oldMin < newMin { + return handled(), fmt.Errorf("minimum items constraint increased from %+v to %+v", oldMin, newMin) + } + fallthrough + default: + return handled(), nil + } +} + +func MinimumPropertiesChangeValidation(diff FieldDiff) (bool, error) { + handled := func() bool { + diff.Old.MinProperties = nil + diff.New.MinProperties = nil + return reflect.DeepEqual(diff.Old, diff.New) + } + + switch { + case diff.Old.MinProperties == nil && diff.New.MinProperties != nil: + m := *diff.New.MinProperties + return handled(), fmt.Errorf("minimum properties constraint added when one did not exist previously: %+v", m) + case diff.Old.MinProperties != nil && diff.New.MinProperties != nil: + oldMin := *diff.Old.MinProperties + newMin := *diff.New.MinProperties + if oldMin < newMin { + return handled(), fmt.Errorf("minimum properties constraint increased from %+v to %+v", oldMin, newMin) + } + fallthrough + default: + return handled(), nil + } +} + // ChangeValidator is a Validation implementation focused on // handling updates to existing fields in a CRD type ChangeValidator struct { diff --git a/pkg/kapp/crdupgradesafety/change_validator_test.go b/pkg/kapp/crdupgradesafety/change_validator_test.go index d979b0f81..0e513e9dd 100644 --- a/pkg/kapp/crdupgradesafety/change_validator_test.go +++ b/pkg/kapp/crdupgradesafety/change_validator_test.go @@ -10,6 +10,7 @@ import ( "carvel.dev/kapp/pkg/kapp/crdupgradesafety" "github.com/stretchr/testify/assert" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/utils/pointer" ) func TestEnumChangeValidation(t *testing.T) { @@ -429,3 +430,359 @@ func TestChangeValidator(t *testing.T) { }) } } + +func TestMinimumChangeValidation(t *testing.T) { + for _, tc := range []struct { + name string + diff crdupgradesafety.FieldDiff + shouldError bool + shouldHandle bool + }{ + { + name: "no change, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(10), + }, + New: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(10), + }, + }, + shouldHandle: true, + }, + { + name: "minimum decreased, no other changes, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(10), + }, + New: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(8), + }, + }, + shouldHandle: true, + }, + { + name: "minimum increased, no other changes, error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(8), + }, + New: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(10), + }, + }, + shouldHandle: true, + shouldError: true, + }, + { + name: "no minimum before, minimum added, no other changes, error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{}, + New: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(8), + }, + }, + shouldHandle: true, + shouldError: true, + }, + { + name: "minimum removed, no other changes, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(8), + }, + New: &v1.JSONSchemaProps{}, + }, + shouldHandle: true, + }, + { + name: "no minimum change, other changes, no error, not marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(8), + ID: "bar", + }, + New: &v1.JSONSchemaProps{ + Minimum: pointer.Float64(8), + ID: "baz", + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + handled, err := crdupgradesafety.MinimumChangeValidation(tc.diff) + assert.Equal(t, tc.shouldError, err != nil, "should error? - %v", tc.shouldError) + assert.Equal(t, tc.shouldHandle, handled, "should be handled? - %v", tc.shouldHandle) + assert.Empty(t, tc.diff.Old.Minimum) + assert.Empty(t, tc.diff.New.Minimum) + }) + } +} + +func TestMinimumLengthChangeValidation(t *testing.T) { + for _, tc := range []struct { + name string + diff crdupgradesafety.FieldDiff + shouldError bool + shouldHandle bool + }{ + { + name: "no change, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(10), + }, + }, + shouldHandle: true, + }, + { + name: "minimum length decreased, no other changes, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(8), + }, + }, + shouldHandle: true, + }, + { + name: "minimum length increased, no other changes, error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(8), + }, + New: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(10), + }, + }, + shouldHandle: true, + shouldError: true, + }, + { + name: "no minimum length before, minimum length added, no other changes, error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{}, + New: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(10), + }, + }, + shouldHandle: true, + shouldError: true, + }, + { + name: "minimum length removed, no other changes, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{}, + }, + shouldHandle: true, + }, + { + name: "no minimum length change, other changes, no error, not marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(10), + ID: "bar", + }, + New: &v1.JSONSchemaProps{ + MinLength: pointer.Int64(10), + ID: "baz", + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + handled, err := crdupgradesafety.MinimumLengthChangeValidation(tc.diff) + assert.Equal(t, tc.shouldError, err != nil, "should error? - %v", tc.shouldError) + assert.Equal(t, tc.shouldHandle, handled, "should be handled? - %v", tc.shouldHandle) + assert.Empty(t, tc.diff.Old.MinLength) + assert.Empty(t, tc.diff.New.MinLength) + }) + } +} + +func TestMinimumItemsChangeValidation(t *testing.T) { + for _, tc := range []struct { + name string + diff crdupgradesafety.FieldDiff + shouldError bool + shouldHandle bool + }{ + { + name: "no change, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(10), + }, + }, + shouldHandle: true, + }, + { + name: "minimum items decreased, no other changes, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(8), + }, + }, + shouldHandle: true, + }, + { + name: "minimum items increased, no other changes, error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(8), + }, + New: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(10), + }, + }, + shouldHandle: true, + shouldError: true, + }, + { + name: "no minimum items before, minimum items added, no other changes, error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{}, + New: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(10), + }, + }, + shouldHandle: true, + shouldError: true, + }, + { + name: "minimum items removed, no other changes, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{}, + }, + shouldHandle: true, + }, + { + name: "no minimum items change, other changes, no error, not marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(10), + ID: "bar", + }, + New: &v1.JSONSchemaProps{ + MinItems: pointer.Int64(10), + ID: "baz", + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + handled, err := crdupgradesafety.MinimumItemsChangeValidation(tc.diff) + assert.Equal(t, tc.shouldError, err != nil, "should error? - %v", tc.shouldError) + assert.Equal(t, tc.shouldHandle, handled, "should be handled? - %v", tc.shouldHandle) + assert.Empty(t, tc.diff.Old.MinItems) + assert.Empty(t, tc.diff.New.MinItems) + }) + } +} + +func TestMinimumPropertiesChangeValidation(t *testing.T) { + for _, tc := range []struct { + name string + diff crdupgradesafety.FieldDiff + shouldError bool + shouldHandle bool + }{ + { + name: "no change, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(10), + }, + }, + shouldHandle: true, + }, + { + name: "minimum properties decreased, no other changes, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(8), + }, + }, + shouldHandle: true, + }, + { + name: "minimum properties increased, no other changes, error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(8), + }, + New: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(10), + }, + }, + shouldHandle: true, + shouldError: true, + }, + { + name: "no minimum properties before, minimum properties added, no other changes, error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{}, + New: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(10), + }, + }, + shouldHandle: true, + shouldError: true, + }, + { + name: "minimum properties removed, no other changes, no error, marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(10), + }, + New: &v1.JSONSchemaProps{}, + }, + shouldHandle: true, + }, + { + name: "no minimum properties change, other changes, no error, not marked as handled", + diff: crdupgradesafety.FieldDiff{ + Old: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(10), + ID: "bar", + }, + New: &v1.JSONSchemaProps{ + MinProperties: pointer.Int64(10), + ID: "baz", + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + handled, err := crdupgradesafety.MinimumPropertiesChangeValidation(tc.diff) + assert.Equal(t, tc.shouldError, err != nil, "should error? - %v", tc.shouldError) + assert.Equal(t, tc.shouldHandle, handled, "should be handled? - %v", tc.shouldHandle) + assert.Empty(t, tc.diff.Old.MinProperties) + assert.Empty(t, tc.diff.New.MinProperties) + }) + } +} diff --git a/pkg/kapp/crdupgradesafety/preflight.go b/pkg/kapp/crdupgradesafety/preflight.go index cd3e661cc..95e01512d 100644 --- a/pkg/kapp/crdupgradesafety/preflight.go +++ b/pkg/kapp/crdupgradesafety/preflight.go @@ -40,6 +40,10 @@ func NewPreflight(df cmdcore.DepsFactory, enabled bool) *Preflight { &ChangeValidator{ Validations: []ChangeValidation{ EnumChangeValidation, + MinimumChangeValidation, + MinimumItemsChangeValidation, + MinimumLengthChangeValidation, + MinimumPropertiesChangeValidation, }, }, }, diff --git a/test/e2e/preflight_crdupgradesafety_invalid_field_change_minimum_added_test.go b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minimum_added_test.go new file mode 100644 index 000000000..f95b31b76 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minimum_added_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyInvalidFieldChangeMinimumAdded(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyinvalidfieldchangeminimumadded" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: integer + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minimum: 10 + type: integer + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that adds minimum constraint for existing field, preflight check enabled, should error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update), AllowError: true}) + require.Error(t, err) + require.Contains(t, err.Error(), "minimum constraint added when one did not exist previously") + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_invalid_field_change_minimum_increased_test.go b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minimum_increased_test.go new file mode 100644 index 000000000..73876c2f9 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minimum_increased_test.go @@ -0,0 +1,116 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyInvalidFieldChangeMinimumIncreased(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyinvalidfieldchangeminimumincreased" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minimum: 5 + type: integer + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minimum: 10 + type: integer + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that increases minimum constraint for existing field, preflight check enabled, should error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update), AllowError: true}) + require.Error(t, err) + require.Contains(t, err.Error(), "minimum constraint increased") + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_invalid_field_change_minitems_added_test.go b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minitems_added_test.go new file mode 100644 index 000000000..7f094904b --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minitems_added_test.go @@ -0,0 +1,119 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyInvalidFieldChangeMinItemsAdded(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyinvalidfieldchangeminitemsadded" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + items: + type: object + type: array + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minItems: 10 + items: + type: object + type: array + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that adds minimum items constraint for existing field, preflight check enabled, should error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update), AllowError: true}) + require.Error(t, err) + require.Contains(t, err.Error(), "minimum items constraint added when one did not exist previously") + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_invalid_field_change_minitems_increased_test.go b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minitems_increased_test.go new file mode 100644 index 000000000..755b36dc3 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minitems_increased_test.go @@ -0,0 +1,120 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyInvalidFieldChangeMinItemsIncreased(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyinvalidfieldchangeminitemsincreased" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minItems: 5 + items: + type: object + type: array + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minItems: 10 + items: + type: object + type: array + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that increases minimum items constraint for existing field, preflight check enabled, should error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update), AllowError: true}) + require.Error(t, err) + require.Contains(t, err.Error(), "minimum items constraint increased") + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_invalid_field_change_minlength_added_test.go b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minlength_added_test.go new file mode 100644 index 000000000..ba75bfc21 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minlength_added_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyInvalidFieldChangeMinLengthAdded(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyinvalidfieldchangeminlengthadded" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: string + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minLength: 10 + type: string + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that adds minimum length constraint for existing field, preflight check enabled, should error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update), AllowError: true}) + require.Error(t, err) + require.Contains(t, err.Error(), "minimum length constraint added when one did not exist previously") + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_invalid_field_change_minlength_increased_test.go b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minlength_increased_test.go new file mode 100644 index 000000000..bf9299dea --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minlength_increased_test.go @@ -0,0 +1,116 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyInvalidFieldChangeMinLengthIncreased(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyinvalidfieldchangeminlengthincreased" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minLength: 5 + type: string + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minLength: 10 + type: string + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that increases minimum length constraint for existing field, preflight check enabled, should error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update), AllowError: true}) + require.Error(t, err) + require.Contains(t, err.Error(), "minimum length constraint increased") + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_invalid_field_change_minproperties_added_test.go b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minproperties_added_test.go new file mode 100644 index 000000000..622ae926c --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minproperties_added_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyInvalidFieldChangeMinPropertiesAdded(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyinvalidfieldchangeminpropertiesadded" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minProperties: 10 + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that adds minimum properties constraint for existing field, preflight check enabled, should error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update), AllowError: true}) + require.Error(t, err) + require.Contains(t, err.Error(), "minimum properties constraint added when one did not exist previously") + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_invalid_field_change_minproperties_increased_test.go b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minproperties_increased_test.go new file mode 100644 index 000000000..74204b029 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_invalid_field_change_minproperties_increased_test.go @@ -0,0 +1,116 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyInvalidFieldChangeMinPropertiesIncreased(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyinvalidfieldchangeminpropertiesincreased" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minProperties: 5 + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minProperties: 10 + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that increases minimum properties constraint for existing field, preflight check enabled, should error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update), AllowError: true}) + require.Error(t, err) + require.Contains(t, err.Error(), "minimum properties constraint increased") + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_valid_field_change_minimum_decreased_test.go b/test/e2e/preflight_crdupgradesafety_valid_field_change_minimum_decreased_test.go new file mode 100644 index 000000000..77cc48b68 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_valid_field_change_minimum_decreased_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyValidFieldChangeMinimumDecreased(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyvalidfieldchangeminimumdecreased" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minimum: 10 + type: integer + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minimum: 5 + type: integer + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that decreases minimum constraint for existing field, preflight check enabled, should not error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update)}) + require.NoError(t, err) + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_valid_field_change_minitems_decreased_test.go b/test/e2e/preflight_crdupgradesafety_valid_field_change_minitems_decreased_test.go new file mode 100644 index 000000000..9fa143521 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_valid_field_change_minitems_decreased_test.go @@ -0,0 +1,119 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyValidFieldChangeMinItemsDecreased(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyvalidfieldchangeminitemsdecreased" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minItems: 10 + items: + type: object + type: array + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minItems: 5 + items: + type: object + type: array + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that decreases minimum items constraint for existing field, preflight check enabled, should not error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update)}) + require.NoError(t, err) + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_valid_field_change_minlength_decreased_test.go b/test/e2e/preflight_crdupgradesafety_valid_field_change_minlength_decreased_test.go new file mode 100644 index 000000000..2ac39f662 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_valid_field_change_minlength_decreased_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyValidFieldChangeMinLengthDecreased(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyvalidfieldchangeminlengthdecreased" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minLength: 10 + type: string + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minLength: 5 + type: string + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that decreases minimum length constraint for existing field, preflight check enabled, should not error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update)}) + require.NoError(t, err) + }) +} diff --git a/test/e2e/preflight_crdupgradesafety_valid_field_change_minproperties_decreased_test.go b/test/e2e/preflight_crdupgradesafety_valid_field_change_minproperties_decreased_test.go new file mode 100644 index 000000000..3d177d7a0 --- /dev/null +++ b/test/e2e/preflight_crdupgradesafety_valid_field_change_minproperties_decreased_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 The Carvel Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPreflightCRDUpgradeSafetyValidFieldChangeMinPropertiesDecreased(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + testName := "preflightcrdupgradesafetyvalidfieldchangeminpropertiesdecreased" + + base := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minProperties: 10 + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + base = strings.ReplaceAll(base, "__test-name__", testName) + appName := "preflight-crdupgradesafety-app" + + cleanUp := func() { + kapp.Run([]string{"delete", "-a", appName}) + } + cleanUp() + defer cleanUp() + + update := ` +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: memcacheds.__test-name__.example.com +spec: + group: __test-name__.example.com + names: + kind: Memcached + listKind: MemcachedList + plural: memcacheds + singular: memcached + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + minProperties: 5 + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} +` + + update = strings.ReplaceAll(update, "__test-name__", testName) + logger.Section("deploy app with CRD update that decreases minimum properties constraint for existing field, preflight check enabled, should not error", func() { + _, err := kapp.RunWithOpts([]string{"deploy", "-a", appName, "-f", "-"}, RunOpts{StdinReader: strings.NewReader(base)}) + require.NoError(t, err) + _, err = kapp.RunWithOpts([]string{"deploy", "--preflight=CRDUpgradeSafety", "-a", appName, "-f", "-"}, + RunOpts{StdinReader: strings.NewReader(update)}) + require.NoError(t, err) + }) +}