Skip to content

Commit 7cd2923

Browse files
(fix): unhandle changes for crd upgrade safety
- Keep unhandled spec changes as errors; message: "unhandled changes found" Assisted-by: Cursor
1 parent c56a811 commit 7cd2923

File tree

6 files changed

+218
-4
lines changed

6 files changed

+218
-4
lines changed

internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@ func (p *Preflight) runPreflight(ctx context.Context, rel *release.Release) erro
108108
resultErrs := crdWideErrors(results)
109109
resultErrs = append(resultErrs, sameVersionErrors(results)...)
110110
resultErrs = append(resultErrs, servedVersionErrors(results)...)
111-
112-
validateErrors = append(validateErrors, fmt.Errorf("validating upgrade for CRD %q: %w", newCrd.Name, errors.Join(resultErrs...)))
111+
if len(resultErrs) > 0 {
112+
validateErrors = append(validateErrors, fmt.Errorf("validating upgrade for CRD %q: %w", newCrd.Name, errors.Join(resultErrs...)))
113+
}
113114
}
114115
}
115116

@@ -171,7 +172,11 @@ func sameVersionErrors(results *runner.Results) []error {
171172
for property, comparisonResults := range propertyResults {
172173
for _, result := range comparisonResults {
173174
for _, err := range result.Errors {
174-
errs = append(errs, fmt.Errorf("%s: %s: %s: %s", version, property, result.Name, err))
175+
msg := err
176+
if result.Name == "unhandled" {
177+
msg = "unhandled changes found"
178+
}
179+
errs = append(errs, fmt.Errorf("%s: %s: %s: %s", version, property, result.Name, msg))
175180
}
176181
}
177182
}
@@ -190,7 +195,11 @@ func servedVersionErrors(results *runner.Results) []error {
190195
for property, comparisonResults := range propertyResults {
191196
for _, result := range comparisonResults {
192197
for _, err := range result.Errors {
193-
errs = append(errs, fmt.Errorf("%s: %s: %s: %s", version, property, result.Name, err))
198+
msg := err
199+
if result.Name == "unhandled" {
200+
msg = "unhandled changes found"
201+
}
202+
errs = append(errs, fmt.Errorf("%s: %s: %s: %s", version, property, result.Name, msg))
194203
}
195204
}
196205
}

internal/operator-controller/rukpak/preflights/crdupgradesafety/crdupgradesafety_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1616
"k8s.io/apimachinery/pkg/runtime"
1717
"k8s.io/apimachinery/pkg/runtime/schema"
18+
crdifyconfig "sigs.k8s.io/crdify/pkg/config"
1819

1920
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety"
2021
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util"
@@ -379,3 +380,37 @@ func TestUpgrade(t *testing.T) {
379380
})
380381
}
381382
}
383+
384+
func TestUpgrade_UnhandledChanges_InSpec_DefaultPolicy(t *testing.T) {
385+
t.Run("unhandled spec changes cause error by default", func(t *testing.T) {
386+
preflight := newMockPreflight(getCrdFromManifestFile(t, "crd-unhandled-old.json"), nil)
387+
rel := &release.Release{
388+
Name: "test-release",
389+
Manifest: getManifestString(t, "crd-unhandled-new.json"),
390+
}
391+
err := preflight.Upgrade(context.Background(), rel)
392+
require.Error(t, err)
393+
require.ErrorContains(t, err, "unhandled changes found")
394+
require.NotContains(t, err.Error(), ".status.", "status changes should be ignored")
395+
require.NotContains(t, err.Error(), "v1.JSONSchemaProps", "error message should be concise without raw diff")
396+
})
397+
}
398+
399+
func TestUpgrade_UnhandledChanges_PolicyError(t *testing.T) {
400+
t.Run("unhandled changes error when policy is Error", func(t *testing.T) {
401+
oldCrd := getCrdFromManifestFile(t, "crd-unhandled-old.json")
402+
preflight := crdupgradesafety.NewPreflight(&MockCRDGetter{oldCrd: oldCrd}, crdupgradesafety.WithConfig(&crdifyconfig.Config{
403+
Conversion: crdifyconfig.ConversionPolicyIgnore,
404+
UnhandledEnforcement: crdifyconfig.EnforcementPolicyError,
405+
}))
406+
407+
rel := &release.Release{
408+
Name: "test-release",
409+
Manifest: getManifestString(t, "crd-unhandled-new.json"),
410+
}
411+
412+
err := preflight.Upgrade(context.Background(), rel)
413+
require.Error(t, err)
414+
require.ErrorContains(t, err, "unhandled changes found")
415+
})
416+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"apiVersion": "apiextensions.k8s.io/v1",
3+
"kind": "CustomResourceDefinition",
4+
"metadata": {"name": "apps.example.com"},
5+
"spec": {
6+
"group": "example.com",
7+
"versions": [
8+
{
9+
"name": "v1alpha1",
10+
"served": true,
11+
"storage": true,
12+
"schema": {
13+
"openAPIV3Schema": {
14+
"type": "object",
15+
"properties": {
16+
"spec": {
17+
"type": "object",
18+
"properties": {
19+
"name": {"type": "string"}
20+
}
21+
},
22+
"status": {
23+
"type": "object",
24+
"properties": {
25+
"resources": {
26+
"type": "array",
27+
"items": {
28+
"type": "object",
29+
"properties": {
30+
"status": {"type": "string"},
31+
"syncWave": {"type": "integer", "format": "int64"}
32+
}
33+
}
34+
}
35+
}
36+
}
37+
}
38+
}
39+
}
40+
}
41+
],
42+
"scope": "Namespaced",
43+
"names": {"plural": "apps", "singular": "app", "kind": "App"}
44+
}
45+
}
46+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"apiVersion": "apiextensions.k8s.io/v1",
3+
"kind": "CustomResourceDefinition",
4+
"metadata": {"name": "apps.example.com"},
5+
"spec": {
6+
"group": "example.com",
7+
"versions": [
8+
{
9+
"name": "v1alpha1",
10+
"served": true,
11+
"storage": true,
12+
"schema": {
13+
"openAPIV3Schema": {
14+
"type": "object",
15+
"properties": {
16+
"spec": {
17+
"type": "object",
18+
"properties": {
19+
"name": {"type": "string"}
20+
}
21+
},
22+
"status": {
23+
"type": "object",
24+
"properties": {
25+
"resources": {
26+
"type": "array",
27+
"items": {
28+
"type": "object",
29+
"properties": {
30+
"status": {"type": "string"}
31+
}
32+
}
33+
}
34+
}
35+
}
36+
}
37+
}
38+
}
39+
}
40+
],
41+
"scope": "Namespaced",
42+
"names": {"plural": "apps", "singular": "app", "kind": "App"}
43+
}
44+
}
45+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"apiVersion": "apiextensions.k8s.io/v1",
3+
"kind": "CustomResourceDefinition",
4+
"metadata": {
5+
"name": "widgets.example.com"
6+
},
7+
"spec": {
8+
"group": "example.com",
9+
"versions": [
10+
{
11+
"name": "v1alpha1",
12+
"served": true,
13+
"storage": true,
14+
"schema": {
15+
"openAPIV3Schema": {
16+
"type": "object",
17+
"properties": {
18+
"spec": {
19+
"type": "object",
20+
"properties": {
21+
"field": {
22+
"type": "string",
23+
"format": "email"
24+
}
25+
}
26+
}
27+
}
28+
}
29+
}
30+
}
31+
],
32+
"scope": "Namespaced",
33+
"names": {
34+
"plural": "widgets",
35+
"singular": "widget",
36+
"kind": "Widget"
37+
}
38+
}
39+
}
40+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"apiVersion": "apiextensions.k8s.io/v1",
3+
"kind": "CustomResourceDefinition",
4+
"metadata": {
5+
"name": "widgets.example.com"
6+
},
7+
"spec": {
8+
"group": "example.com",
9+
"versions": [
10+
{
11+
"name": "v1alpha1",
12+
"served": true,
13+
"storage": true,
14+
"schema": {
15+
"openAPIV3Schema": {
16+
"type": "object",
17+
"properties": {
18+
"spec": {
19+
"type": "object",
20+
"properties": {
21+
"field": {
22+
"type": "string"
23+
}
24+
}
25+
}
26+
}
27+
}
28+
}
29+
}
30+
],
31+
"scope": "Namespaced",
32+
"names": {
33+
"plural": "widgets",
34+
"singular": "widget",
35+
"kind": "Widget"
36+
}
37+
}
38+
}
39+

0 commit comments

Comments
 (0)