Skip to content
This repository was archived by the owner on Jun 13, 2023. It is now read-only.

Commit a986799

Browse files
authored
Make sure module crs get deleted before module operator (#195)
* ignore helmClient not found error use same pattern for RemoveCRs RemoveCRDs * don't delete namespace while installation * remove error nil check * rename noResourceFound to uninstallSuccess write test for this function * fix test * change testEnv.stop to use eventually to check repeatedly. * refactor test to pass lint check
1 parent 013cd85 commit a986799

File tree

6 files changed

+120
-49
lines changed

6 files changed

+120
-49
lines changed

controllers/suite_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,7 @@ var _ = AfterSuite(func() {
165165
cancel()
166166
By("tearing down the test environment")
167167
server.Close()
168-
err := testEnv.Stop()
169-
Expect(err).NotTo(HaveOccurred())
170-
err = os.RemoveAll(helmCacheHome)
168+
Eventually(func() error { return testEnv.Stop() }, standardTimeout, standardInterval).Should(Succeed())
169+
err := os.RemoveAll(helmCacheHome)
171170
Expect(err).NotTo(HaveOccurred())
172171
})

pkg/manifest/helm_processor.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"reflect"
9+
"strings"
910
"sync"
1011
"time"
1112

@@ -168,7 +169,7 @@ func (h *helm) Uninstall(stringifedManifest string, info *types.InstallInfo, tra
168169

169170
// uninstall resources
170171
_, err = h.uninstallResources(resourceLists)
171-
if err != nil {
172+
if IgnoreHelmClientNotFound(err) != nil {
172173
return false, err
173174
}
174175

@@ -218,6 +219,14 @@ func (h *helm) Uninstall(stringifedManifest string, info *types.InstallInfo, tra
218219
return true, nil
219220
}
220221

222+
func IgnoreHelmClientNotFound(err error) error {
223+
// Refactoring this error check after this PR get merged and released https://github.com/helm/helm/pull/11591
224+
if err != nil && strings.Contains(err.Error(), "object not found, skipping delete") {
225+
return nil
226+
}
227+
return err
228+
}
229+
221230
// uninstallChartCRDs uses a types.InstallInfo to lookup a chart and then tries to remove all CRDs found within it
222231
// and its dependencies. It can be used to forcefully remove all CRDs from a chart when CRDs were not included in the
223232
// render. Unlike our optimized installation, this one is built sequentially as we do not need the performance
@@ -343,8 +352,7 @@ func (h *helm) uninstallResources(resourceLists types.ResourceLists) (*kube.Resu
343352
var response *kube.Result
344353
var delErrors []error
345354
if resourceLists.Installed != nil {
346-
// add namespace to deleted resources
347-
response, delErrors = h.clients.KubeClient().Delete(resourceLists.GetResourcesToBeDeleted())
355+
response, delErrors = h.clients.KubeClient().Delete(resourceLists.Installed)
348356
if len(delErrors) > 0 {
349357
var wrappedError error
350358
for _, err := range delErrors {

pkg/manifest/operations.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/go-logr/logr"
88
"helm.sh/helm/v3/pkg/cli"
99
apierrors "k8s.io/apimachinery/pkg/api/errors"
10-
"k8s.io/apimachinery/pkg/api/meta"
1110
"sigs.k8s.io/controller-runtime/pkg/client"
1211

1312
manifestClient "github.com/kyma-project/module-manager/pkg/client"
@@ -16,6 +15,7 @@ import (
1615
"github.com/kyma-project/module-manager/pkg/resource"
1716
"github.com/kyma-project/module-manager/pkg/types"
1817
"github.com/kyma-project/module-manager/pkg/util"
18+
"k8s.io/apimachinery/pkg/api/meta"
1919
)
2020

2121
type Operations struct {
@@ -35,6 +35,11 @@ type OperationOptions struct {
3535
Cache types.RendererCache
3636
}
3737

38+
var (
39+
ErrCRsNotRemoved = errors.New("CustomResources not completely removed")
40+
ErrCRDsNotRemoved = errors.New("CRDs not completely removed")
41+
ErrUninstallInconsistent = errors.New("uninstallation inconsistent")
42+
)
3843
// InstallChart installs the resources based on types.InstallInfo and an appropriate rendering mechanism.
3944
func InstallChart(options OperationOptions) (bool, error) {
4045
ops, err := NewOperations(options)
@@ -273,9 +278,9 @@ func (o *Operations) uninstall() (bool, error) {
273278
// delete crs first - proceed only if not found
274279
// proceed if CR type doesn't exist anymore - since associated CRDs might be deleted from resource uninstallation
275280
// since there might be a deletion process to be completed by other manifest resources
276-
deleted, err := resource.RemoveCRs(o.installInfo.Ctx, o.installInfo.CustomResources, o.client)
277-
if !meta.IsNoMatchError(err) && (err != nil || !deleted) {
278-
return false, err
281+
crDeleted := resource.RemoveCRs(o.installInfo.Ctx, o.installInfo.CustomResources, o.client)
282+
if !crDeleted {
283+
return false, ErrCRsNotRemoved
279284
}
280285

281286
// process manifest
@@ -291,14 +296,18 @@ func (o *Operations) uninstall() (bool, error) {
291296
// uninstall resources
292297
consistent, err := o.renderSrc.Uninstall(parsedFile.GetContent(),
293298
o.installInfo, o.resourceTransforms, o.postRuns)
294-
if err != nil || !consistent {
299+
if !consistent {
300+
return false, ErrUninstallInconsistent
301+
}
302+
if !UninstallSuccess(err) {
295303
return false, err
296304
}
297305

298306
// delete crds last - if not present ignore!
299-
if err := resource.RemoveCRDs(o.installInfo.Ctx, o.installInfo.Crds, o.client); err != nil {
300-
return false, err
301-
}
307+
crdDeleted := resource.RemoveCRDs(o.installInfo.Ctx, o.installInfo.Crds, o.client)
308+
if !crdDeleted {
309+
return false, ErrCRDsNotRemoved
310+
}
302311

303312
// custom states check
304313
if o.installInfo.CheckFn != nil {
@@ -307,6 +316,10 @@ func (o *Operations) uninstall() (bool, error) {
307316
return true, err
308317
}
309318

319+
func UninstallSuccess(err error) bool {
320+
return err == nil || apierrors.IsNotFound(err) || meta.IsNoMatchError(err)
321+
}
322+
310323
func (o *Operations) getManifestForChartPath(installInfo *types.InstallInfo) *types.ParsedFile {
311324
// 1. check provided manifest file
312325
// It is expected for installInfo.ChartPath to contain ONE .yaml or .yml file,

pkg/manifest/operations_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package manifest_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/kyma-project/module-manager/pkg/manifest"
8+
apierrors "k8s.io/apimachinery/pkg/api/errors"
9+
apimeta "k8s.io/apimachinery/pkg/api/meta"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
)
12+
13+
func Test_UninstallSuccess(t *testing.T) {
14+
t.Parallel()
15+
type args struct {
16+
err error
17+
}
18+
tests := []struct {
19+
name string
20+
args args
21+
want bool
22+
}{
23+
{
24+
name: "when no error, expect uninstall successfully",
25+
args: args{err: nil},
26+
want: true,
27+
},
28+
{
29+
name: "when api not found error, expect uninstall successfully",
30+
args: args{err: &apierrors.StatusError{ErrStatus: metav1.Status{
31+
Status: metav1.StatusFailure,
32+
Reason: metav1.StatusReasonNotFound,
33+
}}},
34+
want: true,
35+
},
36+
{
37+
name: "when api no kind match error, expect uninstall successfully",
38+
args: args{err: &apimeta.NoKindMatchError{}},
39+
want: true,
40+
},
41+
{
42+
name: "when api no resource match error, expect uninstall successfully",
43+
args: args{err: &apimeta.NoResourceMatchError{}},
44+
want: true,
45+
},
46+
{
47+
name: "when receive normal error, expect uninstall successfully",
48+
args: args{err: errors.New("some unexpected error")},
49+
want: false,
50+
},
51+
}
52+
for _, tt := range tests {
53+
tt := tt
54+
t.Run(tt.name, func(t *testing.T) {
55+
t.Parallel()
56+
if got := manifest.UninstallSuccess(tt.args.err); got != tt.want {
57+
t.Errorf("UninstallSuccess() = %v, want %v", got, tt.want)
58+
}
59+
})
60+
}
61+
}

pkg/resource/operations.go

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"sigs.k8s.io/controller-runtime/pkg/client"
2121

2222
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
23+
apierrors "k8s.io/apimachinery/pkg/api/errors"
24+
"k8s.io/apimachinery/pkg/api/meta"
2325
"k8s.io/apimachinery/pkg/util/sets"
2426
"k8s.io/apimachinery/pkg/util/yaml"
2527
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -230,46 +232,38 @@ func CheckCRs(ctx context.Context, crs []*unstructured.Unstructured, runtimeClie
230232

231233
func RemoveCRDs(ctx context.Context, crds []*apiextensionsv1.CustomResourceDefinition,
232234
runtimeClient client.Client,
233-
) error {
234-
for _, crd := range crds {
235-
existingCrd := apiextensionsv1.CustomResourceDefinition{}
236-
if err := runtimeClient.Get(ctx, client.ObjectKeyFromObject(crd), &existingCrd); err != nil {
237-
if client.IgnoreNotFound(err) != nil {
238-
return err
239-
}
235+
) bool {
236+
deleted := true
237+
for _, resource := range crds {
238+
if resourceDeleted(ctx, resource, runtimeClient) {
240239
continue
241240
}
242-
243-
if err := runtimeClient.Delete(ctx, &existingCrd); err != nil {
244-
return err
245-
}
241+
deleted = false
246242
}
247-
return nil
243+
return deleted
248244
}
249245

250-
func RemoveCRs(ctx context.Context, crs []*unstructured.Unstructured,
251-
runtimeClient client.Client,
252-
) (bool, error) {
246+
func RemoveCRs(ctx context.Context, crs []*unstructured.Unstructured, runtimeClient client.Client) bool {
253247
deleted := true
254248
for _, resource := range crs {
255-
existingCustomResource := unstructured.Unstructured{}
256-
existingCustomResource.SetGroupVersionKind(resource.GroupVersionKind())
257-
customResourceKey := client.ObjectKey{
258-
Name: resource.GetName(),
259-
Namespace: resource.GetNamespace(),
260-
}
261-
if err := runtimeClient.Get(ctx, customResourceKey, &existingCustomResource); err != nil {
262-
if client.IgnoreNotFound(err) != nil {
263-
return false, err
264-
}
265-
// resource deleted
249+
if resourceDeleted(ctx, resource, runtimeClient) {
266250
continue
267251
}
268-
269252
deleted = false
270-
if err := runtimeClient.Delete(ctx, resource); err != nil {
271-
return false, err
272-
}
273253
}
274-
return deleted, nil
254+
return deleted
255+
}
256+
257+
func resourceDeleted(ctx context.Context, resource client.Object, runtimeClient client.Client) bool {
258+
logger := log.FromContext(ctx)
259+
err := runtimeClient.Delete(ctx, resource)
260+
// only not found or no resource match can make sure resource deleted
261+
if apierrors.IsNotFound(err) || meta.IsNoMatchError(err) {
262+
return true
263+
}
264+
if err != nil {
265+
logger.V(util.DebugLogLevel).Error(err, "RemoveResources")
266+
return false
267+
}
268+
return false
275269
}

pkg/types/installs.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,6 @@ func (r *ResourceLists) GetWaitForResources() kube.ResourceList {
157157
return append(r.Target, r.Namespace...)
158158
}
159159

160-
func (r *ResourceLists) GetResourcesToBeDeleted() kube.ResourceList {
161-
return append(r.Installed, r.Namespace...)
162-
}
163-
164160
// InstallInfo represents deployment information artifacts to be processed.
165161
type InstallInfo struct {
166162
// ChartInfo represents chart information to be processed

0 commit comments

Comments
 (0)