mirror of https://github.com/fluxcd/cli-utils.git
Merge pull request #180 from mortent/FixPruneDeleteSkipped
Fix output from lifecycle directive with the destroy command
This commit is contained in:
commit
03aba2693b
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
|
@ -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)
|
||||
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()
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -166,6 +167,7 @@ func runPruneEventTransformer(eventChannel chan event.Event) (chan event.Event,
|
|||
Type: event.DeleteType,
|
||||
DeleteEvent: event.DeleteEvent{
|
||||
Type: event.DeleteEventResourceUpdate,
|
||||
Operation: transformPruneOperation(msg.PruneEvent.Operation),
|
||||
Object: msg.PruneEvent.Object,
|
||||
},
|
||||
}
|
||||
|
|
@ -173,3 +175,14 @@ func runPruneEventTransformer(eventChannel chan event.Event) (chan event.Event,
|
|||
}()
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
}
|
||||
|
|
@ -104,12 +104,20 @@ 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
|
||||
Operation PruneEventOperation
|
||||
Object runtime.Object
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +129,16 @@ const (
|
|||
DeleteEventCompleted
|
||||
)
|
||||
|
||||
//go:generate stringer -type=DeleteEventOperation
|
||||
type DeleteEventOperation int
|
||||
|
||||
const (
|
||||
Deleted DeleteEventOperation = iota
|
||||
DeleteSkipped
|
||||
)
|
||||
|
||||
type DeleteEvent struct {
|
||||
Type DeleteEventType
|
||||
Operation DeleteEventOperation
|
||||
Object runtime.Object
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -254,7 +254,8 @@ func (po *PruneOptions) Prune(currentObjects []*resource.Info, eventChannel chan
|
|||
eventChannel <- event.Event{
|
||||
Type: event.PruneType,
|
||||
PruneEvent: event.PruneEvent{
|
||||
Type: event.PruneEventSkipped,
|
||||
Type: event.PruneEventResourceUpdate,
|
||||
Operation: event.PruneSkipped,
|
||||
Object: obj,
|
||||
},
|
||||
}
|
||||
|
|
@ -271,6 +272,7 @@ func (po *PruneOptions) Prune(currentObjects []*resource.Info, eventChannel chan
|
|||
Type: event.PruneType,
|
||||
PruneEvent: event.PruneEvent{
|
||||
Type: event.PruneEventResourceUpdate,
|
||||
Operation: event.Pruned,
|
||||
Object: obj,
|
||||
},
|
||||
}
|
||||
|
|
@ -293,6 +295,7 @@ func (po *PruneOptions) Prune(currentObjects []*resource.Info, eventChannel chan
|
|||
Type: event.PruneType,
|
||||
PruneEvent: event.PruneEvent{
|
||||
Type: event.PruneEventResourceUpdate,
|
||||
Operation: event.Pruned,
|
||||
Object: pastGroupInfo.Object,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue