Skip to content

Commit 10dc26a

Browse files
dtfranzPer Goncalves da Silva
authored andcommitted
Bundle + CRD Upgrade Safety Tests (operator-framework#1084)
Adds unit test coverage to the bundleutil and crdupgradesafety packages. Also prunes some unused code. Signed-off-by: dtfranz <[email protected]>
1 parent 4b8a31c commit 10dc26a

File tree

10 files changed

+729
-100
lines changed

10 files changed

+729
-100
lines changed

internal/bundleutil/bundle_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package bundleutil_test
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/operator-framework/operator-registry/alpha/declcfg"
10+
"github.com/operator-framework/operator-registry/alpha/property"
11+
12+
"github.com/operator-framework/operator-controller/internal/bundleutil"
13+
)
14+
15+
func TestGetVersion(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
pkgProperty *property.Property
19+
wantErr bool
20+
}{
21+
{
22+
name: "valid version",
23+
pkgProperty: &property.Property{
24+
Type: property.TypePackage,
25+
Value: json.RawMessage(`{"version": "1.0.0"}`),
26+
},
27+
wantErr: false,
28+
},
29+
{
30+
name: "invalid version",
31+
pkgProperty: &property.Property{
32+
Type: property.TypePackage,
33+
Value: json.RawMessage(`{"version": "abcd"}`),
34+
},
35+
wantErr: true,
36+
},
37+
{
38+
name: "invalid json",
39+
pkgProperty: &property.Property{
40+
Type: property.TypePackage,
41+
Value: json.RawMessage(`abcd`),
42+
},
43+
wantErr: true,
44+
},
45+
{
46+
name: "no version property",
47+
pkgProperty: nil,
48+
wantErr: true,
49+
},
50+
}
51+
52+
for _, tc := range tests {
53+
t.Run(tc.name, func(t *testing.T) {
54+
properties := make([]property.Property, 0)
55+
if tc.pkgProperty != nil {
56+
properties = append(properties, *tc.pkgProperty)
57+
}
58+
59+
bundle := declcfg.Bundle{
60+
Name: "test-bundle",
61+
Properties: properties,
62+
}
63+
64+
_, err := bundleutil.GetVersion(bundle)
65+
if tc.wantErr {
66+
require.Error(t, err)
67+
} else {
68+
require.NoError(t, err)
69+
}
70+
})
71+
}
72+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package crdupgradesafety_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"strings"
8+
"testing"
9+
10+
kappcus "carvel.dev/kapp/pkg/kapp/crdupgradesafety"
11+
"github.com/stretchr/testify/require"
12+
"helm.sh/helm/v3/pkg/release"
13+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
14+
apiextensionsv1client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
18+
"github.com/operator-framework/operator-controller/internal/rukpak/preflights/crdupgradesafety"
19+
"github.com/operator-framework/operator-controller/internal/rukpak/util"
20+
)
21+
22+
type MockCRDGetter struct {
23+
oldCrd *apiextensionsv1.CustomResourceDefinition
24+
getErr error
25+
apiextensionsv1client.CustomResourceDefinitionInterface
26+
}
27+
28+
func (c *MockCRDGetter) Get(ctx context.Context, name string, options metav1.GetOptions) (*apiextensionsv1.CustomResourceDefinition, error) {
29+
return c.oldCrd, c.getErr
30+
}
31+
32+
func newMockPreflight(crd *apiextensionsv1.CustomResourceDefinition, err error, customValidator *kappcus.Validator) *crdupgradesafety.Preflight {
33+
var preflightOpts []crdupgradesafety.Option
34+
if customValidator != nil {
35+
preflightOpts = append(preflightOpts, crdupgradesafety.WithValidator(customValidator))
36+
}
37+
return crdupgradesafety.NewPreflight(&MockCRDGetter{
38+
oldCrd: crd,
39+
getErr: err,
40+
}, preflightOpts...)
41+
}
42+
43+
const crdFolder string = "../../../../testdata/manifests"
44+
45+
func getCrdFromManifestFile(t *testing.T, oldCrdFile string) *apiextensionsv1.CustomResourceDefinition {
46+
if oldCrdFile == "" {
47+
return nil
48+
}
49+
relObjects, err := util.ManifestObjects(strings.NewReader(getManifestString(t, oldCrdFile)), "old")
50+
require.NoError(t, err)
51+
52+
newCrd := &apiextensionsv1.CustomResourceDefinition{}
53+
for _, obj := range relObjects {
54+
if obj.GetObjectKind().GroupVersionKind() != apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition") {
55+
continue
56+
}
57+
uMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
58+
require.NoError(t, err)
59+
err = runtime.DefaultUnstructuredConverter.FromUnstructured(uMap, newCrd)
60+
require.NoError(t, err)
61+
}
62+
return newCrd
63+
}
64+
65+
func getManifestString(t *testing.T, crdFile string) string {
66+
buff, err := os.ReadFile(fmt.Sprintf("%s/%s", crdFolder, crdFile))
67+
require.NoError(t, err)
68+
return string(buff)
69+
}
70+
71+
// TestInstall exists only for completeness as Install() is currently a no-op. It can be used as
72+
// a template for real tests in the future if the func is implemented.
73+
func TestInstall(t *testing.T) {
74+
preflight := newMockPreflight(nil, nil, nil)
75+
require.Nil(t, preflight.Install(context.Background(), nil))
76+
}
77+
78+
func TestUpgrade(t *testing.T) {
79+
tests := []struct {
80+
name string
81+
oldCrdPath string
82+
validator *kappcus.Validator
83+
release *release.Release
84+
wantErrMsgs []string
85+
wantCrdGetErr error
86+
}{
87+
{
88+
name: "nil release",
89+
},
90+
{
91+
name: "release with no objects",
92+
release: &release.Release{
93+
Name: "test-release",
94+
},
95+
},
96+
{
97+
name: "release with invalid manifest",
98+
release: &release.Release{
99+
Name: "test-release",
100+
Manifest: "abcd",
101+
},
102+
wantErrMsgs: []string{"json: cannot unmarshal string into Go value of type unstructured.detector"},
103+
},
104+
{
105+
name: "release with no CRD objects",
106+
release: &release.Release{
107+
Name: "test-release",
108+
Manifest: getManifestString(t, "no-crds.json"),
109+
},
110+
},
111+
{
112+
name: "fail to get old crd",
113+
release: &release.Release{
114+
Name: "test-release",
115+
Manifest: getManifestString(t, "crd-valid-upgrade.json"),
116+
},
117+
wantCrdGetErr: fmt.Errorf("error!"),
118+
wantErrMsgs: []string{"error!"},
119+
},
120+
{
121+
name: "invalid crd manifest file",
122+
release: &release.Release{
123+
Name: "test-release",
124+
Manifest: getManifestString(t, "crd-invalid"),
125+
},
126+
wantErrMsgs: []string{"json: cannot unmarshal"},
127+
},
128+
{
129+
name: "custom validator",
130+
oldCrdPath: "old-crd.json",
131+
release: &release.Release{
132+
Name: "test-release",
133+
Manifest: getManifestString(t, "old-crd.json"),
134+
},
135+
validator: &kappcus.Validator{
136+
Validations: []kappcus.Validation{
137+
kappcus.NewValidationFunc("test", func(old, new apiextensionsv1.CustomResourceDefinition) error {
138+
return fmt.Errorf("custom validation error!!")
139+
}),
140+
},
141+
},
142+
wantErrMsgs: []string{"custom validation error!!"},
143+
},
144+
{
145+
name: "valid upgrade",
146+
oldCrdPath: "old-crd.json",
147+
release: &release.Release{
148+
Name: "test-release",
149+
Manifest: getManifestString(t, "crd-valid-upgrade.json"),
150+
},
151+
},
152+
{
153+
name: "new crd validation failures (all except existing field removal)",
154+
// Not really intended to test kapp validators, although it does anyway to a large extent.
155+
// This test is primarily meant to ensure that we are actually using all of them.
156+
oldCrdPath: "old-crd.json",
157+
release: &release.Release{
158+
Name: "test-release",
159+
Manifest: getManifestString(t, "crd-invalid-upgrade.json"),
160+
},
161+
wantErrMsgs: []string{
162+
`"NoScopeChange"`,
163+
`"NoStoredVersionRemoved"`,
164+
`enums added`,
165+
`new required fields added`,
166+
`maximum constraint added when one did not exist previously`,
167+
`maximum items constraint added`,
168+
`maximum length constraint added`,
169+
`maximum properties constraint added`,
170+
`minimum constraint added when one did not exist previously`,
171+
`minimum items constraint added`,
172+
`minimum length constraint added`,
173+
`minimum properties constraint added`,
174+
`new value added as default`,
175+
},
176+
},
177+
{
178+
name: "new crd validation failure for existing field removal",
179+
// Separate test from above as this error will cause the validator to
180+
// return early and skip some of the above validations.
181+
oldCrdPath: "old-crd.json",
182+
release: &release.Release{
183+
Name: "test-release",
184+
Manifest: getManifestString(t, "crd-field-removed.json"),
185+
},
186+
wantErrMsgs: []string{
187+
`"NoExistingFieldRemoved"`,
188+
},
189+
},
190+
}
191+
192+
for _, tc := range tests {
193+
t.Run(tc.name, func(t *testing.T) {
194+
preflight := newMockPreflight(getCrdFromManifestFile(t, tc.oldCrdPath), tc.wantCrdGetErr, tc.validator)
195+
err := preflight.Upgrade(context.Background(), tc.release)
196+
if len(tc.wantErrMsgs) != 0 {
197+
for _, expectedErrMsg := range tc.wantErrMsgs {
198+
require.ErrorContainsf(t, err, expectedErrMsg, "")
199+
}
200+
} else {
201+
require.NoError(t, err)
202+
}
203+
})
204+
}
205+
}

internal/rukpak/util/fs.go

Lines changed: 0 additions & 37 deletions
This file was deleted.

internal/rukpak/util/tar.go

Lines changed: 0 additions & 63 deletions
This file was deleted.

0 commit comments

Comments
 (0)