mirror of https://github.com/fluxcd/cli-utils.git
fix: Make DependencyFilter handle DryRun
DryRun skips WaitEvents. So the DependencyFilter needs to skip checking for reconciliation if DryRun is enabled.
This commit is contained in:
parent
5c6134aeac
commit
0f7f95b24a
|
|
@ -141,8 +141,9 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.Info, objects objec
|
|||
InvPolicy: options.InventoryPolicy,
|
||||
},
|
||||
filter.DependencyFilter{
|
||||
TaskContext: taskContext,
|
||||
Strategy: actuation.ActuationStrategyApply,
|
||||
TaskContext: taskContext,
|
||||
ActuationStrategy: actuation.ActuationStrategyApply,
|
||||
DryRunStrategy: options.DryRunStrategy,
|
||||
},
|
||||
}
|
||||
// Build list of prune validation filters.
|
||||
|
|
@ -156,8 +157,9 @@ func (a *Applier) Run(ctx context.Context, invInfo inventory.Info, objects objec
|
|||
LocalNamespaces: localNamespaces(invInfo, object.UnstructuredSetToObjMetadataSet(objects)),
|
||||
},
|
||||
filter.DependencyFilter{
|
||||
TaskContext: taskContext,
|
||||
Strategy: actuation.ActuationStrategyDelete,
|
||||
TaskContext: taskContext,
|
||||
ActuationStrategy: actuation.ActuationStrategyDelete,
|
||||
DryRunStrategy: options.DryRunStrategy,
|
||||
},
|
||||
}
|
||||
// Build list of apply mutators.
|
||||
|
|
|
|||
|
|
@ -147,8 +147,9 @@ func (d *Destroyer) Run(ctx context.Context, invInfo inventory.Info, options Des
|
|||
InvPolicy: options.InventoryPolicy,
|
||||
},
|
||||
filter.DependencyFilter{
|
||||
TaskContext: taskContext,
|
||||
Strategy: actuation.ActuationStrategyDelete,
|
||||
TaskContext: taskContext,
|
||||
ActuationStrategy: actuation.ActuationStrategyDelete,
|
||||
DryRunStrategy: options.DryRunStrategy,
|
||||
},
|
||||
}
|
||||
taskBuilder := &solver.TaskQueueBuilder{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
|
||||
"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
|
|
@ -24,8 +25,9 @@ const (
|
|||
// DependencyFilter implements ValidationFilter interface to determine if an
|
||||
// object can be applied or deleted based on the status of it's dependencies.
|
||||
type DependencyFilter struct {
|
||||
TaskContext *taskrunner.TaskContext
|
||||
Strategy actuation.ActuationStrategy
|
||||
TaskContext *taskrunner.TaskContext
|
||||
ActuationStrategy actuation.ActuationStrategy
|
||||
DryRunStrategy common.DryRunStrategy
|
||||
}
|
||||
|
||||
const DependencyFilterName = "DependencyFilter"
|
||||
|
|
@ -40,7 +42,7 @@ func (dnrf DependencyFilter) Name() string {
|
|||
func (dnrf DependencyFilter) Filter(obj *unstructured.Unstructured) (bool, string, error) {
|
||||
id := object.UnstructuredToObjMetadata(obj)
|
||||
|
||||
switch dnrf.Strategy {
|
||||
switch dnrf.ActuationStrategy {
|
||||
case actuation.ActuationStrategyApply:
|
||||
// For apply, check dependencies (outgoing)
|
||||
for _, depID := range dnrf.TaskContext.Graph().Dependencies(id) {
|
||||
|
|
@ -64,7 +66,7 @@ func (dnrf DependencyFilter) Filter(obj *unstructured.Unstructured) (bool, strin
|
|||
}
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid filter strategy: %q", dnrf.Strategy))
|
||||
panic(fmt.Sprintf("invalid filter strategy: %q", dnrf.ActuationStrategy))
|
||||
}
|
||||
return false, "", nil
|
||||
}
|
||||
|
|
@ -91,9 +93,9 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
|
|||
|
||||
// Dependencies must have the same actuation strategy.
|
||||
// If there is a mismatch, skip both.
|
||||
if status.Strategy != dnrf.Strategy {
|
||||
if status.Strategy != dnrf.ActuationStrategy {
|
||||
return true, fmt.Sprintf("%s skipped because %s is scheduled for %s: %q",
|
||||
strings.ToLower(dnrf.Strategy.String()),
|
||||
strings.ToLower(dnrf.ActuationStrategy.String()),
|
||||
strings.ToLower(relationship.String()),
|
||||
strings.ToLower(status.Strategy.String()),
|
||||
id), nil
|
||||
|
|
@ -103,7 +105,7 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
|
|||
case actuation.ActuationPending:
|
||||
// If actuation is still pending, dependency sorting is probably broken.
|
||||
return false, "", fmt.Errorf("premature %s: %s %s actuation %s: %q",
|
||||
strings.ToLower(dnrf.Strategy.String()),
|
||||
strings.ToLower(dnrf.ActuationStrategy.String()),
|
||||
strings.ToLower(relationship.String()),
|
||||
strings.ToLower(status.Strategy.String()),
|
||||
strings.ToLower(status.Actuation.String()),
|
||||
|
|
@ -112,7 +114,7 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
|
|||
// Skip!
|
||||
return true, fmt.Sprintf("%s %s actuation %s: %q",
|
||||
strings.ToLower(relationship.String()),
|
||||
strings.ToLower(dnrf.Strategy.String()),
|
||||
strings.ToLower(dnrf.ActuationStrategy.String()),
|
||||
strings.ToLower(status.Actuation.String()),
|
||||
id), nil
|
||||
case actuation.ActuationSucceeded:
|
||||
|
|
@ -124,11 +126,17 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
|
|||
id)
|
||||
}
|
||||
|
||||
// DryRun skips WaitTasks, so reconcile status can be ignored
|
||||
if dnrf.DryRunStrategy.ClientOrServerDryRun() {
|
||||
// Don't skip!
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
switch status.Reconcile {
|
||||
case actuation.ReconcilePending:
|
||||
// If reconcile is still pending, dependency sorting is probably broken.
|
||||
return false, "", fmt.Errorf("premature %s: %s %s reconcile %s: %q",
|
||||
strings.ToLower(dnrf.Strategy.String()),
|
||||
strings.ToLower(dnrf.ActuationStrategy.String()),
|
||||
strings.ToLower(relationship.String()),
|
||||
strings.ToLower(status.Strategy.String()),
|
||||
strings.ToLower(status.Reconcile.String()),
|
||||
|
|
@ -137,7 +145,7 @@ func (dnrf DependencyFilter) filterByRelationStatus(id object.ObjMetadata, relat
|
|||
// Skip!
|
||||
return true, fmt.Sprintf("%s %s reconcile %s: %q",
|
||||
strings.ToLower(relationship.String()),
|
||||
strings.ToLower(dnrf.Strategy.String()),
|
||||
strings.ToLower(dnrf.ActuationStrategy.String()),
|
||||
strings.ToLower(status.Reconcile.String()),
|
||||
id), nil
|
||||
case actuation.ReconcileSucceeded:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/cli-utils/pkg/apis/actuation"
|
||||
"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"
|
||||
)
|
||||
|
|
@ -43,15 +44,16 @@ var idB = object.ObjMetadata{
|
|||
|
||||
func TestDependencyFilter(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
strategy actuation.ActuationStrategy
|
||||
contextSetup func(*taskrunner.TaskContext)
|
||||
id object.ObjMetadata
|
||||
expectedFiltered bool
|
||||
expectedReason string
|
||||
expectedError error
|
||||
dryRunStrategy common.DryRunStrategy
|
||||
actuationStrategy actuation.ActuationStrategy
|
||||
contextSetup func(*taskrunner.TaskContext)
|
||||
id object.ObjMetadata
|
||||
expectedFiltered bool
|
||||
expectedReason string
|
||||
expectedError error
|
||||
}{
|
||||
"apply A (no deps)": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.InventoryManager().AddPendingApply(idA)
|
||||
|
|
@ -62,7 +64,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"apply A (A -> B) when B is invalid": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idInvalid)
|
||||
|
|
@ -76,7 +78,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"apply A (A -> B) before B is applied": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -88,7 +90,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: fmt.Errorf("premature apply: dependency apply actuation pending: %q", idB),
|
||||
},
|
||||
"apply A (A -> B) before B is reconciled": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -105,7 +107,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: fmt.Errorf("premature apply: dependency apply reconcile pending: %q", idB),
|
||||
},
|
||||
"apply A (A -> B) after B is reconciled": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -124,7 +126,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"apply A (A -> B) after B apply failed": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -143,7 +145,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"apply A (A -> B) after B apply skipped": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -162,7 +164,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"apply A (A -> B) after B reconcile failed": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -181,7 +183,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"apply A (A -> B) after B reconcile timeout": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -201,7 +203,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
},
|
||||
// artificial use case: reconcile should only be skipped if apply failed or was skipped
|
||||
"apply A (A -> B) after B reconcile skipped": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -220,7 +222,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"apply A (A -> B) when B delete pending": {
|
||||
strategy: actuation.ActuationStrategyApply,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -234,7 +236,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"delete B (no deps)": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
taskContext.InventoryManager().AddPendingDelete(idB)
|
||||
|
|
@ -245,7 +247,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"delete B (A -> B) when A is invalid": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idInvalid)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -259,7 +261,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"delete B (A -> B) before A is deleted": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -271,7 +273,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: fmt.Errorf("premature delete: dependent delete actuation pending: %q", idA),
|
||||
},
|
||||
"delete B (A -> B) before A is reconciled": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -288,7 +290,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: fmt.Errorf("premature delete: dependent delete reconcile pending: %q", idA),
|
||||
},
|
||||
"delete B (A -> B) after A is reconciled": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -307,7 +309,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"delete B (A -> B) after A delete failed": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -326,7 +328,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"delete B (A -> B) after A delete skipped": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -346,7 +348,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
},
|
||||
// artificial use case: delete reconcile can't fail, only timeout
|
||||
"delete B (A -> B) after A reconcile failed": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -365,7 +367,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"delete B (A -> B) after A reconcile timeout": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -385,7 +387,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
},
|
||||
// artificial use case: reconcile should only be skipped if delete failed or was skipped
|
||||
"delete B (A -> B) after A reconcile skipped": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -404,7 +406,7 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedError: nil,
|
||||
},
|
||||
"delete B (A -> B) when A apply succeeded": {
|
||||
strategy: actuation.ActuationStrategyDelete,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
|
|
@ -422,6 +424,46 @@ func TestDependencyFilter(t *testing.T) {
|
|||
expectedReason: fmt.Sprintf("delete skipped because dependent is scheduled for apply: %q", idA),
|
||||
expectedError: nil,
|
||||
},
|
||||
"DryRun: apply A (A -> B) when B apply reconcile pending": {
|
||||
dryRunStrategy: common.DryRunClient,
|
||||
actuationStrategy: actuation.ActuationStrategyApply,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
taskContext.Graph().AddEdge(idA, idB)
|
||||
taskContext.InventoryManager().AddPendingApply(idA)
|
||||
taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
|
||||
ObjectReference: inventory.ObjectReferenceFromObjMetadata(idB),
|
||||
Strategy: actuation.ActuationStrategyApply,
|
||||
Actuation: actuation.ActuationSucceeded,
|
||||
Reconcile: actuation.ReconcilePending,
|
||||
})
|
||||
},
|
||||
id: idA,
|
||||
expectedFiltered: false,
|
||||
expectedReason: "",
|
||||
expectedError: nil,
|
||||
},
|
||||
"DryRun: delete B (A -> B) when A delete reconcile pending": {
|
||||
dryRunStrategy: common.DryRunClient,
|
||||
actuationStrategy: actuation.ActuationStrategyDelete,
|
||||
contextSetup: func(taskContext *taskrunner.TaskContext) {
|
||||
taskContext.Graph().AddVertex(idA)
|
||||
taskContext.Graph().AddVertex(idB)
|
||||
taskContext.Graph().AddEdge(idA, idB)
|
||||
taskContext.InventoryManager().AddPendingDelete(idB)
|
||||
taskContext.InventoryManager().SetObjectStatus(actuation.ObjectStatus{
|
||||
ObjectReference: inventory.ObjectReferenceFromObjMetadata(idA),
|
||||
Strategy: actuation.ActuationStrategyDelete,
|
||||
Actuation: actuation.ActuationSucceeded,
|
||||
Reconcile: actuation.ReconcilePending,
|
||||
})
|
||||
},
|
||||
id: idB,
|
||||
expectedFiltered: false,
|
||||
expectedReason: "",
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
|
|
@ -430,8 +472,9 @@ func TestDependencyFilter(t *testing.T) {
|
|||
tc.contextSetup(taskContext)
|
||||
|
||||
filter := DependencyFilter{
|
||||
TaskContext: taskContext,
|
||||
Strategy: tc.strategy,
|
||||
TaskContext: taskContext,
|
||||
ActuationStrategy: tc.actuationStrategy,
|
||||
DryRunStrategy: tc.dryRunStrategy,
|
||||
}
|
||||
obj := defaultObj.DeepCopy()
|
||||
obj.SetGroupVersionKind(tc.id.GroupKind.WithVersion("v1"))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,301 @@
|
|||
// Copyright 2020 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/apply/event"
|
||||
"sigs.k8s.io/cli-utils/pkg/common"
|
||||
"sigs.k8s.io/cli-utils/pkg/inventory"
|
||||
"sigs.k8s.io/cli-utils/pkg/kstatus/status"
|
||||
"sigs.k8s.io/cli-utils/pkg/object"
|
||||
"sigs.k8s.io/cli-utils/pkg/testutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func dryRunTest(ctx context.Context, c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) {
|
||||
By("Apply with DryRun")
|
||||
applier := invConfig.ApplierFactoryFunc()
|
||||
inventoryID := fmt.Sprintf("%s-%s", inventoryName, namespaceName)
|
||||
|
||||
inventoryInfo := createInventoryInfo(invConfig, inventoryName, namespaceName, inventoryID)
|
||||
|
||||
namespace1Name := fmt.Sprintf("%s-ns1", namespaceName)
|
||||
|
||||
fields := struct{ Namespace string }{Namespace: namespace1Name}
|
||||
namespace1Obj := templateToUnstructured(namespaceTemplate, fields)
|
||||
podBObj := templateToUnstructured(podBTemplate, fields)
|
||||
|
||||
// Dependency order: podB -> namespace1
|
||||
// Apply order: namespace1, podB
|
||||
resources := []*unstructured.Unstructured{
|
||||
namespace1Obj,
|
||||
podBObj,
|
||||
}
|
||||
|
||||
applierEvents := runCollect(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
|
||||
ReconcileTimeout: 2 * time.Minute,
|
||||
EmitStatusEvents: true,
|
||||
DryRunStrategy: common.DryRunClient,
|
||||
}))
|
||||
|
||||
expEvents := []testutil.ExpEvent{
|
||||
{
|
||||
// InitTask
|
||||
EventType: event.InitType,
|
||||
InitEvent: &testutil.ExpInitEvent{},
|
||||
},
|
||||
{
|
||||
// InvAddTask start
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.InventoryAction,
|
||||
GroupName: "inventory-add-0",
|
||||
Type: event.Started,
|
||||
},
|
||||
},
|
||||
{
|
||||
// InvAddTask finished
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.InventoryAction,
|
||||
GroupName: "inventory-add-0",
|
||||
Type: event.Finished,
|
||||
},
|
||||
},
|
||||
{
|
||||
// ApplyTask start
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.ApplyAction,
|
||||
GroupName: "apply-0",
|
||||
Type: event.Started,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Create namespace
|
||||
EventType: event.ApplyType,
|
||||
ApplyEvent: &testutil.ExpApplyEvent{
|
||||
GroupName: "apply-0",
|
||||
Operation: event.Created,
|
||||
Identifier: object.UnstructuredToObjMetadata(namespace1Obj),
|
||||
Error: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
// ApplyTask finished
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.ApplyAction,
|
||||
GroupName: "apply-0",
|
||||
Type: event.Finished,
|
||||
},
|
||||
},
|
||||
{
|
||||
// ApplyTask start
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.ApplyAction,
|
||||
GroupName: "apply-1",
|
||||
Type: event.Started,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Create pod
|
||||
EventType: event.ApplyType,
|
||||
ApplyEvent: &testutil.ExpApplyEvent{
|
||||
GroupName: "apply-1",
|
||||
Operation: event.Created,
|
||||
Identifier: object.UnstructuredToObjMetadata(podBObj),
|
||||
Error: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
// ApplyTask finished
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.ApplyAction,
|
||||
GroupName: "apply-1",
|
||||
Type: event.Finished,
|
||||
},
|
||||
},
|
||||
// No Wait Tasks for Dry Run
|
||||
{
|
||||
// InvSetTask start
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.InventoryAction,
|
||||
GroupName: "inventory-set-0",
|
||||
Type: event.Started,
|
||||
},
|
||||
},
|
||||
{
|
||||
// InvSetTask finished
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.InventoryAction,
|
||||
GroupName: "inventory-set-0",
|
||||
Type: event.Finished,
|
||||
},
|
||||
},
|
||||
}
|
||||
received := testutil.EventsToExpEvents(applierEvents)
|
||||
|
||||
// handle required async NotFound StatusEvent for pod
|
||||
expected := testutil.ExpEvent{
|
||||
EventType: event.StatusType,
|
||||
StatusEvent: &testutil.ExpStatusEvent{
|
||||
Identifier: object.UnstructuredToObjMetadata(podBObj),
|
||||
Status: status.NotFoundStatus,
|
||||
Error: nil,
|
||||
},
|
||||
}
|
||||
received, matches := testutil.RemoveEqualEvents(received, expected)
|
||||
Expect(matches).To(BeNumerically(">=", 1), "unexpected number of %q status events for namespace", status.NotFoundStatus)
|
||||
|
||||
// handle required async NotFound StatusEvent for namespace
|
||||
expected = testutil.ExpEvent{
|
||||
EventType: event.StatusType,
|
||||
StatusEvent: &testutil.ExpStatusEvent{
|
||||
Identifier: object.UnstructuredToObjMetadata(namespace1Obj),
|
||||
Status: status.NotFoundStatus,
|
||||
Error: nil,
|
||||
},
|
||||
}
|
||||
received, matches = testutil.RemoveEqualEvents(received, expected)
|
||||
Expect(matches).To(BeNumerically(">=", 1), "unexpected number of %q status events for pod", status.NotFoundStatus)
|
||||
|
||||
Expect(received).To(testutil.Equal(expEvents))
|
||||
|
||||
By("Verify pod NotFound")
|
||||
assertUnstructuredDoesNotExist(ctx, c, podBObj)
|
||||
|
||||
By("Verify inventory NotFound")
|
||||
invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID)
|
||||
|
||||
By("Apply")
|
||||
runWithNoErr(applier.Run(ctx, inventoryInfo, resources, apply.ApplierOptions{
|
||||
ReconcileTimeout: 2 * time.Minute,
|
||||
}))
|
||||
|
||||
By("Verify pod created")
|
||||
assertUnstructuredExists(ctx, c, podBObj)
|
||||
|
||||
By("Verify inventory size")
|
||||
invConfig.InvSizeVerifyFunc(ctx, c, inventoryName, namespaceName, inventoryID, 2)
|
||||
|
||||
By("Destroy with DryRun")
|
||||
destroyer := invConfig.DestroyerFactoryFunc()
|
||||
|
||||
destroyerEvents := runCollect(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{
|
||||
InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
|
||||
EmitStatusEvents: true,
|
||||
DryRunStrategy: common.DryRunClient,
|
||||
}))
|
||||
|
||||
expEvents = []testutil.ExpEvent{
|
||||
{
|
||||
// InitTask
|
||||
EventType: event.InitType,
|
||||
InitEvent: &testutil.ExpInitEvent{},
|
||||
},
|
||||
{
|
||||
// PruneTask start
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.DeleteAction,
|
||||
GroupName: "prune-0",
|
||||
Type: event.Started,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Delete pod
|
||||
EventType: event.DeleteType,
|
||||
DeleteEvent: &testutil.ExpDeleteEvent{
|
||||
GroupName: "prune-0",
|
||||
Operation: event.Deleted,
|
||||
Identifier: object.UnstructuredToObjMetadata(podBObj),
|
||||
Error: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
// PruneTask finished
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.DeleteAction,
|
||||
GroupName: "prune-0",
|
||||
Type: event.Finished,
|
||||
},
|
||||
},
|
||||
{
|
||||
// PruneTask start
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.DeleteAction,
|
||||
GroupName: "prune-1",
|
||||
Type: event.Started,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Delete namespace
|
||||
EventType: event.DeleteType,
|
||||
DeleteEvent: &testutil.ExpDeleteEvent{
|
||||
GroupName: "prune-1",
|
||||
Operation: event.Deleted,
|
||||
Identifier: object.UnstructuredToObjMetadata(namespace1Obj),
|
||||
Error: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
// PruneTask finished
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.DeleteAction,
|
||||
GroupName: "prune-1",
|
||||
Type: event.Finished,
|
||||
},
|
||||
},
|
||||
// No Wait Tasks for Dry Run
|
||||
{
|
||||
// DeleteInvTask start
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.InventoryAction,
|
||||
GroupName: "delete-inventory-0",
|
||||
Type: event.Started,
|
||||
},
|
||||
},
|
||||
{
|
||||
// DeleteInvTask finished
|
||||
EventType: event.ActionGroupType,
|
||||
ActionGroupEvent: &testutil.ExpActionGroupEvent{
|
||||
Action: event.InventoryAction,
|
||||
GroupName: "delete-inventory-0",
|
||||
Type: event.Finished,
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(testutil.EventsToExpEvents(destroyerEvents)).To(testutil.Equal(expEvents))
|
||||
|
||||
By("Verify pod still exists")
|
||||
assertUnstructuredExists(ctx, c, podBObj)
|
||||
|
||||
By("Destroy")
|
||||
runWithNoErr(destroyer.Run(ctx, inventoryInfo, apply.DestroyerOptions{
|
||||
InventoryPolicy: inventory.PolicyAdoptIfNoInventory,
|
||||
}))
|
||||
|
||||
By("Verify pod deleted")
|
||||
assertUnstructuredDoesNotExist(ctx, c, podBObj)
|
||||
|
||||
By("Verify inventory deleted")
|
||||
invConfig.InvNotExistsFunc(ctx, c, inventoryName, namespaceName, inventoryID)
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ type applierFactoryFunc func() *apply.Applier
|
|||
type destroyerFactoryFunc func() *apply.Destroyer
|
||||
type invSizeVerifyFunc func(ctx context.Context, c client.Client, name, namespace, id string, count int)
|
||||
type invCountVerifyFunc func(ctx context.Context, c client.Client, namespace string, count int)
|
||||
type invNotExistsFunc func(ctx context.Context, c client.Client, name, namespace, id string)
|
||||
|
||||
type InventoryConfig struct {
|
||||
Strategy inventory.Strategy
|
||||
|
|
@ -45,6 +46,7 @@ type InventoryConfig struct {
|
|||
DestroyerFactoryFunc destroyerFactoryFunc
|
||||
InvSizeVerifyFunc invSizeVerifyFunc
|
||||
InvCountVerifyFunc invCountVerifyFunc
|
||||
InvNotExistsFunc invNotExistsFunc
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -61,6 +63,7 @@ var inventoryConfigs = map[string]InventoryConfig{
|
|||
DestroyerFactoryFunc: newDefaultInvDestroyer,
|
||||
InvSizeVerifyFunc: defaultInvSizeVerifyFunc,
|
||||
InvCountVerifyFunc: defaultInvCountVerifyFunc,
|
||||
InvNotExistsFunc: defaultInvNotExistsFunc,
|
||||
},
|
||||
CustomTypeInvConfig: {
|
||||
Strategy: inventory.NameStrategy,
|
||||
|
|
@ -70,6 +73,7 @@ var inventoryConfigs = map[string]InventoryConfig{
|
|||
DestroyerFactoryFunc: newCustomInvDestroyer,
|
||||
InvSizeVerifyFunc: customInvSizeVerifyFunc,
|
||||
InvCountVerifyFunc: customInvCountVerifyFunc,
|
||||
InvNotExistsFunc: customInvNotExistsFunc,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -163,6 +167,10 @@ var _ = Describe("Applier", func() {
|
|||
applyAndDestroyTest(ctx, c, invConfig, inventoryName, namespace.GetName())
|
||||
})
|
||||
|
||||
It("DryRun", func() {
|
||||
dryRunTest(ctx, c, invConfig, inventoryName, namespace.GetName())
|
||||
})
|
||||
|
||||
It("Deletion Prevention", func() {
|
||||
deletionPreventionTest(ctx, c, invConfig, inventoryName, namespace.GetName())
|
||||
})
|
||||
|
|
@ -340,6 +348,15 @@ func newDefaultInvDestroyer() *apply.Destroyer {
|
|||
return newDestroyerFromInvFactory(inventory.ClusterClientFactory{})
|
||||
}
|
||||
|
||||
func defaultInvNotExistsFunc(ctx context.Context, c client.Client, name, namespace, id string) {
|
||||
var cmList v1.ConfigMapList
|
||||
err := c.List(ctx, &cmList,
|
||||
client.MatchingLabels(map[string]string{common.InventoryLabel: id}),
|
||||
client.InNamespace(namespace))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cmList.Items).To(HaveLen(0), "expected inventory list to be empty")
|
||||
}
|
||||
|
||||
func defaultInvSizeVerifyFunc(ctx context.Context, c client.Client, name, namespace, id string, count int) {
|
||||
var cmList v1.ConfigMapList
|
||||
err := c.List(ctx, &cmList,
|
||||
|
|
@ -376,6 +393,14 @@ func newFactory() util.Factory {
|
|||
return util.NewFactory(matchVersionKubeConfigFlags)
|
||||
}
|
||||
|
||||
func customInvNotExistsFunc(ctx context.Context, c client.Client, name, namespace, id string) {
|
||||
var u unstructured.Unstructured
|
||||
u.SetGroupVersionKind(customprovider.InventoryGVK)
|
||||
u.SetName(name)
|
||||
u.SetNamespace(namespace)
|
||||
assertUnstructuredDoesNotExist(ctx, c, &u)
|
||||
}
|
||||
|
||||
func customInvSizeVerifyFunc(ctx context.Context, c client.Client, name, namespace, _ string, count int) {
|
||||
var u unstructured.Unstructured
|
||||
u.SetGroupVersionKind(customprovider.InventoryGVK)
|
||||
|
|
|
|||
Loading…
Reference in New Issue