Skip to content

Commit

Permalink
Merge pull request #369 from seans3/apply-prune-filter
Browse files Browse the repository at this point in the history
Refactor prune/delete using ValidationFilter interface
  • Loading branch information
k8s-ci-robot authored Jun 17, 2021
2 parents 70b9f67 + a3f3792 commit c726dba
Show file tree
Hide file tree
Showing 18 changed files with 621 additions and 205 deletions.
16 changes: 13 additions & 3 deletions pkg/apply/applier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
11 changes: 9 additions & 2 deletions pkg/apply/destroyer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions pkg/apply/filter/current-uids-filter.go
Original file line number Diff line number Diff line change
@@ -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
}
62 changes: 62 additions & 0 deletions pkg/apply/filter/current-uids-filter_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
19 changes: 19 additions & 0 deletions pkg/apply/filter/filter.go
Original file line number Diff line number Diff line change
@@ -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)
}
34 changes: 34 additions & 0 deletions pkg/apply/filter/inventory-policy-filter.go
Original file line number Diff line number Diff line change
@@ -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
}
101 changes: 101 additions & 0 deletions pkg/apply/filter/inventory-policy-filter_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
35 changes: 35 additions & 0 deletions pkg/apply/filter/local-namespaces-filter.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit c726dba

Please sign in to comment.