Skip to content

Commit 42d34da

Browse files
authored
Merge pull request #562 from karlkfi/karl-filter-errors
feat: expose skip reason as error
2 parents a5fa288 + 7aee3cc commit 42d34da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1238
-635
lines changed

examples/alphaTestExamples/pruneAndDelete.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ Apply the three resources to the cluster.
133133
<!-- @runApply @testE2EAgainstLatestRelease -->
134134
```
135135
kapply apply $BASE --reconcile-timeout=1m | tee $OUTPUT/status
136+
137+
expectedOutputLine "3 resource(s) applied. 3 created, 0 unchanged, 0 configured, 0 failed"
138+
expectedOutputLine "3 resource(s) reconciled, 0 skipped, 0 failed to reconcile, 0 timed out"
136139
```
137140
138141
Use the preview command to show what will happen if we run destroy. This should
@@ -143,10 +146,9 @@ command.
143146
kapply preview --destroy $BASE | tee $OUTPUT/status
144147

145148
expectedOutputLine "configmap/firstmap deleted"
146-
147-
expectedOutputLine "configmap/secondmap delete skipped"
148-
149-
expectedOutputLine "configmap/thirdmap delete skipped"
149+
expectedOutputLine 'configmap/secondmap delete skipped: annotation prevents deletion ("cli-utils.sigs.k8s.io/on-remove": "keep")'
150+
expectedOutputLine 'configmap/thirdmap delete skipped: annotation prevents deletion ("client.lifecycle.config.k8s.io/deletion": "detach")'
151+
expectedOutputLine "1 resource(s) deleted, 2 skipped"
150152
```
151153
152154
We run the destroy command and see that the resource without the annotations (firstmap)
@@ -157,12 +159,15 @@ cluster.
157159
kapply destroy $BASE | tee $OUTPUT/status
158160

159161
expectedOutputLine "configmap/firstmap deleted"
162+
expectedOutputLine 'configmap/secondmap delete skipped: annotation prevents deletion ("cli-utils.sigs.k8s.io/on-remove": "keep")'
163+
expectedOutputLine 'configmap/thirdmap delete skipped: annotation prevents deletion ("client.lifecycle.config.k8s.io/deletion": "detach")'
164+
expectedOutputLine "1 resource(s) deleted, 2 skipped"
160165

161-
expectedOutputLine "configmap/secondmap delete skipped"
162-
163-
expectedOutputLine "configmap/thirdmap delete skipped"
166+
expectedOutputLine "configmap/firstmap reconciled"
167+
expectedOutputLine "configmap/secondmap reconcile skipped"
168+
expectedOutputLine "configmap/thirdmap reconcile skipped"
169+
expectedOutputLine "1 resource(s) reconciled, 2 skipped, 0 failed to reconcile, 0 timed out"
164170

165-
expectedOutputLine "1 resource(s) deleted, 2 skipped"
166171
expectedNotFound "resource(s) pruned"
167172

168173
kubectl get cm --no-headers | awk '{print $1}' | tee $OUTPUT/status
@@ -194,7 +199,6 @@ will instead be skipped due to the lifecycle directive.
194199
kapply preview $BASE | tee $OUTPUT/status
195200

196201
expectedOutputLine "configmap/secondmap prune skipped"
197-
198202
expectedOutputLine "configmap/thirdmap prune skipped"
199203
```
200204
@@ -204,7 +208,6 @@ Run apply and verify that secondmap and thirdmap are still in the cluster.
204208
kapply apply $BASE | tee $OUTPUT/status
205209

206210
expectedOutputLine "configmap/secondmap prune skipped"
207-
208211
expectedOutputLine "configmap/thirdmap prune skipped"
209212

210213
kubectl get cm --no-headers | awk '{print $1}' | tee $OUTPUT/status

examples/alphaTestExamples/pruneNamespace.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,21 @@ test-namespace should **not** be pruned.
170170
<!-- @applySecondTime @testE2EAgainstLatestRelease -->
171171
```
172172
kapply apply $BASE --reconcile-timeout=1m | tee $OUTPUT/status
173+
174+
expectedOutputLine "configmap/cm-c apply skipped: dependency scheduled for delete: _test-namespace__Namespace"
175+
expectedOutputLine "1 resource(s) applied. 0 created, 1 unchanged, 0 configured, 0 failed"
176+
expectedOutputLine "configmap/cm-c reconcile skipped"
177+
173178
expectedOutputLine "configmap/cm-a pruned"
174179
expectedOutputLine "configmap/cm-b pruned"
175-
expectedOutputLine "configmap/cm-c unchanged"
176-
expectedOutputLine "2 resource(s) pruned, 1 skipped"
180+
expectedOutputLine "namespace/test-namespace prune skipped: namespace still in use: test-namespace"
181+
expectedOutputLine "2 resource(s) pruned, 1 skipped, 0 failed to prune"
182+
183+
expectedOutputLine "namespace/test-namespace reconcile skipped"
184+
expectedOutputLine "configmap/cm-a reconciled"
185+
expectedOutputLine "configmap/cm-b reconciled"
186+
expectedOutputLine "configmap/cm-c reconcile skipped"
187+
expectedOutputLine "2 resource(s) reconciled, 2 skipped, 0 failed to reconcile, 0 timed out"
177188

178189
# The test-namespace should not be pruned.
179190
kubectl get ns test-namespace --no-headers | wc -l | tee $OUTPUT/status

pkg/apply/applier.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.Info, objects objec
149149
// Build list of prune validation filters.
150150
pruneFilters := []filter.ValidationFilter{
151151
filter.PreventRemoveFilter{},
152-
filter.InventoryPolicyFilter{
152+
filter.InventoryPolicyPruneFilter{
153153
Inv: invInfo,
154154
InvPolicy: options.InventoryPolicy,
155155
},

pkg/apply/applier_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package apply
55

66
import (
77
"context"
8-
"fmt"
98
"sort"
109
"sync"
1110
"testing"
@@ -15,6 +14,7 @@ import (
1514
"github.com/stretchr/testify/require"
1615
"k8s.io/apimachinery/pkg/util/validation/field"
1716
"k8s.io/kubectl/pkg/scheme"
17+
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
1818
"sigs.k8s.io/cli-utils/pkg/apply/event"
1919
"sigs.k8s.io/cli-utils/pkg/inventory"
2020
pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
@@ -875,7 +875,12 @@ func TestApplier(t *testing.T) {
875875
ApplyEvent: &testutil.ExpApplyEvent{
876876
GroupName: "apply-0",
877877
Identifier: testutil.ToIdentifier(t, resources["deployment"]),
878-
Error: testutil.EqualErrorType(inventory.NewInventoryOverlapError(fmt.Errorf(""))),
878+
Operation: event.Unchanged,
879+
Error: &inventory.PolicyPreventedActuationError{
880+
Strategy: actuation.ActuationStrategyApply,
881+
Policy: inventory.PolicyMustMatch,
882+
Status: inventory.NoMatch,
883+
},
879884
},
880885
},
881886
{
@@ -983,6 +988,11 @@ func TestApplier(t *testing.T) {
983988
GroupName: "prune-0",
984989
Operation: event.PruneSkipped,
985990
Identifier: testutil.ToIdentifier(t, resources["deployment"]),
991+
Error: &inventory.PolicyPreventedActuationError{
992+
Strategy: actuation.ActuationStrategyDelete,
993+
Policy: inventory.PolicyMustMatch,
994+
Status: inventory.NoMatch,
995+
},
986996
},
987997
},
988998
{

pkg/apply/destroyer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func (d *Destroyer) Run(ctx context.Context, invInfo inventory.Info, options Des
142142
}
143143
deleteFilters := []filter.ValidationFilter{
144144
filter.PreventRemoveFilter{},
145-
filter.InventoryPolicyFilter{
145+
filter.InventoryPolicyPruneFilter{
146146
Inv: invInfo,
147147
InvPolicy: options.InventoryPolicy,
148148
},

pkg/apply/event/event.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -261,15 +261,13 @@ type PruneEvent struct {
261261
Identifier object.ObjMetadata
262262
Operation PruneEventOperation
263263
Object *unstructured.Unstructured
264-
// If prune is skipped, this reason string explains why
265-
Reason string
266-
Error error
264+
Error error
267265
}
268266

269267
// String returns a string suitable for logging
270268
func (pe PruneEvent) String() string {
271-
return fmt.Sprintf("PruneEvent{ GroupName: %q, Operation: %q, Identifier: %q, Reason: %q, Error: %q }",
272-
pe.GroupName, pe.Operation, pe.Identifier, pe.Reason, pe.Error)
269+
return fmt.Sprintf("PruneEvent{ GroupName: %q, Operation: %q, Identifier: %q, Error: %q }",
270+
pe.GroupName, pe.Operation, pe.Identifier, pe.Error)
273271
}
274272

275273
//go:generate stringer -type=DeleteEventOperation
@@ -286,15 +284,13 @@ type DeleteEvent struct {
286284
Identifier object.ObjMetadata
287285
Operation DeleteEventOperation
288286
Object *unstructured.Unstructured
289-
// If delete is skipped, this reason string explains why
290-
Reason string
291-
Error error
287+
Error error
292288
}
293289

294290
// String returns a string suitable for logging
295291
func (de DeleteEvent) String() string {
296-
return fmt.Sprintf("DeleteEvent{ GroupName: %q, Operation: %q, Identifier: %q, Reason: %q, Error: %q }",
297-
de.GroupName, de.Operation, de.Identifier, de.Reason, de.Error)
292+
return fmt.Sprintf("DeleteEvent{ GroupName: %q, Operation: %q, Identifier: %q, Error: %q }",
293+
de.GroupName, de.Operation, de.Identifier, de.Error)
298294
}
299295

300296
type ValidationEvent struct {

pkg/apply/filter/current-uids-filter.go

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88

99
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10+
"k8s.io/apimachinery/pkg/types"
1011
"k8s.io/apimachinery/pkg/util/sets"
1112
)
1213

@@ -22,13 +23,31 @@ func (cuf CurrentUIDFilter) Name() string {
2223
return "CurrentUIDFilter"
2324
}
2425

25-
// Filter returns true if the passed object should NOT be pruned (deleted)
26-
// because it has recently been applied.
27-
func (cuf CurrentUIDFilter) Filter(obj *unstructured.Unstructured) (bool, string, error) {
28-
uid := string(obj.GetUID())
29-
if cuf.CurrentUIDs.Has(uid) {
30-
reason := fmt.Sprintf("resource just applied (UID: %q)", uid)
31-
return true, reason, nil
26+
// Filter returns a ApplyPreventedDeletionError if the object prune/delete
27+
// should be skipped.
28+
func (cuf CurrentUIDFilter) Filter(obj *unstructured.Unstructured) error {
29+
uid := obj.GetUID()
30+
if cuf.CurrentUIDs.Has(string(uid)) {
31+
return &ApplyPreventedDeletionError{UID: uid}
3232
}
33-
return false, "", nil
33+
return nil
34+
}
35+
36+
type ApplyPreventedDeletionError struct {
37+
UID types.UID
38+
}
39+
40+
func (e *ApplyPreventedDeletionError) Error() string {
41+
return fmt.Sprintf("object just applied (UID: %q)", e.UID)
42+
}
43+
44+
func (e *ApplyPreventedDeletionError) Is(err error) bool {
45+
if err == nil {
46+
return false
47+
}
48+
tErr, ok := err.(*ApplyPreventedDeletionError)
49+
if !ok {
50+
return false
51+
}
52+
return e.UID == tErr.UID
3453
}

pkg/apply/filter/current-uids-filter_test.go

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,36 @@ import (
88

99
"k8s.io/apimachinery/pkg/types"
1010
"k8s.io/apimachinery/pkg/util/sets"
11+
"sigs.k8s.io/cli-utils/pkg/testutil"
1112
)
1213

1314
func TestCurrentUIDFilter(t *testing.T) {
1415
tests := map[string]struct {
15-
filterUIDs sets.String
16-
objUID string
17-
filtered bool
16+
filterUIDs sets.String
17+
objUID string
18+
expectedError error
1819
}{
1920
"Empty filter UIDs, object is not filtered": {
2021
filterUIDs: sets.NewString(),
2122
objUID: "bar",
22-
filtered: false,
2323
},
2424
"Empty object UID, object is not filtered": {
2525
filterUIDs: sets.NewString("foo"),
2626
objUID: "",
27-
filtered: false,
2827
},
2928
"Object UID not in filter UID set, object is not filtered": {
3029
filterUIDs: sets.NewString("foo", "baz"),
3130
objUID: "bar",
32-
filtered: false,
3331
},
3432
"Object UID is in filter UID set, object is filtered": {
35-
filterUIDs: sets.NewString("foo"),
36-
objUID: "foo",
37-
filtered: true,
33+
filterUIDs: sets.NewString("foo"),
34+
objUID: "foo",
35+
expectedError: &ApplyPreventedDeletionError{UID: "foo"},
3836
},
3937
"Object UID is among several filter UIDs, object is filtered": {
40-
filterUIDs: sets.NewString("foo", "bar", "baz"),
41-
objUID: "foo",
42-
filtered: true,
38+
filterUIDs: sets.NewString("foo", "bar", "baz"),
39+
objUID: "foo",
40+
expectedError: &ApplyPreventedDeletionError{UID: "foo"},
4341
},
4442
}
4543

@@ -50,19 +48,8 @@ func TestCurrentUIDFilter(t *testing.T) {
5048
}
5149
obj := defaultObj.DeepCopy()
5250
obj.SetUID(types.UID(tc.objUID))
53-
actual, reason, err := filter.Filter(obj)
54-
if err != nil {
55-
t.Fatalf("CurrentUIDFilter unexpected error (%s)", err)
56-
}
57-
if tc.filtered != actual {
58-
t.Errorf("CurrentUIDFilter expected filter (%t), got (%t)", tc.filtered, actual)
59-
}
60-
if tc.filtered && len(reason) == 0 {
61-
t.Errorf("CurrentUIDFilter filtered; expected but missing Reason")
62-
}
63-
if !tc.filtered && len(reason) > 0 {
64-
t.Errorf("CurrentUIDFilter not filtered; received unexpected Reason: %s", reason)
65-
}
51+
err := filter.Filter(obj)
52+
testutil.AssertEqual(t, tc.expectedError, err)
6653
})
6754
}
6855
}

0 commit comments

Comments
 (0)