Handle deletion prevention correctly

If an object with the deletion prevention annotation is removed from the
inventory, the config.k8s.io/owning-inventory annotation should be
removed from the object, and the object should be removed from the
inventory.
This commit is contained in:
Haiyan Meng 2021-10-07 13:08:09 -07:00
parent 223d4d51b4
commit 7c27474df5
9 changed files with 354 additions and 47 deletions

View File

@ -34,10 +34,14 @@ function expectedOutputLine() {
}
```
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
In this example we will just use three ConfigMap resources for simplicity, but
of course any type of resource can be used.
- the first ConfigMap resource does not have any annotations;
- the second ConfigMap resource has the **cli-utils.sigs.k8s.io/on-remove** annotation with the value of **keep**;
- the third ConfigMap resource has the **client.lifecycle.config.k8s.io/deletion** annotation with the value of **detach**.
These two annotations tell the kapply tool that a resource should not be deleted, even
if it would otherwise be pruned or deleted with the destroy command.
<!-- @createFirstCM @testE2EAgainstLatestRelease-->
@ -53,7 +57,7 @@ data:
EOF
```
This ConfigMap includes the lifecycle directive annotation
This ConfigMap includes the **cli-utils.sigs.k8s.io/on-remove** annotation
<!-- @createSecondCM @testE2EAgainstLatestRelease-->
```
@ -70,6 +74,24 @@ data:
EOF
```
This ConfigMap includes the **client.lifecycle.config.k8s.io/deletion** annotation
<!-- @createSecondCM @testE2EAgainstLatestRelease-->
```
cat <<EOF >$BASE/configMap3.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: thirdmap
annotations:
client.lifecycle.config.k8s.io/deletion: detach
data:
artist: Husker Du
album: New Day Rising
EOF
```
## Run end-to-end tests
The following requires installation of [kind].
@ -90,14 +112,14 @@ expectedOutputLine "namespace: default is used for inventory object"
```
Apply both resources to the cluster.
Apply the three 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
show that secondmap and thirdmap will not be deleted even when using the destroy
command.
<!-- @runDestroyPreview @testE2EAgainstLatestRelease -->
```
@ -106,10 +128,12 @@ kapply preview --destroy $BASE > $OUTPUT/status
expectedOutputLine "configmap/firstmap deleted (preview)"
expectedOutputLine "configmap/secondmap delete skipped (preview)"
expectedOutputLine "configmap/thirdmap 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
We run the destroy command and see that the resource without the annotations (firstmap)
has been deleted, while the resources with the annotations (secondmap and thirdmap) are still in the
cluster.
<!-- @runDestroy @testE2EAgainstLatestRelease -->
```
@ -119,45 +143,58 @@ expectedOutputLine "configmap/firstmap deleted"
expectedOutputLine "configmap/secondmap delete skipped"
expectedOutputLine "1 resource(s) deleted, 1 skipped"
expectedOutputLine "configmap/thirdmap delete skipped"
expectedOutputLine "1 resource(s) deleted, 2 skipped"
expectedNotFound "resource(s) pruned"
kubectl get cm --no-headers | awk '{print $1}' > $OUTPUT/status
expectedOutputLine "secondmap"
```
kubectl get cm --no-headers | awk '{print $1}' > $OUTPUT/status
expectedOutputLine "thirdmap"
```
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
kapply apply $BASE --inventory-policy=adopt --reconcile-timeout=1m > $OUTPUT/status
```
Delete the manifest for the second configmap
Delete the manifest for secondmap and thirdmap
<!-- @runDeleteManifest @testE2EAgainstLatestRelease -->
```
rm $BASE/configMap2.yaml
rm $BASE/configMap3.yaml
```
Run preview to see that while secondmap would normally be pruned, it
Run preview to see that while secondmap and thirdmap would normally be pruned, they
will instead be skipped due to the lifecycle directive.
<!-- @runPreviewForPrune @testE2EAgainstLatestRelease -->
```
kapply preview $BASE > $OUTPUT/status
expectedOutputLine "configmap/secondmap prune skipped (preview)"
expectedOutputLine "configmap/thirdmap prune skipped (preview)"
```
Run apply and verify that secondmap is still in the cluster.
Run apply and verify that secondmap and thirdmap are still in the cluster.
<!-- @runApplyToPrune @testE2EAgainstLatestRelease -->
```
kapply apply $BASE > $OUTPUT/status
expectedOutputLine "configmap/secondmap prune skipped"
expectedOutputLine "configmap/thirdmap prune skipped"
kubectl get cm --no-headers | awk '{print $1}' > $OUTPUT/status
expectedOutputLine "secondmap"
kubectl get cm --no-headers | awk '{print $1}' > $OUTPUT/status
expectedOutputLine "thirdmap"
kind delete cluster;
```

View File

@ -15,10 +15,12 @@ import (
// "prevent remove" annotation.
type PreventRemoveFilter struct{}
const PreventRemoveFilterName = "PreventRemoveFilter"
// Name returns the preferred name for the filter. Usually
// used for logging.
func (prf PreventRemoveFilter) Name() string {
return "PreventRemoveFilter"
return PreventRemoveFilterName
}
// Filter returns true if the passed object should NOT be pruned (deleted)

View File

@ -103,21 +103,42 @@ func (po *PruneOptions) Prune(pruneObjs []*unstructured.Unstructured,
var filtered bool
var reason string
var err error
for _, filter := range pruneFilters {
klog.V(6).Infof("prune filter %s: %s", filter.Name(), pruneID)
filtered, reason, err = filter.Filter(pruneObj)
for _, pruneFilter := range pruneFilters {
klog.V(6).Infof("prune filter %s: %s", pruneFilter.Name(), pruneID)
filtered, reason, err = pruneFilter.Filter(pruneObj)
if err != nil {
if klog.V(5).Enabled() {
klog.Errorf("error during %s, (%s): %s", filter.Name(), pruneID, err)
klog.Errorf("error during %s, (%s): %s", pruneFilter.Name(), pruneID, err)
}
taskContext.EventChannel() <- eventFactory.CreateFailedEvent(pruneID, err)
taskContext.CapturePruneFailure(pruneID)
break
}
if filtered {
klog.V(4).Infof("prune filtered (filter: %q, resource: %q, reason: %q)", filter.Name(), pruneID, reason)
klog.V(4).Infof("prune filtered (filter: %q, resource: %q, reason: %q)", pruneFilter.Name(), pruneID, reason)
// pruneFailure indicates whether `taskContext.CapturePruneFailure` should be called.
pruneFailure := true
if pruneFilter.Name() == filter.PreventRemoveFilterName {
if o.DryRunStrategy.ClientOrServerDryRun() {
pruneFailure = false
} else {
err := po.handleDeletePrevention(pruneObj)
if err != nil {
if klog.V(4).Enabled() {
klog.Errorf("Failed to remove the %q annotation from %s: %v", inventory.OwningInventoryKey, pruneID, err)
}
taskContext.EventChannel() <- eventFactory.CreateFailedEvent(pruneID, err)
taskContext.CapturePruneFailure(pruneID)
break
} else {
pruneFailure = false
}
}
}
taskContext.EventChannel() <- eventFactory.CreateSkippedEvent(pruneObj, reason)
taskContext.CapturePruneFailure(pruneID)
if pruneFailure {
taskContext.CapturePruneFailure(pruneID)
}
break
}
}
@ -153,6 +174,26 @@ func (po *PruneOptions) Prune(pruneObjs []*unstructured.Unstructured,
return nil
}
// handleDeletePrevention removes the `config.k8s.io/owning-inventory` annotation from pruneObj.
func (po *PruneOptions) handleDeletePrevention(pruneObj *unstructured.Unstructured) error {
pruneID := object.UnstructuredToObjMetaOrDie(pruneObj)
annotations := pruneObj.GetAnnotations()
if annotations != nil {
if _, ok := annotations[inventory.OwningInventoryKey]; ok {
klog.V(4).Infof("remove the %q annotation from the object %s", inventory.OwningInventoryKey, pruneID)
delete(annotations, inventory.OwningInventoryKey)
pruneObj.SetAnnotations(annotations)
namespacedClient, err := po.namespacedClient(pruneID)
if err != nil {
return err
}
_, err = namespacedClient.Update(context.TODO(), pruneObj, metav1.UpdateOptions{})
return err
}
}
return nil
}
// GetPruneObjs calculates the set of prune objects, and retrieves them
// from the cluster. Set of prune objects equals the set of inventory
// objects minus the set of currently applied objects. Returns an error

View File

@ -136,8 +136,8 @@ func createInventoryInfo(children ...*unstructured.Unstructured) inventory.Inven
return inventory.WrapInventoryInfoObj(obj)
}
// preventDelete object contains the "on-remove:keep" lifecycle directive.
var preventDelete = &unstructured.Unstructured{
// podDeletionPrevention object contains the "on-remove:keep" lifecycle directive.
var podDeletionPrevention = &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Pod",
@ -145,13 +145,26 @@ var preventDelete = &unstructured.Unstructured{
"name": "test-prevent-delete",
"namespace": testNamespace,
"annotations": map[string]interface{}{
common.OnRemoveAnnotation: common.OnRemoveKeep,
common.OnRemoveAnnotation: common.OnRemoveKeep,
inventory.OwningInventoryKey: testInventoryLabel,
},
"uid": "prevent-delete",
},
},
}
var pdbDeletePreventionManifest = `
apiVersion: "policy/v1beta1"
kind: PodDisruptionBudget
metadata:
name: pdb-delete-prevention
namespace: test-namespace
uid: uid2
annotations:
client.lifecycle.config.k8s.io/deletion: detach
config.k8s.io/owning-inventory: test-app-label
`
// Options with different dry-run values.
var (
defaultOptions = Options{
@ -324,7 +337,7 @@ func TestPrune(t *testing.T) {
},
},
"Prevent delete annotation equals prune skipped": {
pruneObjs: []*unstructured.Unstructured{preventDelete},
pruneObjs: []*unstructured.Unstructured{podDeletionPrevention, testutil.Unstructured(t, pdbDeletePreventionManifest)},
pruneFilters: []filter.ValidationFilter{filter.PreventRemoveFilter{}},
options: defaultOptions,
expectedEvents: []testutil.ExpEvent{
@ -334,10 +347,16 @@ func TestPrune(t *testing.T) {
Operation: event.PruneSkipped,
},
},
{
EventType: event.PruneType,
PruneEvent: &testutil.ExpPruneEvent{
Operation: event.PruneSkipped,
},
},
},
},
"Prevent delete annotation equals delete skipped": {
pruneObjs: []*unstructured.Unstructured{preventDelete},
pruneObjs: []*unstructured.Unstructured{podDeletionPrevention, testutil.Unstructured(t, pdbDeletePreventionManifest)},
pruneFilters: []filter.ValidationFilter{filter.PreventRemoveFilter{}},
options: defaultOptionsDestroy,
expectedEvents: []testutil.ExpEvent{
@ -347,10 +366,16 @@ func TestPrune(t *testing.T) {
Operation: event.DeleteSkipped,
},
},
{
EventType: event.DeleteType,
DeleteEvent: &testutil.ExpDeleteEvent{
Operation: event.DeleteSkipped,
},
},
},
},
"Prevent delete annotation, one skipped, one pruned": {
pruneObjs: []*unstructured.Unstructured{preventDelete, pod},
pruneObjs: []*unstructured.Unstructured{podDeletionPrevention, pod},
pruneFilters: []filter.ValidationFilter{filter.PreventRemoveFilter{}},
options: defaultOptions,
expectedEvents: []testutil.ExpEvent{
@ -428,6 +453,72 @@ func TestPrune(t *testing.T) {
}
}
func TestPruneDeletionPrevention(t *testing.T) {
tests := map[string]struct {
pruneObj *unstructured.Unstructured
options Options
}{
"an object with the cli-utils.sigs.k8s.io/on-remove annotation (prune)": {
pruneObj: podDeletionPrevention,
options: defaultOptions,
},
"an object with the cli-utils.sigs.k8s.io/on-remove annotation (destroy)": {
pruneObj: podDeletionPrevention,
options: defaultOptionsDestroy,
},
"an object with the client.lifecycle.config.k8s.io/deletion annotation (prune)": {
pruneObj: testutil.Unstructured(t, pdbDeletePreventionManifest),
options: defaultOptions,
},
"an object with the client.lifecycle.config.k8s.io/deletion annotation (destroy)": {
pruneObj: testutil.Unstructured(t, pdbDeletePreventionManifest),
options: defaultOptionsDestroy,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
pruneID, err := object.UnstructuredToObjMeta(tc.pruneObj)
require.NoError(t, err)
po := PruneOptions{
InvClient: inventory.NewFakeInventoryClient(object.ObjMetadataSet{pruneID}),
Client: fake.NewSimpleDynamicClient(scheme.Scheme, tc.pruneObj),
Mapper: testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme,
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, 2)
resourceCache := cache.NewResourceCacheMap()
taskContext := taskrunner.NewTaskContext(eventChannel, resourceCache)
err = func() error {
defer close(eventChannel)
// Run the prune and validate.
return po.Prune([]*unstructured.Unstructured{tc.pruneObj}, []filter.ValidationFilter{filter.PreventRemoveFilter{}}, taskContext, "test-0", tc.options)
}()
if err != nil {
t.Fatalf("Unexpected error during Prune(): %#v", err)
}
// verify that the object no longer has the annotation
obj, err := po.GetObject(pruneID)
if err != nil {
t.Fatalf("Unexpected error: %#v", err)
}
hasOwningInventoryAnnotation := false
for annotation := range obj.GetAnnotations() {
if annotation == inventory.OwningInventoryKey {
hasOwningInventoryAnnotation = true
}
}
if hasOwningInventoryAnnotation {
t.Fatalf("Prune() should remove the %s annotation", inventory.OwningInventoryKey)
}
})
}
}
// failureNamespaceClient wrappers around a namespaceClient with the overwriting to Get and Delete functions.
type failureNamespaceClient struct {
dynamic.ResourceInterface
@ -637,6 +728,30 @@ func TestGetObject_NotFoundError(t *testing.T) {
}
}
func TestHandleDeletePrevention(t *testing.T) {
obj := testutil.Unstructured(t, pdbDeletePreventionManifest)
po := PruneOptions{
Client: fake.NewSimpleDynamicClient(scheme.Scheme, obj, namespace),
Mapper: testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme,
scheme.Scheme.PrioritizedVersionsAllGroups()...),
}
if err := po.handleDeletePrevention(obj); err != nil {
t.Fatalf("unexpected error %s returned", err)
}
// Get the object from the cluster and verify that the `config.k8s.io/owning-inventory` annotation is removed from the object.
liveObj, err := po.GetObject(testutil.ToIdentifier(t, pdbDeletePreventionManifest))
if err != nil {
t.Fatalf("unexpected error %s returned", err)
}
annotations := liveObj.GetAnnotations()
if annotations != nil {
if _, ok := annotations[inventory.OwningInventoryKey]; ok {
t.Fatalf("expected handleDeletePrevention() to remove the %q annotation", inventory.OwningInventoryKey)
}
}
}
type optionsCaptureNamespaceClient struct {
dynamic.ResourceInterface
options metav1.DeleteOptions

View File

@ -66,7 +66,8 @@ const (
AdoptAll
)
const owningInventoryKey = "config.k8s.io/owning-inventory"
// OwningInventoryKey is the annotation key indicating the inventory owning an object.
const OwningInventoryKey = "config.k8s.io/owning-inventory"
// inventoryIDMatchStatus represents the result of comparing the
// id from current inventory info and the inventory-id from a live object.
@ -81,7 +82,7 @@ const (
func InventoryIDMatch(inv InventoryInfo, obj *unstructured.Unstructured) inventoryIDMatchStatus {
annotations := obj.GetAnnotations()
value, found := annotations[owningInventoryKey]
value, found := annotations[OwningInventoryKey]
if !found {
return Empty
}
@ -101,7 +102,7 @@ func CanApply(inv InventoryInfo, obj *unstructured.Unstructured, policy Inventor
if policy != InventoryPolicyMustMatch {
return true, nil
}
err := fmt.Errorf("can't adopt an object without the annotation %s", owningInventoryKey)
err := fmt.Errorf("can't adopt an object without the annotation %s", OwningInventoryKey)
return false, NewNeedAdoptionError(err)
case Match:
return true, nil
@ -109,7 +110,7 @@ func CanApply(inv InventoryInfo, obj *unstructured.Unstructured, policy Inventor
if policy == AdoptAll {
return true, nil
}
err := fmt.Errorf("can't apply the resource since its annotation %s is a different inventory object", owningInventoryKey)
err := fmt.Errorf("can't apply the resource since its annotation %s is a different inventory object", OwningInventoryKey)
return false, NewInventoryOverlapError(err)
}
// shouldn't reach here
@ -137,7 +138,7 @@ func AddInventoryIDAnnotation(obj *unstructured.Unstructured, inv InventoryInfo)
if annotations == nil {
annotations = make(map[string]string)
}
annotations[owningInventoryKey] = inv.ID()
annotations[OwningInventoryKey] = inv.ID()
obj.SetAnnotations(annotations)
}

View File

@ -63,13 +63,13 @@ func TestInventoryIDMatch(t *testing.T) {
},
{
name: "matched",
obj: testObjectWithAnnotation(owningInventoryKey, "matched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "matched"),
inv: &fakeInventoryInfo{id: "matched"},
expected: Match,
},
{
name: "unmatched",
obj: testObjectWithAnnotation(owningInventoryKey, "unmatched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "unmatched"),
inv: &fakeInventoryInfo{id: "random-id"},
expected: NoMatch,
},
@ -119,42 +119,42 @@ func TestCanApply(t *testing.T) {
},
{
name: "matched with InventoryPolicyMustMatch",
obj: testObjectWithAnnotation(owningInventoryKey, "matched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "matched"),
inv: &fakeInventoryInfo{id: "matched"},
policy: InventoryPolicyMustMatch,
canApply: true,
},
{
name: "matched with AdoptIfNoInventory",
obj: testObjectWithAnnotation(owningInventoryKey, "matched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "matched"),
inv: &fakeInventoryInfo{id: "matched"},
policy: AdoptIfNoInventory,
canApply: true,
},
{
name: "matched with AloptAll",
obj: testObjectWithAnnotation(owningInventoryKey, "matched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "matched"),
inv: &fakeInventoryInfo{id: "matched"},
policy: AdoptAll,
canApply: true,
},
{
name: "unmatched with InventoryPolicyMustMatch",
obj: testObjectWithAnnotation(owningInventoryKey, "unmatched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "unmatched"),
inv: &fakeInventoryInfo{id: "random-id"},
policy: InventoryPolicyMustMatch,
canApply: false,
},
{
name: "unmatched with AdoptIfNoInventory",
obj: testObjectWithAnnotation(owningInventoryKey, "unmatched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "unmatched"),
inv: &fakeInventoryInfo{id: "random-id"},
policy: AdoptIfNoInventory,
canApply: false,
},
{
name: "unmatched with AdoptAll",
obj: testObjectWithAnnotation(owningInventoryKey, "unmatched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "unmatched"),
inv: &fakeInventoryInfo{id: "random-id"},
policy: AdoptAll,
canApply: true,
@ -205,42 +205,42 @@ func TestCanPrune(t *testing.T) {
},
{
name: "matched with InventoryPolicyMustMatch",
obj: testObjectWithAnnotation(owningInventoryKey, "matched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "matched"),
inv: &fakeInventoryInfo{id: "matched"},
policy: InventoryPolicyMustMatch,
canPrune: true,
},
{
name: "matched with AdoptIfNoInventory",
obj: testObjectWithAnnotation(owningInventoryKey, "matched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "matched"),
inv: &fakeInventoryInfo{id: "matched"},
policy: AdoptIfNoInventory,
canPrune: true,
},
{
name: "matched with AloptAll",
obj: testObjectWithAnnotation(owningInventoryKey, "matched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "matched"),
inv: &fakeInventoryInfo{id: "matched"},
policy: AdoptAll,
canPrune: true,
},
{
name: "unmatched with InventoryPolicyMustMatch",
obj: testObjectWithAnnotation(owningInventoryKey, "unmatched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "unmatched"),
inv: &fakeInventoryInfo{id: "random-id"},
policy: InventoryPolicyMustMatch,
canPrune: false,
},
{
name: "unmatched with AdoptIfNoInventory",
obj: testObjectWithAnnotation(owningInventoryKey, "unmatched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "unmatched"),
inv: &fakeInventoryInfo{id: "random-id"},
policy: AdoptIfNoInventory,
canPrune: false,
},
{
name: "unmatched with AdoptAll",
obj: testObjectWithAnnotation(owningInventoryKey, "unmatched"),
obj: testObjectWithAnnotation(OwningInventoryKey, "unmatched"),
inv: &fakeInventoryInfo{id: "random-id"},
policy: AdoptAll,
canPrune: true,

View File

@ -36,6 +36,16 @@ func withNamespace(obj *unstructured.Unstructured, namespace string) *unstructur
return obj
}
func withAnnotation(obj *unstructured.Unstructured, key, value string) *unstructured.Unstructured {
annotations := obj.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
annotations[key] = value
obj.SetAnnotations(annotations)
return obj
}
func withDependsOn(obj *unstructured.Unstructured, dep string) *unstructured.Unstructured {
a := obj.GetAnnotations()
if a == nil {

View File

@ -0,0 +1,97 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/cli-utils/pkg/apply"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func deletionPreventionTest(c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) {
By("Apply resources")
applier := invConfig.ApplierFactoryFunc()
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName)
inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID)
resources := []*unstructured.Unstructured{
withNamespace(manifestToUnstructured(deployment1), namespaceName),
withAnnotation(withNamespace(manifestToUnstructured(pod1), namespaceName), common.OnRemoveAnnotation, common.OnRemoveKeep),
withAnnotation(withNamespace(manifestToUnstructured(pod2), namespaceName), common.LifecycleDeleteAnnotation, common.PreventDeletion),
}
runCollect(applier.Run(context.TODO(), inventoryInfo, resources, apply.Options{
ReconcileTimeout: 2 * time.Minute,
}))
By("Verify deployment created")
obj := assertUnstructuredExists(c, withNamespace(manifestToUnstructured(deployment1), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify pod1 created")
obj = assertUnstructuredExists(c, withNamespace(manifestToUnstructured(pod1), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify pod2 created")
obj = assertUnstructuredExists(c, withNamespace(manifestToUnstructured(pod2), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify the inventory size is 3")
invConfig.InvSizeVerifyFunc(c, inventoryName, namespaceName, inventoryID, 3)
resources = []*unstructured.Unstructured{
withNamespace(manifestToUnstructured(deployment1), namespaceName),
}
runCollect(applier.Run(context.TODO(), inventoryInfo, resources, apply.Options{
ReconcileTimeout: 2 * time.Minute,
DryRunStrategy: common.DryRunClient,
}))
By("Verify deployment still exists and has the config.k8s.io/owning-inventory annotation")
obj = assertUnstructuredExists(c, withNamespace(manifestToUnstructured(deployment1), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify pod1 still exits and does not have the config.k8s.io/owning-inventory annotation")
obj = assertUnstructuredExists(c, withNamespace(manifestToUnstructured(pod1), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify pod2 still exits and does not have the config.k8s.io/owning-inventory annotation")
obj = assertUnstructuredExists(c, withNamespace(manifestToUnstructured(pod2), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify the inventory size is still 3")
invConfig.InvSizeVerifyFunc(c, inventoryName, namespaceName, inventoryID, 3)
resources = []*unstructured.Unstructured{
withNamespace(manifestToUnstructured(deployment1), namespaceName),
}
runCollect(applier.Run(context.TODO(), inventoryInfo, resources, apply.Options{
ReconcileTimeout: 2 * time.Minute,
}))
By("Verify deployment still exists and has the config.k8s.io/owning-inventory annotation")
obj = assertUnstructuredExists(c, withNamespace(manifestToUnstructured(deployment1), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(inventoryInfo.ID()))
By("Verify pod1 still exits and does not have the config.k8s.io/owning-inventory annotation")
obj = assertUnstructuredExists(c, withNamespace(manifestToUnstructured(pod1), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(""))
By("Verify pod2 still exits and does not have the config.k8s.io/owning-inventory annotation")
obj = assertUnstructuredExists(c, withNamespace(manifestToUnstructured(pod2), namespaceName))
Expect(obj.GetAnnotations()[inventory.OwningInventoryKey]).To(Equal(""))
By("Verify the inventory size is 1")
invConfig.InvSizeVerifyFunc(c, inventoryName, namespaceName, inventoryID, 1)
}

View File

@ -144,6 +144,10 @@ var _ = Describe("Applier", func() {
applyAndDestroyTest(c, invConfig, inventoryName, namespace.GetName())
})
It("Deletion Prevention", func() {
deletionPreventionTest(c, invConfig, inventoryName, namespace.GetName())
})
It("Apply CRD and CR", func() {
crdTest(c, invConfig, inventoryName, namespace.GetName())
})