mirror of https://github.com/fluxcd/cli-utils.git
Refactor prune/delete using ValidationFilter interface
This commit is contained in:
parent
70b9f67440
commit
4432f51ac3
|
|
@ -16,6 +16,7 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubectl/pkg/cmd/util"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/filter"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/info"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/poller"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/prune"
|
||||
|
|
@ -138,8 +139,6 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.InventoryInfo, obje
|
|||
}
|
||||
// Fetch the queue (channel) of tasks that should be executed.
|
||||
klog.V(4).Infoln("applier building task queue...")
|
||||
// TODO(seans): Remove this once Filter interface implemented.
|
||||
a.pruneOptions.LocalNamespaces = localNamespaces(invInfo, object.UnstructuredsToObjMetas(objects))
|
||||
taskBuilder := &solver.TaskQueueBuilder{
|
||||
PruneOptions: a.pruneOptions,
|
||||
Factory: a.factory,
|
||||
|
|
@ -156,11 +155,22 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.InventoryInfo, obje
|
|||
PruneTimeout: options.PruneTimeout,
|
||||
InventoryPolicy: options.InventoryPolicy,
|
||||
}
|
||||
// Build list of prune validation filters.
|
||||
pruneFilters := []filter.ValidationFilter{
|
||||
filter.PreventRemoveFilter{},
|
||||
filter.InventoryPolicyFilter{
|
||||
Inv: invInfo,
|
||||
InvPolicy: options.InventoryPolicy,
|
||||
},
|
||||
filter.LocalNamespacesFilter{
|
||||
LocalNamespaces: localNamespaces(invInfo, object.UnstructuredsToObjMetas(objects)),
|
||||
},
|
||||
}
|
||||
// Build the task queue by appending tasks in the proper order.
|
||||
taskQueue := taskBuilder.
|
||||
AppendInvAddTask(invInfo, applyObjs).
|
||||
AppendApplyWaitTasks(invInfo, applyObjs, opts).
|
||||
AppendPruneWaitTasks(invInfo, pruneObjs, opts).
|
||||
AppendPruneWaitTasks(pruneObjs, pruneFilters, opts).
|
||||
AppendInvSetTask(invInfo).
|
||||
Build()
|
||||
// Send event to inform the caller about the resources that
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/filter"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/poller"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/prune"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/solver"
|
||||
|
|
@ -118,11 +119,17 @@ func (d *Destroyer) Run(inv inventory.InventoryInfo, option *DestroyerOption) <-
|
|||
PruneTimeout: option.DeleteTimeout,
|
||||
DryRunStrategy: option.DryRunStrategy,
|
||||
PrunePropagationPolicy: option.DeletePropagationPolicy,
|
||||
InventoryPolicy: option.InventoryPolicy,
|
||||
}
|
||||
deleteFilters := []filter.ValidationFilter{
|
||||
filter.PreventRemoveFilter{},
|
||||
filter.InventoryPolicyFilter{
|
||||
Inv: inv,
|
||||
InvPolicy: option.InventoryPolicy,
|
||||
},
|
||||
}
|
||||
// Build the ordered set of tasks to execute.
|
||||
taskQueue := taskBuilder.
|
||||
AppendPruneWaitTasks(inv, deleteObjs, opts).
|
||||
AppendPruneWaitTasks(deleteObjs, deleteFilters, opts).
|
||||
AppendDeleteInvTask(inv).
|
||||
Build()
|
||||
// Send event to inform the caller about the resources that
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// CurrentUIDFilter implements ValidationFilter interface to determine
|
||||
// if an object should not be pruned (deleted) because it has recently
|
||||
// been applied.
|
||||
type CurrentUIDFilter struct {
|
||||
CurrentUIDs sets.String
|
||||
}
|
||||
|
||||
// Name returns a filter identifier for logging.
|
||||
func (cuf CurrentUIDFilter) Name() string {
|
||||
return "CurrentUIDFilter"
|
||||
}
|
||||
|
||||
// Filter returns true if the passed object should NOT be pruned (deleted)
|
||||
// because the it is a namespace that objects still reside in; otherwise
|
||||
// returns false. This filter should not be added to the list of filters
|
||||
// for "destroying", since every object is being deletet. Never returns an error.
|
||||
func (cuf CurrentUIDFilter) Filter(obj *unstructured.Unstructured) (bool, error) {
|
||||
uid := string(obj.GetUID())
|
||||
if cuf.CurrentUIDs.Has(uid) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
func TestCurrentUIDFilter(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
filterUIDs sets.String
|
||||
objUID string
|
||||
filtered bool
|
||||
}{
|
||||
"Empty filter UIDs, object is not filtered": {
|
||||
filterUIDs: sets.NewString(),
|
||||
objUID: "bar",
|
||||
filtered: false,
|
||||
},
|
||||
"Empty object UID, object is not filtered": {
|
||||
filterUIDs: sets.NewString("foo"),
|
||||
objUID: "",
|
||||
filtered: false,
|
||||
},
|
||||
"Object UID not in filter UID set, object is not filtered": {
|
||||
filterUIDs: sets.NewString("foo", "baz"),
|
||||
objUID: "bar",
|
||||
filtered: false,
|
||||
},
|
||||
"Object UID is in filter UID set, object is filtered": {
|
||||
filterUIDs: sets.NewString("foo"),
|
||||
objUID: "foo",
|
||||
filtered: true,
|
||||
},
|
||||
"Object UID is among several filter UIDs, object is filtered": {
|
||||
filterUIDs: sets.NewString("foo", "bar", "baz"),
|
||||
objUID: "foo",
|
||||
filtered: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
filter := CurrentUIDFilter{
|
||||
CurrentUIDs: tc.filterUIDs,
|
||||
}
|
||||
obj := defaultObj.DeepCopy()
|
||||
obj.SetUID(types.UID(tc.objUID))
|
||||
actual, err := filter.Filter(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("CurrentUIDFilter unexpected error (%s)", err)
|
||||
}
|
||||
if tc.filtered != actual {
|
||||
t.Errorf("CurrentUIDFilter expected filter (%t), got (%t)", tc.filtered, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
// ValidationFilter interface decouples apply/prune validation
|
||||
// from the concrete structs used for validation. The apply/prune
|
||||
// functionality will run validation filters to remove objects
|
||||
// which should not be applied or pruned.
|
||||
type ValidationFilter interface {
|
||||
// Name returns a filter name (usually for logging).
|
||||
Name() string
|
||||
// Filter returns true if validation fails or an error.
|
||||
Filter(obj *unstructured.Unstructured) (bool, error)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/cli-utils/pkg/inventory"
|
||||
)
|
||||
|
||||
// InventoryPolicyFilter implements ValidationFilter interface to determine
|
||||
// if an object should be pruned (deleted) because of the InventoryPolicy
|
||||
// and if the objects owning inventory identifier matchs the inventory id.
|
||||
type InventoryPolicyFilter struct {
|
||||
Inv inventory.InventoryInfo
|
||||
InvPolicy inventory.InventoryPolicy
|
||||
}
|
||||
|
||||
// Name returns a filter identifier for logging.
|
||||
func (ipf InventoryPolicyFilter) Name() string {
|
||||
return "InventoryPolictyFilter"
|
||||
}
|
||||
|
||||
// Filter returns true if the passed object should NOT be pruned (deleted)
|
||||
// because the "prevent remove" annotation is present; otherwise returns
|
||||
// false. Never returns an error.
|
||||
func (ipf InventoryPolicyFilter) Filter(obj *unstructured.Unstructured) (bool, error) {
|
||||
// Check the inventory id "match" and the adopt policy to determine
|
||||
// if an object should be pruned (deleted).
|
||||
if !inventory.CanPrune(ipf.Inv, obj, ipf.InvPolicy) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
"sigs.k8s.io/cli-utils/pkg/inventory"
|
||||
)
|
||||
|
||||
var inventoryObj = &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "inventory-name",
|
||||
"namespace": "inventory-namespace",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestInventoryPolicyFilter(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
inventoryID string
|
||||
objInventoryID string
|
||||
policy inventory.InventoryPolicy
|
||||
filtered bool
|
||||
}{
|
||||
"inventory and object ids match, not filtered": {
|
||||
inventoryID: "foo",
|
||||
objInventoryID: "foo",
|
||||
policy: inventory.InventoryPolicyMustMatch,
|
||||
filtered: false,
|
||||
},
|
||||
"inventory and object ids match and adopt, not filtered": {
|
||||
inventoryID: "foo",
|
||||
objInventoryID: "foo",
|
||||
policy: inventory.AdoptIfNoInventory,
|
||||
filtered: false,
|
||||
},
|
||||
"inventory and object ids do no match and policy must match, filtered": {
|
||||
inventoryID: "foo",
|
||||
objInventoryID: "bar",
|
||||
policy: inventory.InventoryPolicyMustMatch,
|
||||
filtered: true,
|
||||
},
|
||||
"inventory and object ids do no match and adopt if no inventory, filtered": {
|
||||
inventoryID: "foo",
|
||||
objInventoryID: "bar",
|
||||
policy: inventory.AdoptIfNoInventory,
|
||||
filtered: true,
|
||||
},
|
||||
"inventory and object ids do no match and adopt all, not filtered": {
|
||||
inventoryID: "foo",
|
||||
objInventoryID: "bar",
|
||||
policy: inventory.AdoptAll,
|
||||
filtered: false,
|
||||
},
|
||||
"object id empty and adopt all, not filtered": {
|
||||
inventoryID: "foo",
|
||||
objInventoryID: "",
|
||||
policy: inventory.AdoptAll,
|
||||
filtered: false,
|
||||
},
|
||||
"object id empty and policy must match, filtered": {
|
||||
inventoryID: "foo",
|
||||
objInventoryID: "",
|
||||
policy: inventory.InventoryPolicyMustMatch,
|
||||
filtered: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
invIDLabel := map[string]string{
|
||||
common.InventoryLabel: tc.inventoryID,
|
||||
}
|
||||
invObj := inventoryObj.DeepCopy()
|
||||
invObj.SetLabels(invIDLabel)
|
||||
filter := InventoryPolicyFilter{
|
||||
Inv: inventory.WrapInventoryInfoObj(invObj),
|
||||
InvPolicy: tc.policy,
|
||||
}
|
||||
objIDAnnotation := map[string]string{
|
||||
"config.k8s.io/owning-inventory": tc.objInventoryID,
|
||||
}
|
||||
obj := defaultObj.DeepCopy()
|
||||
obj.SetAnnotations(objIDAnnotation)
|
||||
actual, err := filter.Filter(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("InventoryPolicyFilter unexpected error (%s)", err)
|
||||
}
|
||||
if tc.filtered != actual {
|
||||
t.Errorf("InventoryPolicyFilter expected filter (%t), got (%t)", tc.filtered, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
// LocalNamespacesFilter encapsulates the set of namespaces
|
||||
// that are currently in use. Used to ensure we do not delete
|
||||
// namespaces with currently applied objects in them.
|
||||
type LocalNamespacesFilter struct {
|
||||
LocalNamespaces sets.String
|
||||
}
|
||||
|
||||
// Name returns a filter identifier for logging.
|
||||
func (lnf LocalNamespacesFilter) Name() string {
|
||||
return "LocalNamespacesFilter"
|
||||
}
|
||||
|
||||
// Filter returns true if the passed object should NOT be pruned (deleted)
|
||||
// because the it is a namespace that objects still reside in; otherwise
|
||||
// returns false. This filter should not be added to the list of filters
|
||||
// for "destroying", since every object is being delete. Never returns an error.
|
||||
func (lnf LocalNamespacesFilter) Filter(obj *unstructured.Unstructured) (bool, error) {
|
||||
id := object.UnstructuredToObjMeta(obj)
|
||||
if id.GroupKind == object.CoreV1Namespace.GroupKind() &&
|
||||
lnf.LocalNamespaces.Has(id.Name) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
var testNamespace = &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Namespace",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-namespace",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestLocalNamespacesFilter(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
localNamespaces sets.String
|
||||
namespace string
|
||||
filtered bool
|
||||
}{
|
||||
"No local namespaces, namespace is not filtered": {
|
||||
localNamespaces: sets.NewString(),
|
||||
namespace: "test-namespace",
|
||||
filtered: false,
|
||||
},
|
||||
"Namespace not in local namespaces, namespace is not filtered": {
|
||||
localNamespaces: sets.NewString("foo", "bar"),
|
||||
namespace: "test-namespace",
|
||||
filtered: false,
|
||||
},
|
||||
"Namespace is in local namespaces, namespace is filtered": {
|
||||
localNamespaces: sets.NewString("foo", "test-namespace", "bar"),
|
||||
namespace: "test-namespace",
|
||||
filtered: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
filter := LocalNamespacesFilter{
|
||||
LocalNamespaces: tc.localNamespaces,
|
||||
}
|
||||
namespace := testNamespace.DeepCopy()
|
||||
namespace.SetName(tc.namespace)
|
||||
actual, err := filter.Filter(namespace)
|
||||
if err != nil {
|
||||
t.Fatalf("LocalNamespacesFilter unexpected error (%s)", err)
|
||||
}
|
||||
if tc.filtered != actual {
|
||||
t.Errorf("LocalNamespacesFilter expected filter (%t), got (%t)", tc.filtered, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
)
|
||||
|
||||
// PreventRemoveFilter implements ValidationFilter interface to determine
|
||||
// if an object should not be pruned (deleted) because of a
|
||||
// "prevent remove" annotation.
|
||||
type PreventRemoveFilter struct{}
|
||||
|
||||
// Name returns the preferred name for the filter. Usually
|
||||
// used for logging.
|
||||
func (prf PreventRemoveFilter) Name() string {
|
||||
return "PreventRemoveFilter"
|
||||
}
|
||||
|
||||
// Filter returns true if the passed object should NOT be pruned (deleted)
|
||||
// because the "prevent remove" annotation is present; otherwise returns
|
||||
// false. Never returns an error.
|
||||
func (prf PreventRemoveFilter) Filter(obj *unstructured.Unstructured) (bool, error) {
|
||||
for annotation, value := range obj.GetAnnotations() {
|
||||
if common.NoDeletion(annotation, value) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
)
|
||||
|
||||
var defaultObj = &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "pod-name",
|
||||
"namespace": "test-namespace",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestPreventDeleteAnnotation(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
annotations map[string]string
|
||||
expected bool
|
||||
}{
|
||||
"Nil map returns false": {
|
||||
annotations: nil,
|
||||
expected: false,
|
||||
},
|
||||
"Empty map returns false": {
|
||||
annotations: map[string]string{},
|
||||
expected: false,
|
||||
},
|
||||
"Wrong annotation key/value is false": {
|
||||
annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"Annotation key without value is false": {
|
||||
annotations: map[string]string{
|
||||
common.OnRemoveAnnotation: "bar",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"Annotation key and value is true": {
|
||||
annotations: map[string]string{
|
||||
common.OnRemoveAnnotation: common.OnRemoveKeep,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
"Annotation key client.lifecycle.config.k8s.io/deletion without value is false": {
|
||||
annotations: map[string]string{
|
||||
common.LifecycleDeleteAnnotation: "any",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"Annotation key client.lifecycle.config.k8s.io/deletion and value is true": {
|
||||
annotations: map[string]string{
|
||||
common.LifecycleDeleteAnnotation: common.PreventDeletion,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
filter := PreventRemoveFilter{}
|
||||
obj := defaultObj.DeepCopy()
|
||||
obj.SetAnnotations(tc.annotations)
|
||||
actual, err := filter.Filter(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("PreventRemoveFilter unexpected error (%s)", err)
|
||||
}
|
||||
if tc.expected != actual {
|
||||
t.Errorf("PreventRemoveFilter expected (%t), got (%t)", tc.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -13,16 +13,15 @@ package prune
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubectl/pkg/cmd/util"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/filter"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
"sigs.k8s.io/cli-utils/pkg/inventory"
|
||||
|
|
@ -39,8 +38,6 @@ type PruneOptions struct {
|
|||
// True if we are destroying, which deletes the inventory object
|
||||
// as well (possibly) the inventory namespace.
|
||||
Destroy bool
|
||||
// TODO(seans): Replace this with Filter interface to generalize prune skipping.
|
||||
LocalNamespaces sets.String
|
||||
}
|
||||
|
||||
// NewPruneOptions returns a struct (PruneOptions) encapsulating the necessary
|
||||
|
|
@ -48,8 +45,7 @@ type PruneOptions struct {
|
|||
// gathering this information.
|
||||
func NewPruneOptions() *PruneOptions {
|
||||
po := &PruneOptions{
|
||||
Destroy: false,
|
||||
LocalNamespaces: sets.NewString(),
|
||||
Destroy: false,
|
||||
}
|
||||
return po
|
||||
}
|
||||
|
|
@ -77,55 +73,57 @@ type Options struct {
|
|||
DryRunStrategy common.DryRunStrategy
|
||||
|
||||
PropagationPolicy metav1.DeletionPropagation
|
||||
|
||||
// InventoryPolicy defines the inventory policy of prune.
|
||||
InventoryPolicy inventory.InventoryPolicy
|
||||
}
|
||||
|
||||
// Prune deletes the set of passed pruneObjs.
|
||||
// Prune deletes the set of passed pruneObjs. A prune skip/failure is
|
||||
// captured in the TaskContext, so we do not lose track of these
|
||||
// objects from the inventory. The passed prune filters are used to
|
||||
// determine if permission exists to delete the object. An example
|
||||
// of a prune filter is PreventDeleteFilter, which checks if an
|
||||
// annotation exists on the object to ensure the objects is not
|
||||
// deleted (e.g. a PersistentVolume that we do no want to
|
||||
// automatically prune/delete).
|
||||
//
|
||||
// Parameters:
|
||||
// localInv - locally read inventory object
|
||||
// pruneObjs - objects to prune (delete)
|
||||
// currentUIDs - UIDs for successfully applied objects
|
||||
// pruneFilters - list of filters for deletion permission
|
||||
// taskContext - task for apply/prune
|
||||
func (po *PruneOptions) Prune(localInv inventory.InventoryInfo,
|
||||
pruneObjs []*unstructured.Unstructured,
|
||||
currentUIDs sets.String,
|
||||
// o - options for dry-run
|
||||
func (po *PruneOptions) Prune(pruneObjs []*unstructured.Unstructured,
|
||||
pruneFilters []filter.ValidationFilter,
|
||||
taskContext *taskrunner.TaskContext,
|
||||
o Options) error {
|
||||
// Validate parameters
|
||||
if localInv == nil {
|
||||
return fmt.Errorf("the local inventory object can't be nil")
|
||||
}
|
||||
// Sort the resources in reverse order using the same rules as is
|
||||
// used for apply.
|
||||
eventFactory := CreateEventFactory(po.Destroy)
|
||||
// Iterate through objects to prune (delete). If an object is not pruned
|
||||
// and we need to keep it in the inventory, we must capture the prune failure.
|
||||
for _, pruneObj := range pruneObjs {
|
||||
pruneID := object.UnstructuredToObjMeta(pruneObj)
|
||||
klog.V(5).Infof("attempting prune: %s", pruneID)
|
||||
// Do not prune objects that are in set of currently applied objects.
|
||||
uid := string(pruneObj.GetUID())
|
||||
if currentUIDs.Has(uid) {
|
||||
klog.V(5).Infof("prune object in current apply; do not prune: %s", uid)
|
||||
continue
|
||||
}
|
||||
// Handle lifecycle directive preventing deletion.
|
||||
if !canPrune(localInv, pruneObj, o.InventoryPolicy, uid) {
|
||||
klog.V(4).Infof("skip prune for lifecycle directive %s", pruneID)
|
||||
taskContext.EventChannel() <- eventFactory.CreateSkippedEvent(pruneObj)
|
||||
taskContext.CapturePruneFailure(pruneID)
|
||||
continue
|
||||
}
|
||||
// If regular pruning (not destroying), skip deleting namespace containing
|
||||
// currently applied objects.
|
||||
if pruneID.GroupKind == object.CoreV1Namespace.GroupKind() &&
|
||||
po.LocalNamespaces.Has(pruneID.Name) {
|
||||
klog.V(4).Infof("skip pruning namespace: %s", pruneID.Name)
|
||||
taskContext.EventChannel() <- eventFactory.CreateSkippedEvent(pruneObj)
|
||||
taskContext.CapturePruneFailure(pruneID)
|
||||
// Check filters to see if we're prevented from pruning/deleting object.
|
||||
var filtered bool
|
||||
var err error
|
||||
for _, filter := range pruneFilters {
|
||||
klog.V(6).Infof("prune filter %s: %s", filter.Name(), pruneID)
|
||||
filtered, err = filter.Filter(pruneObj)
|
||||
if err != nil {
|
||||
if klog.V(5).Enabled() {
|
||||
klog.Errorf("error during %s, (%s): %s", filter.Name(), pruneID, err)
|
||||
}
|
||||
taskContext.EventChannel() <- eventFactory.CreateFailedEvent(pruneID, err)
|
||||
taskContext.CapturePruneFailure(pruneID)
|
||||
break
|
||||
}
|
||||
if filtered {
|
||||
klog.V(4).Infof("prune filtered by %s: %s", filter.Name(), pruneID)
|
||||
taskContext.EventChannel() <- eventFactory.CreateSkippedEvent(pruneObj)
|
||||
taskContext.CapturePruneFailure(pruneID)
|
||||
break
|
||||
}
|
||||
}
|
||||
if filtered || err != nil {
|
||||
continue
|
||||
}
|
||||
// Filters passed--actually delete object if not dry run.
|
||||
if !o.DryRunStrategy.ClientOrServerDryRun() {
|
||||
klog.V(4).Infof("prune object delete: %s", pruneID)
|
||||
namespacedClient, err := po.namespacedClient(pruneID)
|
||||
|
|
@ -193,27 +191,3 @@ func (po *PruneOptions) namespacedClient(obj object.ObjMetadata) (dynamic.Resour
|
|||
}
|
||||
return po.Client.Resource(mapping.Resource).Namespace(obj.Namespace), nil
|
||||
}
|
||||
|
||||
// preventDeleteAnnotation returns true if the "onRemove:keep"
|
||||
// annotation exists within the annotation map; false otherwise.
|
||||
func preventDeleteAnnotation(annotations map[string]string) bool {
|
||||
for annotation, value := range annotations {
|
||||
if common.NoDeletion(annotation, value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func canPrune(localInv inventory.InventoryInfo, obj *unstructured.Unstructured,
|
||||
policy inventory.InventoryPolicy, uid string) bool {
|
||||
if !inventory.CanPrune(localInv, obj, policy) {
|
||||
klog.V(4).Infof("skip pruning object that doesn't belong to current inventory: %s", uid)
|
||||
return false
|
||||
}
|
||||
if preventDeleteAnnotation(obj.GetAnnotations()) {
|
||||
klog.V(4).Infof("prune object lifecycle directive; do not prune: %s", uid)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/filter"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
"sigs.k8s.io/cli-utils/pkg/inventory"
|
||||
|
|
@ -70,7 +71,7 @@ var pod = &unstructured.Unstructured{
|
|||
"metadata": map[string]interface{}{
|
||||
"name": podName,
|
||||
"namespace": testNamespace,
|
||||
"uid": "uid1",
|
||||
"uid": "pod-uid",
|
||||
"annotations": map[string]interface{}{
|
||||
"config.k8s.io/owning-inventory": testInventoryLabel,
|
||||
},
|
||||
|
|
@ -160,39 +161,33 @@ var (
|
|||
defaultOptions = Options{
|
||||
DryRunStrategy: common.DryRunNone,
|
||||
PropagationPolicy: metav1.DeletePropagationBackground,
|
||||
InventoryPolicy: inventory.InventoryPolicyMustMatch,
|
||||
}
|
||||
clientDryRunOptions = Options{
|
||||
DryRunStrategy: common.DryRunClient,
|
||||
PropagationPolicy: metav1.DeletePropagationBackground,
|
||||
InventoryPolicy: inventory.InventoryPolicyMustMatch,
|
||||
}
|
||||
serverDryRunOptions = Options{
|
||||
DryRunStrategy: common.DryRunServer,
|
||||
PropagationPolicy: metav1.DeletePropagationBackground,
|
||||
InventoryPolicy: inventory.InventoryPolicyMustMatch,
|
||||
}
|
||||
)
|
||||
|
||||
func TestPrune(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
pruneObjs []*unstructured.Unstructured
|
||||
destroy bool
|
||||
options Options
|
||||
currentUIDs sets.String
|
||||
localNamespaces sets.String
|
||||
expectedEvents []testutil.ExpEvent
|
||||
pruneObjs []*unstructured.Unstructured
|
||||
pruneFilters []filter.ValidationFilter
|
||||
destroy bool
|
||||
options Options
|
||||
expectedEvents []testutil.ExpEvent
|
||||
}{
|
||||
"No pruned objects; no prune/delete events": {
|
||||
pruneObjs: []*unstructured.Unstructured{},
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
expectedEvents: []testutil.ExpEvent{},
|
||||
pruneObjs: []*unstructured.Unstructured{},
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{},
|
||||
},
|
||||
"One successfully pruned object": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
|
|
@ -203,9 +198,8 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"Multiple successfully pruned object": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod, pdb, namespace},
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{pod, pdb, namespace},
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
|
|
@ -228,10 +222,9 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"One successfully deleted object": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
destroy: true,
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
destroy: true,
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.DeleteType,
|
||||
|
|
@ -242,10 +235,9 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"Multiple successfully deleted objects": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod, pdb, namespace},
|
||||
destroy: true,
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{pod, pdb, namespace},
|
||||
destroy: true,
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.DeleteType,
|
||||
|
|
@ -268,9 +260,8 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"Client dry run still pruned event": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
options: clientDryRunOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
options: clientDryRunOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
|
|
@ -281,10 +272,9 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"Server dry run still deleted event": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
destroy: true,
|
||||
options: serverDryRunOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
destroy: true,
|
||||
options: serverDryRunOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.DeleteType,
|
||||
|
|
@ -294,19 +284,40 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"UID match means no prune": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
currentUIDs: sets.NewString("uid1"),
|
||||
expectedEvents: []testutil.ExpEvent{},
|
||||
},
|
||||
"UID match for only one object means only one pruned": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod, pdb},
|
||||
options: defaultOptions,
|
||||
currentUIDs: sets.NewString("uid1"),
|
||||
localNamespaces: sets.NewString(),
|
||||
"UID match means prune skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod},
|
||||
pruneFilters: []filter.ValidationFilter{
|
||||
filter.CurrentUIDFilter{
|
||||
// Add pod UID to set of current UIDs
|
||||
CurrentUIDs: sets.NewString("pod-uid"),
|
||||
},
|
||||
},
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
PruneEvent: &testutil.ExpPruneEvent{
|
||||
Operation: event.PruneSkipped,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"UID match for only one object one pruned, one skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{pod, pdb},
|
||||
pruneFilters: []filter.ValidationFilter{
|
||||
filter.CurrentUIDFilter{
|
||||
// Add pod UID to set of current UIDs
|
||||
CurrentUIDs: sets.NewString("pod-uid"),
|
||||
},
|
||||
},
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
PruneEvent: &testutil.ExpPruneEvent{
|
||||
Operation: event.PruneSkipped,
|
||||
},
|
||||
},
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
PruneEvent: &testutil.ExpPruneEvent{
|
||||
|
|
@ -316,9 +327,9 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"Prevent delete annotation equals prune skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{preventDelete},
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{preventDelete},
|
||||
pruneFilters: []filter.ValidationFilter{filter.PreventRemoveFilter{}},
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
|
|
@ -329,10 +340,10 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"Prevent delete annotation equals delete skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{preventDelete},
|
||||
destroy: true,
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{preventDelete},
|
||||
pruneFilters: []filter.ValidationFilter{filter.PreventRemoveFilter{}},
|
||||
destroy: true,
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.DeleteType,
|
||||
|
|
@ -343,9 +354,9 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"Prevent delete annotation, one skipped, one pruned": {
|
||||
pruneObjs: []*unstructured.Unstructured{preventDelete, pod},
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(),
|
||||
pruneObjs: []*unstructured.Unstructured{preventDelete, pod},
|
||||
pruneFilters: []filter.ValidationFilter{filter.PreventRemoveFilter{}},
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
|
|
@ -362,9 +373,13 @@ func TestPrune(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"Namespace prune skipped": {
|
||||
pruneObjs: []*unstructured.Unstructured{namespace},
|
||||
options: defaultOptions,
|
||||
localNamespaces: sets.NewString(namespace.GetName()),
|
||||
pruneObjs: []*unstructured.Unstructured{namespace},
|
||||
pruneFilters: []filter.ValidationFilter{
|
||||
filter.LocalNamespacesFilter{
|
||||
LocalNamespaces: sets.NewString(namespace.GetName()),
|
||||
},
|
||||
},
|
||||
options: defaultOptions,
|
||||
expectedEvents: []testutil.ExpEvent{
|
||||
{
|
||||
EventType: event.PruneType,
|
||||
|
|
@ -380,11 +395,9 @@ func TestPrune(t *testing.T) {
|
|||
t.Run(name, func(t *testing.T) {
|
||||
po := NewPruneOptions()
|
||||
po.Destroy = tc.destroy
|
||||
po.LocalNamespaces = tc.localNamespaces
|
||||
pruneIds := object.UnstructuredsToObjMetas(tc.pruneObjs)
|
||||
fakeInvClient := inventory.NewFakeInventoryClient(pruneIds)
|
||||
po.InvClient = fakeInvClient
|
||||
currentInventory := createInventoryInfo(tc.pruneObjs...)
|
||||
// Set up the fake dynamic client to recognize all objects, and the RESTMapper.
|
||||
objs := []runtime.Object{}
|
||||
for _, obj := range tc.pruneObjs {
|
||||
|
|
@ -395,12 +408,12 @@ func TestPrune(t *testing.T) {
|
|||
scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||
// The event channel can not block; make sure its bigger than all
|
||||
// the events that can be put on it.
|
||||
eventChannel := make(chan event.Event, len(tc.pruneObjs))
|
||||
eventChannel := make(chan event.Event, len(tc.pruneObjs)+1)
|
||||
taskContext := taskrunner.NewTaskContext(eventChannel)
|
||||
err := func() error {
|
||||
defer close(eventChannel)
|
||||
// Run the prune and validate.
|
||||
return po.Prune(currentInventory, tc.pruneObjs, tc.currentUIDs, taskContext, tc.options)
|
||||
return po.Prune(tc.pruneObjs, tc.pruneFilters, taskContext, tc.options)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -456,7 +469,6 @@ func TestPruneWithErrors(t *testing.T) {
|
|||
pruneIds := object.UnstructuredsToObjMetas(tc.pruneObjs)
|
||||
fakeInvClient := inventory.NewFakeInventoryClient(pruneIds)
|
||||
po.InvClient = fakeInvClient
|
||||
currentInventory := createInventoryInfo(tc.pruneObjs...)
|
||||
// Set up the fake dynamic client to recognize all objects, and the RESTMapper.
|
||||
po.Client = &fakeDynamicFailureClient{dynamic: fake.NewSimpleDynamicClient(scheme.Scheme,
|
||||
namespace, pdb, role)}
|
||||
|
|
@ -469,7 +481,7 @@ func TestPruneWithErrors(t *testing.T) {
|
|||
err := func() error {
|
||||
defer close(eventChannel)
|
||||
// Run the prune and validate.
|
||||
return po.Prune(currentInventory, tc.pruneObjs, sets.NewString(), taskContext, defaultOptions)
|
||||
return po.Prune(tc.pruneObjs, []filter.ValidationFilter{}, taskContext, defaultOptions)
|
||||
}()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error during Prune(): %#v", err)
|
||||
|
|
@ -554,60 +566,6 @@ func TestGetPruneObjs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPreventDeleteAnnotation(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
annotations map[string]string
|
||||
expected bool
|
||||
}{
|
||||
"Nil map returns false": {
|
||||
annotations: nil,
|
||||
expected: false,
|
||||
},
|
||||
"Empty map returns false": {
|
||||
annotations: map[string]string{},
|
||||
expected: false,
|
||||
},
|
||||
"Wrong annotation key/value is false": {
|
||||
annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"Annotation key without value is false": {
|
||||
annotations: map[string]string{
|
||||
common.OnRemoveAnnotation: "bar",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"Annotation key and value is true": {
|
||||
annotations: map[string]string{
|
||||
common.OnRemoveAnnotation: common.OnRemoveKeep,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
"Annotation key client.lifecycle.config.k8s.io/deletion without value is false": {
|
||||
annotations: map[string]string{
|
||||
common.LifecycleDeleteAnnotation: "any",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"Annotation key client.lifecycle.config.k8s.io/deletion and value is true": {
|
||||
annotations: map[string]string{
|
||||
common.LifecycleDeleteAnnotation: common.PreventDeletion,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actual := preventDeleteAnnotation(tc.annotations)
|
||||
if tc.expected != actual {
|
||||
t.Errorf("preventDeleteAnnotation Expected (%t), got (%t)", tc.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeDynamicFailureClient struct {
|
||||
dynamic dynamic.Interface
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubectl/pkg/cmd/util"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/filter"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/info"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/prune"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/task"
|
||||
|
|
@ -180,17 +181,17 @@ func (t *TaskQueueBuilder) AppendWaitTask(waitIds []object.ObjMetadata) *TaskQue
|
|||
|
||||
// AppendInvAddTask appends a task to delete objects from the cluster to the task queue.
|
||||
// Returns a pointer to the Builder to chain function calls.
|
||||
func (t *TaskQueueBuilder) AppendPruneTask(inv inventory.InventoryInfo, pruneObjs []*unstructured.Unstructured, o Options) *TaskQueueBuilder {
|
||||
func (t *TaskQueueBuilder) AppendPruneTask(pruneObjs []*unstructured.Unstructured,
|
||||
pruneFilters []filter.ValidationFilter, o Options) *TaskQueueBuilder {
|
||||
klog.V(5).Infoln("adding prune task")
|
||||
t.tasks = append(t.tasks,
|
||||
&task.PruneTask{
|
||||
TaskName: fmt.Sprintf("prune-%d", t.pruneCounter),
|
||||
Objects: pruneObjs,
|
||||
InventoryObject: inv,
|
||||
Filters: pruneFilters,
|
||||
PruneOptions: t.PruneOptions,
|
||||
PropagationPolicy: o.PrunePropagationPolicy,
|
||||
DryRunStrategy: o.DryRunStrategy,
|
||||
InventoryPolicy: o.InventoryPolicy,
|
||||
},
|
||||
)
|
||||
t.pruneCounter += 1
|
||||
|
|
@ -221,9 +222,10 @@ func (t *TaskQueueBuilder) AppendApplyWaitTasks(inv inventory.InventoryInfo, app
|
|||
// AppendPruneWaitTasks adds prune and wait tasks to the task queue
|
||||
// based on build variables (like dry-run). Returns a pointer to the
|
||||
// Builder to chain function calls.
|
||||
func (t *TaskQueueBuilder) AppendPruneWaitTasks(inv inventory.InventoryInfo, pruneObjs []*unstructured.Unstructured, o Options) *TaskQueueBuilder {
|
||||
func (t *TaskQueueBuilder) AppendPruneWaitTasks(pruneObjs []*unstructured.Unstructured,
|
||||
pruneFilters []filter.ValidationFilter, o Options) *TaskQueueBuilder {
|
||||
if o.Prune {
|
||||
t.AppendPruneTask(inv, pruneObjs, o)
|
||||
t.AppendPruneTask(pruneObjs, pruneFilters, o)
|
||||
if !o.DryRunStrategy.ClientOrServerDryRun() && o.PruneTimeout != time.Duration(0) {
|
||||
pruneIds := object.UnstructuredsToObjMetas(pruneObjs)
|
||||
t.AppendWaitTask(pruneIds)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"gotest.tools/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/filter"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/prune"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/task"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
|
||||
|
|
@ -267,7 +268,7 @@ func TestTaskQueueBuilder_BuildTaskQueue(t *testing.T) {
|
|||
tq := tqb.
|
||||
AppendInvAddTask(localInv, tc.objs).
|
||||
AppendApplyWaitTasks(localInv, tc.objs, tc.options).
|
||||
AppendPruneWaitTasks(localInv, emptyPruneObjs, tc.options).
|
||||
AppendPruneWaitTasks(emptyPruneObjs, []filter.ValidationFilter{}, tc.options).
|
||||
AppendInvSetTask(localInv).
|
||||
Build()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/filter"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/prune"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
"sigs.k8s.io/cli-utils/pkg/inventory"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
|
|
@ -21,11 +21,10 @@ type PruneTask struct {
|
|||
TaskName string
|
||||
|
||||
PruneOptions *prune.PruneOptions
|
||||
InventoryObject inventory.InventoryInfo
|
||||
Objects []*unstructured.Unstructured
|
||||
Filters []filter.ValidationFilter
|
||||
DryRunStrategy common.DryRunStrategy
|
||||
PropagationPolicy metav1.DeletionPropagation
|
||||
InventoryPolicy inventory.InventoryPolicy
|
||||
}
|
||||
|
||||
func (p *PruneTask) Name() string {
|
||||
|
|
@ -50,12 +49,16 @@ func (p *PruneTask) Identifiers() []object.ObjMetadata {
|
|||
// to signal to the taskrunner that the task has completed (or failed).
|
||||
func (p *PruneTask) Start(taskContext *taskrunner.TaskContext) {
|
||||
go func() {
|
||||
currentUIDs := taskContext.AllResourceUIDs()
|
||||
err := p.PruneOptions.Prune(p.InventoryObject, p.Objects,
|
||||
currentUIDs, taskContext, prune.Options{
|
||||
// Create filter to prevent deletion of currently applied
|
||||
// objects. Must be done here to wait for applied UIDs.
|
||||
uidFilter := filter.CurrentUIDFilter{
|
||||
CurrentUIDs: taskContext.AllResourceUIDs(),
|
||||
}
|
||||
p.Filters = append(p.Filters, uidFilter)
|
||||
err := p.PruneOptions.Prune(p.Objects,
|
||||
p.Filters, taskContext, prune.Options{
|
||||
DryRunStrategy: p.DryRunStrategy,
|
||||
PropagationPolicy: p.PropagationPolicy,
|
||||
InventoryPolicy: p.InventoryPolicy,
|
||||
})
|
||||
taskContext.TaskChannel() <- taskrunner.TaskResult{
|
||||
Err: err,
|
||||
|
|
|
|||
Loading…
Reference in New Issue