mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com> Co-authored-by: Leonardo Luz Almeida <leoluz@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a66fe2af24
commit
228378474a
@@ -1,13 +1,17 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/test"
|
||||
"github.com/argoproj/argo-cd/v3/test/e2e/fixture"
|
||||
"github.com/argoproj/argo-cd/v3/test/e2e/fixture/certs"
|
||||
"github.com/argoproj/argo-cd/v3/test/e2e/fixture/gpgkeys"
|
||||
@@ -481,3 +485,16 @@ func (c *Context) RegisterKustomizeVersion(version, path string) *Context {
|
||||
require.NoError(c.T(), fixture.RegisterKustomizeVersion(version, path))
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Context) Resource(content string) *Context {
|
||||
c.T().Helper()
|
||||
u := test.YamlToUnstructured(content)
|
||||
mapping, err := fixture.Mapper.RESTMapping(u.GroupVersionKind().GroupKind(), u.GroupVersionKind().Version)
|
||||
require.NoError(c.T(), err)
|
||||
if mapping == nil {
|
||||
require.NoError(c.T(), fmt.Errorf("cannot find mapping for %s", u.GroupVersionKind().String()))
|
||||
}
|
||||
_, err = fixture.DynamicClientset.Resource(mapping.Resource).Namespace(c.DeploymentNamespace()).Apply(c.T().Context(), u.GetName(), u, metav1.ApplyOptions{FieldManager: "e2e-given-step"})
|
||||
require.NoError(c.T(), err)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -28,6 +28,26 @@ const (
|
||||
|
||||
type Expectation func(c *Consequences) (state state, message string)
|
||||
|
||||
func Or(e1 Expectation, e2 Expectation) Expectation {
|
||||
return func(c *Consequences) (state, string) {
|
||||
s1, m1 := e1(c)
|
||||
if s1 == succeeded {
|
||||
return s1, m1
|
||||
}
|
||||
s2, m2 := e2(c)
|
||||
if s2 == succeeded {
|
||||
return s2, m2
|
||||
}
|
||||
if s1 == pending {
|
||||
return s1, m1
|
||||
}
|
||||
if s2 == pending {
|
||||
return s2, m2
|
||||
}
|
||||
return failed, fmt.Sprintf("expectations unsuccessful: %s and %s", m1, m2)
|
||||
}
|
||||
}
|
||||
|
||||
func OperationPhaseIs(expected common.OperationPhase) Expectation {
|
||||
return func(c *Consequences) (state, string) {
|
||||
operationState := c.app().Status.OperationState
|
||||
@@ -199,6 +219,9 @@ func ResourceHealthWithNamespaceIs(kind, resource, namespace string, expected he
|
||||
|
||||
func ResourceResultNumbering(num int) Expectation {
|
||||
return func(c *Consequences) (state, string) {
|
||||
if c.app().Status.OperationState == nil || c.app().Status.OperationState.SyncResult == nil {
|
||||
return pending, "no sync result yet"
|
||||
}
|
||||
actualNum := len(c.app().Status.OperationState.SyncResult.Resources)
|
||||
if actualNum < num {
|
||||
return pending, fmt.Sprintf("not enough results yet, want %d, got %d", num, actualNum)
|
||||
@@ -211,6 +234,9 @@ func ResourceResultNumbering(num int) Expectation {
|
||||
|
||||
func ResourceResultIs(result v1alpha1.ResourceResult) Expectation {
|
||||
return func(c *Consequences) (state, string) {
|
||||
if c.app().Status.OperationState == nil || c.app().Status.OperationState.SyncResult == nil {
|
||||
return pending, "no sync result yet"
|
||||
}
|
||||
results := c.app().Status.OperationState.SyncResult.Resources
|
||||
for _, res := range results {
|
||||
if reflect.DeepEqual(*res, result) {
|
||||
@@ -233,6 +259,9 @@ func sameResourceResult(res1, res2 v1alpha1.ResourceResult) bool {
|
||||
|
||||
func ResourceResultMatches(result v1alpha1.ResourceResult) Expectation {
|
||||
return func(c *Consequences) (state, string) {
|
||||
if c.app().Status.OperationState == nil || c.app().Status.OperationState.SyncResult == nil {
|
||||
return pending, "no sync result yet"
|
||||
}
|
||||
results := c.app().Status.OperationState.SyncResult.Resources
|
||||
for _, res := range results {
|
||||
if sameResourceResult(*res, result) {
|
||||
|
||||
@@ -20,11 +20,14 @@ import (
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/discovery/cached/memory"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
@@ -91,6 +94,7 @@ var (
|
||||
DynamicClientset dynamic.Interface
|
||||
AppClientset appclientset.Interface
|
||||
ArgoCDClientset apiclient.Client
|
||||
Mapper meta.RESTMapper
|
||||
adminUsername string
|
||||
AdminPassword string
|
||||
apiServerAddress string
|
||||
@@ -195,6 +199,7 @@ func init() {
|
||||
AppClientset = appclientset.NewForConfigOrDie(config)
|
||||
KubeClientset = kubernetes.NewForConfigOrDie(config)
|
||||
DynamicClientset = dynamic.NewForConfigOrDie(config)
|
||||
Mapper = restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(KubeClientset.Discovery()))
|
||||
KubeConfig = config
|
||||
|
||||
apiServerAddress = GetEnvWithDefault(apiclient.EnvArgoCDServer, defaultAPIServer)
|
||||
|
||||
@@ -10,9 +10,11 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
. "github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
"github.com/argoproj/gitops-engine/pkg/sync/hook"
|
||||
|
||||
. "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
. "github.com/argoproj/argo-cd/v3/test/e2e/fixture"
|
||||
@@ -50,7 +52,13 @@ func testHookSuccessful(t *testing.T, hookType HookType) {
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod", SyncStatusCodeSynced)).
|
||||
Expect(ResourceHealthIs("Pod", "pod", health.HealthStatusHealthy)).
|
||||
Expect(ResourceResultNumbering(2)).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "hook", Message: "pod/hook created", HookType: hookType, Status: ResultCodeSynced, HookPhase: OperationSucceeded, SyncPhase: SyncPhase(hookType)}))
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "hook", Status: ResultCodeSynced, Message: "pod/hook created", HookType: hookType, HookPhase: OperationSucceeded, SyncPhase: SyncPhase(hookType)})).
|
||||
Expect(Pod(func(p corev1.Pod) bool {
|
||||
// Completed hooks should not have a finalizer
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return isHook && !hasFinalizer
|
||||
}))
|
||||
}
|
||||
|
||||
func TestPreDeleteHook(t *testing.T) {
|
||||
@@ -133,8 +141,8 @@ func TestHookDiff(t *testing.T) {
|
||||
|
||||
// make sure that if pre-sync fails, we fail the app and we do not create the pod
|
||||
func TestPreSyncHookFailure(t *testing.T) {
|
||||
Given(t).
|
||||
Path("hook").
|
||||
ctx := Given(t)
|
||||
ctx.Path("hook").
|
||||
When().
|
||||
PatchFile("hook.yaml", `[{"op": "replace", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/hook": "PreSync"}}]`).
|
||||
// make hook fail
|
||||
@@ -143,14 +151,19 @@ func TestPreSyncHookFailure(t *testing.T) {
|
||||
IgnoreErrors().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(Error("hook Failed Synced PreSync container \"main\" failed", "")).
|
||||
// make sure resource are also printed
|
||||
Expect(Error("pod OutOfSync Missing", "")).
|
||||
Expect(OperationPhaseIs(OperationFailed)).
|
||||
// if a pre-sync hook fails, we should not start the main sync
|
||||
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
|
||||
Expect(OperationPhaseIs(OperationFailed)).
|
||||
Expect(ResourceResultNumbering(1)).
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod", SyncStatusCodeOutOfSync))
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "hook", Status: ResultCodeSynced, Message: `container "main" failed with exit code 1`, HookType: HookTypePreSync, HookPhase: OperationFailed, SyncPhase: SyncPhase(HookTypePreSync)})).
|
||||
Expect(ResourceHealthIs("Pod", "pod", health.HealthStatusMissing)).
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod", SyncStatusCodeOutOfSync)).
|
||||
Expect(Pod(func(p corev1.Pod) bool {
|
||||
// Completed hooks should not have a finalizer
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return isHook && !hasFinalizer
|
||||
}))
|
||||
}
|
||||
|
||||
// make sure that if sync fails, we fail the app and we did create the pod
|
||||
@@ -168,7 +181,13 @@ func TestSyncHookFailure(t *testing.T) {
|
||||
// even thought the hook failed, we expect the pod to be in sync
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(ResourceResultNumbering(2)).
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod", SyncStatusCodeSynced))
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod", SyncStatusCodeSynced)).
|
||||
Expect(Pod(func(p corev1.Pod) bool {
|
||||
// Completed hooks should not have a finalizer
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return isHook && !hasFinalizer
|
||||
}))
|
||||
}
|
||||
|
||||
// make sure that if the deployments fails, we still get success and synced
|
||||
@@ -184,7 +203,7 @@ func TestSyncHookResourceFailure(t *testing.T) {
|
||||
Expect(HealthIs(health.HealthStatusProgressing))
|
||||
}
|
||||
|
||||
// make sure that if post-sync fails, we fail the app and we did not create the pod
|
||||
// make sure that if post-sync fails, we fail the app and we did create the pod
|
||||
func TestPostSyncHookFailure(t *testing.T) {
|
||||
Given(t).
|
||||
Path("hook").
|
||||
@@ -199,7 +218,13 @@ func TestPostSyncHookFailure(t *testing.T) {
|
||||
Expect(OperationPhaseIs(OperationFailed)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
Expect(ResourceResultNumbering(2)).
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod", SyncStatusCodeSynced))
|
||||
Expect(ResourceSyncStatusIs("Pod", "pod", SyncStatusCodeSynced)).
|
||||
Expect(Pod(func(p corev1.Pod) bool {
|
||||
// Completed hooks should not have a finalizer
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return isHook && !hasFinalizer
|
||||
}))
|
||||
}
|
||||
|
||||
// make sure that if the pod fails, we do not run the post-sync hook
|
||||
@@ -298,9 +323,119 @@ spec:
|
||||
CreateApp().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Name: "successful-sync-fail-hook", Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Message: "pod/successful-sync-fail-hook created", HookType: HookTypeSyncFail, Status: ResultCodeSynced, HookPhase: OperationSucceeded, SyncPhase: SyncPhaseSyncFail})).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Name: "failed-sync-fail-hook", Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Message: `container "main" failed with exit code 1`, HookType: HookTypeSyncFail, Status: ResultCodeSynced, HookPhase: OperationFailed, SyncPhase: SyncPhaseSyncFail})).
|
||||
Expect(OperationPhaseIs(OperationFailed))
|
||||
Expect(ResourceResultNumbering(4)).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Name: "successful-sync-fail-hook", Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Status: ResultCodeSynced, Message: "pod/successful-sync-fail-hook created", HookType: HookTypeSyncFail, HookPhase: OperationSucceeded, SyncPhase: SyncPhaseSyncFail})).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Name: "failed-sync-fail-hook", Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Status: ResultCodeSynced, Message: `container "main" failed with exit code 1`, HookType: HookTypeSyncFail, HookPhase: OperationFailed, SyncPhase: SyncPhaseSyncFail})).
|
||||
Expect(OperationPhaseIs(OperationFailed)).
|
||||
Expect(Pod(func(p corev1.Pod) bool {
|
||||
// Completed hooks should not have a finalizer
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return p.GetName() == "failed-sync-fail-hook" && isHook && !hasFinalizer
|
||||
}))
|
||||
}
|
||||
|
||||
// Make sure that if a hook is invalid (must pass the dry-run client), it fails without affecting other hooks.
|
||||
func TestInvalidlHookWaitsForOtherHooksToComplete(t *testing.T) {
|
||||
existingHook := `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: Sync
|
||||
argocd.argoproj.io/hook-delete-policy: HookFailed # To preserve existence before sync
|
||||
name: invalid-hook
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- "true"
|
||||
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never`
|
||||
|
||||
ctx := Given(t)
|
||||
ctx.Path("hook").
|
||||
Resource(existingHook).
|
||||
When().
|
||||
AddFile("invalid-hook.yaml", existingHook).
|
||||
// The invalid hook needs to be valid in dry-run, but fail at apply time
|
||||
// We change an immutable field to make it happen, and hook should already exist since delete policy was HookFailed on last sync
|
||||
PatchFile("invalid-hook.yaml", `[{"op": "replace", "path": "/spec/containers/0/name", "value": "immutable" }]`).
|
||||
CreateApp().
|
||||
IgnoreErrors().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(ResourceResultNumbering(3)).
|
||||
Expect(ResourceResultMatches(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "invalid-hook", Status: ResultCodeSyncFailed, Message: `Pod "invalid-hook" is invalid`, HookType: HookTypeSync, HookPhase: OperationFailed, SyncPhase: SyncPhase(HookTypeSync)})).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "hook", Status: ResultCodeSynced, Message: "pod/hook created", HookType: HookTypeSync, HookPhase: OperationSucceeded, SyncPhase: SyncPhase(HookTypeSync)})).
|
||||
Expect(OperationPhaseIs(OperationFailed)).
|
||||
Expect(Pod(func(p corev1.Pod) bool {
|
||||
// Completed hooks should not have a finalizer
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return p.GetName() == "hook" && isHook && !hasFinalizer
|
||||
}))
|
||||
}
|
||||
|
||||
func TestInvalidSyncFailureHookWaitsForOtherHooksToComplete(t *testing.T) {
|
||||
existingHook := `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: SyncFail
|
||||
argocd.argoproj.io/hook-delete-policy: HookSucceeded # To preserve existence before sync
|
||||
name: invalid-sync-fail-hook
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- "true"
|
||||
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never`
|
||||
|
||||
ctx := Given(t)
|
||||
ctx.Path("hook").
|
||||
Resource(existingHook).
|
||||
When().
|
||||
AddFile("successful-sync-fail-hook.yaml", `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: SyncFail
|
||||
name: successful-sync-fail-hook
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- "true"
|
||||
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never
|
||||
`).
|
||||
AddFile("invalid-sync-fail-hook.yaml", existingHook).
|
||||
// The invalid hook needs to be valid in dry-run, but fail at apply time
|
||||
// We change an immutable field to make it happen, and hook should already exist since delete policy was HookFailed on last sync
|
||||
PatchFile("invalid-sync-fail-hook.yaml", `[{"op": "replace", "path": "/spec/containers/0/name", "value": "immutable" }]`).
|
||||
// Make the sync fail
|
||||
PatchFile("hook.yaml", `[{"op": "replace", "path": "/spec/containers/0/command/0", "value": "false"}]`).
|
||||
CreateApp().
|
||||
IgnoreErrors().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(ResourceResultNumbering(4)).
|
||||
Expect(ResourceResultMatches(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "invalid-sync-fail-hook", Status: ResultCodeSyncFailed, Message: `Pod "invalid-sync-fail-hook" is invalid`, HookType: HookTypeSyncFail, HookPhase: OperationFailed, SyncPhase: SyncPhase(HookTypeSyncFail)})).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "successful-sync-fail-hook", Status: ResultCodeSynced, Message: "pod/successful-sync-fail-hook created", HookType: HookTypeSyncFail, HookPhase: OperationSucceeded, SyncPhase: SyncPhase(HookTypeSyncFail)})).
|
||||
Expect(OperationPhaseIs(OperationFailed)).
|
||||
Expect(Pod(func(p corev1.Pod) bool {
|
||||
// Completed hooks should not have a finalizer
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return p.GetName() == "successful-sync-fail-hook" && isHook && !hasFinalizer
|
||||
}))
|
||||
}
|
||||
|
||||
// make sure that we delete the hook on success
|
||||
@@ -491,6 +626,8 @@ func TestHookFinalizerPostSync(t *testing.T) {
|
||||
}
|
||||
|
||||
func testHookFinalizer(t *testing.T, hookType HookType) {
|
||||
// test that the finalizer prevents hooks from being deleted by Kubernetes without observing
|
||||
// its health to evaluate completion first.
|
||||
t.Helper()
|
||||
ctx := Given(t)
|
||||
ctx.
|
||||
@@ -531,3 +668,79 @@ func testHookFinalizer(t *testing.T, hookType HookType) {
|
||||
Expect(ResourceResultNumbering(2)).
|
||||
Expect(ResourceResultIs(ResourceResult{Group: "batch", Version: "v1", Kind: "Job", Namespace: ctx.DeploymentNamespace(), Name: "hook", Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Message: "Resource has finalizer", HookType: hookType, Status: ResultCodeSynced, HookPhase: OperationSucceeded, SyncPhase: SyncPhase(hookType)}))
|
||||
}
|
||||
|
||||
// test terminate operation stops running hooks
|
||||
func TestTerminateWithRunningHooks(t *testing.T) {
|
||||
newHook := func(name string, deletePolicy HookDeletePolicy, cmd string) string {
|
||||
return fmt.Sprintf(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
argocd.argoproj.io/hook-delete-policy: %s
|
||||
name: %s
|
||||
spec:
|
||||
containers:
|
||||
- command: [ "/bin/sh", "-c", "--" ]
|
||||
args: [ "%s" ]
|
||||
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: main
|
||||
restartPolicy: Never`, deletePolicy, name, cmd)
|
||||
}
|
||||
|
||||
podDeletedOrTerminatingWithoutFinalizer := func(name string) Expectation {
|
||||
return Or(
|
||||
NotPod(func(p corev1.Pod) bool {
|
||||
return p.GetName() == name
|
||||
}),
|
||||
Pod(func(p corev1.Pod) bool {
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return p.GetName() == name && isHook && !hasFinalizer && p.GetDeletionTimestamp() != nil
|
||||
}))
|
||||
}
|
||||
|
||||
podWithoutFinalizer := func(name string) Expectation {
|
||||
return Pod(func(p corev1.Pod) bool {
|
||||
_, isHook := p.GetAnnotations()[AnnotationKeyHook]
|
||||
hasFinalizer := controllerutil.ContainsFinalizer(&p, hook.HookFinalizer)
|
||||
return p.GetName() == name && isHook && !hasFinalizer
|
||||
})
|
||||
}
|
||||
|
||||
ctx := Given(t)
|
||||
ctx.Path("hook").
|
||||
Async(true).
|
||||
When().
|
||||
AddFile("running-delete-on-success.yaml", newHook("running-delete-on-success", HookDeletePolicyHookSucceeded, "sleep 300")).
|
||||
AddFile("running-delete-on-create.yaml", newHook("running-delete-on-create", HookDeletePolicyBeforeHookCreation, "sleep 300")).
|
||||
AddFile("running-delete-on-failed.yaml", newHook("running-delete-on-failed", HookDeletePolicyHookFailed, "sleep 300")).
|
||||
AddFile("complete-delete-on-success.yaml", newHook("complete-delete-on-success", HookDeletePolicyHookSucceeded, "true")).
|
||||
AddFile("complete-delete-on-create.yaml", newHook("complete-delete-on-create", HookDeletePolicyBeforeHookCreation, "true")).
|
||||
AddFile("complete-delete-on-failed.yaml", newHook("complete-delete-on-failed", HookDeletePolicyHookFailed, "true")).
|
||||
CreateApp().
|
||||
Sync().
|
||||
Then().
|
||||
Expect(ResourceResultNumbering(6)).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "complete-delete-on-success", Status: ResultCodeSynced, Message: "pod/complete-delete-on-success created", HookType: HookTypePreSync, HookPhase: OperationSucceeded, SyncPhase: SyncPhase(HookTypePreSync)})).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "complete-delete-on-create", Status: ResultCodeSynced, Message: "pod/complete-delete-on-create created", HookType: HookTypePreSync, HookPhase: OperationSucceeded, SyncPhase: SyncPhase(HookTypePreSync)})).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "complete-delete-on-failed", Status: ResultCodeSynced, Message: "pod/complete-delete-on-failed created", HookType: HookTypePreSync, HookPhase: OperationSucceeded, SyncPhase: SyncPhase(HookTypePreSync)})).
|
||||
Expect(OperationPhaseIs(OperationRunning)).
|
||||
When().
|
||||
TerminateOp().
|
||||
Then().
|
||||
Expect(OperationPhaseIs(OperationFailed)).
|
||||
// Running hooks are terminated
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "running-delete-on-success", Status: ResultCodeSynced, Message: "Terminated", HookType: HookTypePreSync, HookPhase: OperationFailed, SyncPhase: SyncPhase(HookTypePreSync)})).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "running-delete-on-create", Status: ResultCodeSynced, Message: "Terminated", HookType: HookTypePreSync, HookPhase: OperationFailed, SyncPhase: SyncPhase(HookTypePreSync)})).
|
||||
Expect(ResourceResultIs(ResourceResult{Version: "v1", Kind: "Pod", Namespace: ctx.DeploymentNamespace(), Images: []string{"quay.io/argoprojlabs/argocd-e2e-container:0.1"}, Name: "running-delete-on-failed", Status: ResultCodeSynced, Message: "Terminated", HookType: HookTypePreSync, HookPhase: OperationFailed, SyncPhase: SyncPhase(HookTypePreSync)})).
|
||||
// terminated hooks finalizer is removed and are deleted successfully
|
||||
Expect(podDeletedOrTerminatingWithoutFinalizer("running-delete-on-success")).
|
||||
Expect(podDeletedOrTerminatingWithoutFinalizer("running-delete-on-create")).
|
||||
Expect(podDeletedOrTerminatingWithoutFinalizer("running-delete-on-failed")).
|
||||
Expect(podWithoutFinalizer("complete-delete-on-success")).
|
||||
Expect(podWithoutFinalizer("complete-delete-on-create")).
|
||||
Expect(podWithoutFinalizer("complete-delete-on-failed"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user