fix(hooks): always remove finalizers (#23226) (#25916)

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:
Alexandre Gaudreault
2026-01-19 11:42:03 -05:00
committed by GitHub
parent a66fe2af24
commit 228378474a
8 changed files with 1119 additions and 347 deletions

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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"))
}