Skip to content

Commit a0e9c1d

Browse files
authored
Merge pull request #697 from pires/pires/delete_crds_on_stop
✨ Clean-up installed CRDs on Stop()
2 parents 9b41b21 + 2a965fc commit a0e9c1d

File tree

3 files changed

+176
-2
lines changed

3 files changed

+176
-2
lines changed

pkg/envtest/crd.go

+35
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727

2828
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
2929
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
30+
apierrors "k8s.io/apimachinery/pkg/api/errors"
31+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3032
"k8s.io/apimachinery/pkg/runtime/schema"
3133
"k8s.io/apimachinery/pkg/util/sets"
3234
"k8s.io/apimachinery/pkg/util/wait"
@@ -51,6 +53,11 @@ type CRDInstallOptions struct {
5153

5254
// PollInterval is the interval to check
5355
PollInterval time.Duration
56+
57+
// CleanUpAfterUse will cause the CRDs listed for installation to be
58+
// uninstalled when terminating the test environment.
59+
// Defaults to false.
60+
CleanUpAfterUse bool
5461
}
5562

5663
const defaultPollInterval = 100 * time.Millisecond
@@ -180,6 +187,34 @@ func (p *poller) poll() (done bool, err error) {
180187
return allFound, nil
181188
}
182189

190+
// UninstallCRDs uninstalls a collection of CRDs by reading the crd yaml files from a directory
191+
func UninstallCRDs(config *rest.Config, options CRDInstallOptions) error {
192+
193+
// Read the CRD yamls into options.CRDs
194+
if err := readCRDFiles(&options); err != nil {
195+
return err
196+
}
197+
198+
// Delete the CRDs from the apiserver
199+
cs, err := clientset.NewForConfig(config)
200+
if err != nil {
201+
return err
202+
}
203+
204+
// Uninstall each CRD
205+
for _, crd := range options.CRDs {
206+
log.V(1).Info("uninstalling CRD", "crd", crd.Name)
207+
if err := cs.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(crd.Name, &metav1.DeleteOptions{}); err != nil {
208+
// If CRD is not found, we can consider success
209+
if !apierrors.IsNotFound(err) {
210+
return err
211+
}
212+
}
213+
}
214+
215+
return nil
216+
}
217+
183218
// CreateCRDs creates the CRDs
184219
func CreateCRDs(config *rest.Config, crds []*apiextensionsv1beta1.CustomResourceDefinition) error {
185220
cs, err := clientset.NewForConfig(config)

pkg/envtest/envtest_test.go

+134-1
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ package envtest
1919
import (
2020
"context"
2121
"path/filepath"
22-
2322
"time"
2423

2524
. "github.com/onsi/ginkgo"
2625
. "github.com/onsi/gomega"
2726
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
27+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2828
"k8s.io/apimachinery/pkg/runtime"
2929
"k8s.io/apimachinery/pkg/types"
3030
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -52,6 +52,17 @@ var _ = Describe("Test", func() {
5252
// Cleanup CRDs
5353
AfterEach(func(done Done) {
5454
for _, crd := range crds {
55+
// Delete only if CRD exists.
56+
crdObjectKey := client.ObjectKey{
57+
Name: crd.Name,
58+
}
59+
var placeholder v1beta1.CustomResourceDefinition
60+
err := c.Get(context.TODO(), crdObjectKey, &placeholder)
61+
if err != nil && apierrors.IsNotFound(err) {
62+
// CRD doesn't need to be deleted.
63+
continue
64+
}
65+
Expect(err).NotTo(HaveOccurred())
5566
Expect(c.Delete(context.TODO(), crd)).To(Succeed())
5667
}
5768
close(done)
@@ -216,4 +227,126 @@ var _ = Describe("Test", func() {
216227
close(done)
217228
}, 5)
218229
})
230+
231+
Describe("UninstallCRDs", func() {
232+
It("should uninstall the CRDs from the cluster", func(done Done) {
233+
234+
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
235+
Paths: []string{filepath.Join(".", "testdata")},
236+
})
237+
Expect(err).NotTo(HaveOccurred())
238+
239+
// Expect to find the CRDs
240+
241+
crd := &v1beta1.CustomResourceDefinition{}
242+
err = c.Get(context.TODO(), types.NamespacedName{Name: "foos.bar.example.com"}, crd)
243+
Expect(err).NotTo(HaveOccurred())
244+
Expect(crd.Spec.Names.Kind).To(Equal("Foo"))
245+
246+
crd = &v1beta1.CustomResourceDefinition{}
247+
err = c.Get(context.TODO(), types.NamespacedName{Name: "bazs.qux.example.com"}, crd)
248+
Expect(err).NotTo(HaveOccurred())
249+
Expect(crd.Spec.Names.Kind).To(Equal("Baz"))
250+
251+
crd = &v1beta1.CustomResourceDefinition{}
252+
err = c.Get(context.TODO(), types.NamespacedName{Name: "captains.crew.example.com"}, crd)
253+
Expect(err).NotTo(HaveOccurred())
254+
Expect(crd.Spec.Names.Kind).To(Equal("Captain"))
255+
256+
crd = &v1beta1.CustomResourceDefinition{}
257+
err = c.Get(context.TODO(), types.NamespacedName{Name: "firstmates.crew.example.com"}, crd)
258+
Expect(err).NotTo(HaveOccurred())
259+
Expect(crd.Spec.Names.Kind).To(Equal("FirstMate"))
260+
261+
crd = &v1beta1.CustomResourceDefinition{}
262+
err = c.Get(context.TODO(), types.NamespacedName{Name: "drivers.crew.example.com"}, crd)
263+
Expect(err).NotTo(HaveOccurred())
264+
Expect(crd.Spec.Names.Kind).To(Equal("Driver"))
265+
266+
err = WaitForCRDs(env.Config, []*v1beta1.CustomResourceDefinition{
267+
{
268+
Spec: v1beta1.CustomResourceDefinitionSpec{
269+
Group: "qux.example.com",
270+
Version: "v1beta1",
271+
Names: v1beta1.CustomResourceDefinitionNames{
272+
Plural: "bazs",
273+
}},
274+
},
275+
{
276+
Spec: v1beta1.CustomResourceDefinitionSpec{
277+
Group: "bar.example.com",
278+
Version: "v1beta1",
279+
Names: v1beta1.CustomResourceDefinitionNames{
280+
Plural: "foos",
281+
}},
282+
},
283+
{
284+
Spec: v1beta1.CustomResourceDefinitionSpec{
285+
Group: "crew.example.com",
286+
Version: "v1beta1",
287+
Names: v1beta1.CustomResourceDefinitionNames{
288+
Plural: "captains",
289+
}},
290+
},
291+
{
292+
Spec: v1beta1.CustomResourceDefinitionSpec{
293+
Group: "crew.example.com",
294+
Version: "v1beta1",
295+
Names: v1beta1.CustomResourceDefinitionNames{
296+
Plural: "firstmates",
297+
}},
298+
},
299+
{
300+
Spec: v1beta1.CustomResourceDefinitionSpec{
301+
Group: "crew.example.com",
302+
Names: v1beta1.CustomResourceDefinitionNames{
303+
Plural: "drivers",
304+
},
305+
Versions: []v1beta1.CustomResourceDefinitionVersion{
306+
{
307+
Name: "v1",
308+
Storage: true,
309+
Served: true,
310+
},
311+
{
312+
Name: "v2",
313+
Storage: false,
314+
Served: true,
315+
},
316+
}},
317+
},
318+
},
319+
CRDInstallOptions{MaxTime: 50 * time.Millisecond, PollInterval: 15 * time.Millisecond},
320+
)
321+
Expect(err).NotTo(HaveOccurred())
322+
323+
err = UninstallCRDs(env.Config, CRDInstallOptions{
324+
Paths: []string{filepath.Join(".", "testdata")},
325+
})
326+
Expect(err).NotTo(HaveOccurred())
327+
328+
// Expect to NOT find the CRDs
329+
330+
crds := []string{
331+
"foos.bar.example.com",
332+
"bazs.qux.example.com",
333+
"captains.crew.example.com",
334+
"firstmates.crew.example.com",
335+
"drivers.crew.example.com",
336+
}
337+
placeholder := &v1beta1.CustomResourceDefinition{}
338+
Eventually(func() bool {
339+
for _, crd := range crds {
340+
err = c.Get(context.TODO(), types.NamespacedName{Name: crd}, placeholder)
341+
notFound := err != nil && apierrors.IsNotFound(err)
342+
if !notFound {
343+
return false
344+
}
345+
}
346+
return true
347+
}, 20).Should(BeTrue())
348+
349+
close(done)
350+
}, 30)
351+
})
219352
})

pkg/envtest/server.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,14 @@ type Environment struct {
135135
}
136136

137137
// Stop stops a running server.
138-
// If USE_EXISTING_CLUSTER is set to true, this method is a no-op.
138+
// Previously installed CRDs, as listed in CRDInstallOptions.CRDs, will be uninstalled
139+
// if CRDInstallOptions.CleanUpAfterUse are set to true.
139140
func (te *Environment) Stop() error {
141+
if te.CRDInstallOptions.CleanUpAfterUse {
142+
if err := UninstallCRDs(te.Config, te.CRDInstallOptions); err != nil {
143+
return err
144+
}
145+
}
140146
if te.useExistingCluster() {
141147
return nil
142148
}

0 commit comments

Comments
 (0)