Merge pull request #180 from mortent/FixPruneDeleteSkipped

Fix output from lifecycle directive with the destroy command
This commit is contained in:
Kubernetes Prow Robot 2020-05-29 13:31:33 -07:00 committed by GitHub
commit 03aba2693b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 299 additions and 40 deletions

View File

@ -76,9 +76,13 @@ type ResourceInfo struct {
// a resource has been applied to the cluster.
ApplyOpResult *event.ApplyEventOperation
// Pruned contains information about whether
// the resources has been pruned.
Pruned bool
// PruneOpResult contains the result after
// a prune operation on a resource
PruneOpResult *event.PruneEventOperation
// DeleteOpResult contains the result after
// a delete operation on a resource
DeleteOpResult *event.DeleteEventOperation
}
// Identifier returns the identifier for the given resource.
@ -205,7 +209,7 @@ func (r *ResourceStateCollector) processPruneEvent(e event.PruneEvent) {
if !found {
return
}
previous.Pruned = true
previous.PruneOpResult = &e.Operation
}
}
@ -290,7 +294,8 @@ func (r *ResourceStateCollector) LatestState() *ResourceState {
resourceStatus: ri.resourceStatus,
ResourceAction: ri.ResourceAction,
ApplyOpResult: ri.ApplyOpResult,
Pruned: ri.Pruned,
PruneOpResult: ri.PruneOpResult,
DeleteOpResult: ri.DeleteOpResult,
})
}
sort.Sort(resourceInfos)

View File

@ -89,8 +89,8 @@ var (
text = resInfo.ApplyOpResult.String()
}
case event.PruneAction:
if resInfo.Pruned {
text = "Pruned"
if resInfo.PruneOpResult != nil {
text = resInfo.PruneOpResult.String()
}
}

View File

@ -13,6 +13,7 @@ import (
var (
createdOpResult = event.Created
prunedOpResult = event.Pruned
)
func TestActionColumnDef(t *testing.T) {
@ -42,7 +43,7 @@ func TestActionColumnDef(t *testing.T) {
"pruned": {
resource: &ResourceInfo{
ResourceAction: event.PruneAction,
Pruned: true,
PruneOpResult: &prunedOpResult,
},
columnWidth: 15,
expectedOutput: "Pruned",

View File

@ -0,0 +1,156 @@
[kind]: https://github.com/kubernetes-sigs/kind
# Demo: Lifecycle directives
This demo shows how it is possible to use a lifecycle directive to
change the behavior of prune and delete for specific resources.
First define a place to work:
<!-- @makeWorkplace @testE2EAgainstLatestRelease -->
```
DEMO_HOME=$(mktemp -d)
```
Alternatively, use
> ```
> DEMO_HOME=~/hello
> ```
## Establish the base
<!-- @createBase @testE2EAgainstLatestRelease -->
```
BASE=$DEMO_HOME/base
mkdir -p $BASE
OUTPUT=$DEMO_HOME/output
mkdir -p $OUTPUT
function expectedOutputLine() {
test 1 == \
$(grep "$@" $OUTPUT/status | wc -l); \
echo $?
}
```
In this example we will just use two ConfigMap resources for simplicity, but
of course any type of resource can be used. On one of our ConfigMaps, we add the
**cli-utils.sigs.k8s.io/on-remove** annotation with the value of **keep**. This
annotation tells the kapply tool that this resource should not be deleted, even
if it would otherwise be pruned or deleted with the destroy command.
<!-- @createFirstCM @testE2EAgainstLatestRelease-->
```
cat <<EOF >$BASE/configMap1.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: firstmap
data:
artist: Ornette Coleman
album: The shape of jazz to come
EOF
```
This ConfigMap includes the lifecycle directive annotation
<!-- @createSecondCM @testE2EAgainstLatestRelease-->
```
cat <<EOF >$BASE/configMap2.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: secondmap
annotations:
cli-utils.sigs.k8s.io/on-remove: keep
data:
artist: Husker Du
album: New Day Rising
EOF
```
## Run end-to-end tests
The following requires installation of [kind].
Delete any existing kind cluster and create a new one. By default the name of the cluster is "kind"
<!-- @deleteAndCreateKindCluster @testE2EAgainstLatestRelease -->
```
kind delete cluster
kind create cluster
```
Use the kapply init command to generate the inventory template. This contains
the namespace and inventory id used by apply to create inventory objects.
<!-- @createInventoryTemplate @testE2EAgainstLatestRelease-->
```
kapply init $BASE
```
Apply both resources to the cluster.
<!-- @runApply @testE2EAgainstLatestRelease -->
```
kapply apply $BASE --reconcile-timeout=1m > $OUTPUT/status
```
Use the preview command to show what will happen if we run destroy. This should
show that the second ConfigMap will not be deleted even when using the destroy
command.
<!-- @runDestroyPreview @testE2EAgainstLatestRelease -->
```
kapply preview --destroy $BASE > $OUTPUT/status
expectedOutputLine "configmap/firstmap deleted (preview)"
expectedOutputLine "configmap/secondmap delete skipped (preview)"
```
We run the destroy command and see that the resource without the annotation
has been deleted, while the resource with the annotation is still in the
cluster.
<!-- @runDestroy @testE2EAgainstLatestRelease -->
```
kapply destroy $BASE > $OUTPUT/status
expectedOutputLine "configmap/firstmap deleted"
expectedOutputLine "configmap/secondmap delete skipped"
kubectl get cm --no-headers | awk '{print $1}' > $OUTPUT/status
expectedOutputLine "secondmap"
```
Apply the resources back to the cluster so we can demonstrate the lifecycle
directive with pruning.
<!-- @runApplyAgain @testE2EAgainstLatestRelease -->
```
kapply apply $BASE --reconcile-timeout=1m > $OUTPUT/status
```
Delete the manifest for the second configmap
<!-- @runDeleteManifest @testE2EAgainstLatestRelease -->
```
rm $BASE/configMap2.yaml
```
Run preview to see that while secondmap would normally be pruned, it
will instead be skipped due to the lifecycle directive.
<!-- @runPreviewForPrune @testE2EAgainstLatestRelease -->
```
kapply preview $BASE > $OUTPUT/status
expectedOutputLine "configmap/secondmap prune skipped (preview)"
```
Run apply and verify that secondmap is still in the cluster.
<!-- @runApplyToPrune @testE2EAgainstLatestRelease -->
```
kapply apply $BASE > $OUTPUT/status
expectedOutputLine "configmap/secondmap prune skipped"
kubectl get cm --no-headers | awk '{print $1}' > $OUTPUT/status
expectedOutputLine "secondmap"
```

View File

@ -64,11 +64,16 @@ func (p *pruneStats) incSkipped() {
}
type deleteStats struct {
count int
deleted int
skipped int
}
func (d *deleteStats) inc() {
d.count++
func (d *deleteStats) incDeleted() {
d.deleted++
}
func (d *deleteStats) incSkipped() {
d.skipped++
}
type statusCollector struct {
@ -185,31 +190,37 @@ func (b *BasicPrinter) processPruneEvent(pe event.PruneEvent, ps *pruneStats, p
switch pe.Type {
case event.PruneEventCompleted:
p("%d resource(s) pruned, %d skipped", ps.pruned, ps.skipped)
case event.PruneEventSkipped:
obj := pe.Object
gvk := obj.GetObjectKind().GroupVersionKind()
name := getName(obj)
ps.incSkipped()
p("%s %s", resourceIDToString(gvk.GroupKind(), name), "pruned skipped")
case event.PruneEventResourceUpdate:
obj := pe.Object
gvk := obj.GetObjectKind().GroupVersionKind()
name := getName(obj)
ps.incPruned()
p("%s %s", resourceIDToString(gvk.GroupKind(), name), "pruned")
switch pe.Operation {
case event.Pruned:
ps.incPruned()
p("%s %s", resourceIDToString(gvk.GroupKind(), name), "pruned")
case event.PruneSkipped:
ps.incSkipped()
p("%s %s", resourceIDToString(gvk.GroupKind(), name), "prune skipped")
}
}
}
func (b *BasicPrinter) processDeleteEvent(de event.DeleteEvent, ds *deleteStats, p printFunc) {
switch de.Type {
case event.DeleteEventCompleted:
p("%d resource(s) deleted", ds.count)
p("%d resource(s) deleted, %d skipped", ds.deleted, ds.skipped)
case event.DeleteEventResourceUpdate:
obj := de.Object
gvk := obj.GetObjectKind().GroupVersionKind()
name := getName(obj)
ds.inc()
p("%s %s", resourceIDToString(gvk.GroupKind(), name), "deleted")
switch de.Operation {
case event.Deleted:
ds.incDeleted()
p("%s %s", resourceIDToString(gvk.GroupKind(), name), "deleted")
case event.DeleteSkipped:
ds.incSkipped()
p("%s %s", resourceIDToString(gvk.GroupKind(), name), "delete skipped")
}
}
}

View File

@ -4,9 +4,10 @@
package apply
import (
"fmt"
"github.com/go-errors/errors"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -165,11 +166,23 @@ func runPruneEventTransformer(eventChannel chan event.Event) (chan event.Event,
eventChannel <- event.Event{
Type: event.DeleteType,
DeleteEvent: event.DeleteEvent{
Type: event.DeleteEventResourceUpdate,
Object: msg.PruneEvent.Object,
Type: event.DeleteEventResourceUpdate,
Operation: transformPruneOperation(msg.PruneEvent.Operation),
Object: msg.PruneEvent.Object,
},
}
}
}()
return tempEventChannel, completedChannel
}
func transformPruneOperation(pruneOp event.PruneEventOperation) event.DeleteEventOperation {
switch pruneOp {
case event.PruneSkipped:
return event.DeleteSkipped
case event.Pruned:
return event.Deleted
default:
panic(fmt.Errorf("unknown prune operation %s", pruneOp.String()))
}
}

View File

@ -0,0 +1,27 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Code generated by "stringer -type=DeleteEventOperation"; DO NOT EDIT.
package event
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Deleted-0]
_ = x[DeleteSkipped-1]
}
const _DeleteEventOperation_name = "DeletedDeleteSkipped"
var _DeleteEventOperation_index = [...]uint8{0, 7, 20}
func (i DeleteEventOperation) String() string {
if i < 0 || i >= DeleteEventOperation(len(_DeleteEventOperation_index)-1) {
return "DeleteEventOperation(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _DeleteEventOperation_name[_DeleteEventOperation_index[i]:_DeleteEventOperation_index[i+1]]
}

View File

@ -104,13 +104,21 @@ type PruneEventType int
const (
PruneEventResourceUpdate PruneEventType = iota
PruneEventSkipped
PruneEventCompleted
)
//go:generate stringer -type=PruneEventOperation
type PruneEventOperation int
const (
Pruned PruneEventOperation = iota
PruneSkipped
)
type PruneEvent struct {
Type PruneEventType
Object runtime.Object
Type PruneEventType
Operation PruneEventOperation
Object runtime.Object
}
//go:generate stringer -type=DeleteEventType
@ -121,7 +129,16 @@ const (
DeleteEventCompleted
)
//go:generate stringer -type=DeleteEventOperation
type DeleteEventOperation int
const (
Deleted DeleteEventOperation = iota
DeleteSkipped
)
type DeleteEvent struct {
Type DeleteEventType
Object runtime.Object
Type DeleteEventType
Operation DeleteEventOperation
Object runtime.Object
}

View File

@ -0,0 +1,27 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Code generated by "stringer -type=PruneEventOperation"; DO NOT EDIT.
package event
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[Pruned-0]
_ = x[PruneSkipped-1]
}
const _PruneEventOperation_name = "PrunedPruneSkipped"
var _PruneEventOperation_index = [...]uint8{0, 6, 18}
func (i PruneEventOperation) String() string {
if i < 0 || i >= PruneEventOperation(len(_PruneEventOperation_index)-1) {
return "PruneEventOperation(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _PruneEventOperation_name[_PruneEventOperation_index[i]:_PruneEventOperation_index[i+1]]
}

View File

@ -12,13 +12,12 @@ func _() {
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[PruneEventResourceUpdate-0]
_ = x[PruneEventSkipped-1]
_ = x[PruneEventCompleted-2]
_ = x[PruneEventCompleted-1]
}
const _PruneEventType_name = "PruneEventResourceUpdatePruneEventSkippedPruneEventCompleted"
const _PruneEventType_name = "PruneEventResourceUpdatePruneEventCompleted"
var _PruneEventType_index = [...]uint8{0, 24, 41, 60}
var _PruneEventType_index = [...]uint8{0, 24, 43}
func (i PruneEventType) String() string {
if i < 0 || i >= PruneEventType(len(_PruneEventType_index)-1) {

View File

@ -254,8 +254,9 @@ func (po *PruneOptions) Prune(currentObjects []*resource.Info, eventChannel chan
eventChannel <- event.Event{
Type: event.PruneType,
PruneEvent: event.PruneEvent{
Type: event.PruneEventSkipped,
Object: obj,
Type: event.PruneEventResourceUpdate,
Operation: event.PruneSkipped,
Object: obj,
},
}
continue
@ -270,8 +271,9 @@ func (po *PruneOptions) Prune(currentObjects []*resource.Info, eventChannel chan
eventChannel <- event.Event{
Type: event.PruneType,
PruneEvent: event.PruneEvent{
Type: event.PruneEventResourceUpdate,
Object: obj,
Type: event.PruneEventResourceUpdate,
Operation: event.Pruned,
Object: obj,
},
}
}
@ -292,8 +294,9 @@ func (po *PruneOptions) Prune(currentObjects []*resource.Info, eventChannel chan
eventChannel <- event.Event{
Type: event.PruneType,
PruneEvent: event.PruneEvent{
Type: event.PruneEventResourceUpdate,
Object: pastGroupInfo.Object,
Type: event.PruneEventResourceUpdate,
Operation: event.Pruned,
Object: pastGroupInfo.Object,
},
}
}