mirror of
https://github.com/argoproj/argo-cd
synced 2026-04-21 17:07:16 +00:00
fix: Hook resources not created at PostSync when configured with PreDelete PostDelete hooks (cherry-pick #26996 for 3.3) (#26999)
Signed-off-by: reggie-k <regina.voloshin@codefresh.io> Co-authored-by: Regina Voloshin <regina.voloshin@codefresh.io>
This commit is contained in:
parent
06d960ddf4
commit
494b44ca5b
3 changed files with 124 additions and 18 deletions
|
|
@ -76,6 +76,21 @@ func isPostDeleteHook(obj *unstructured.Unstructured) bool {
|
|||
return isHookOfType(obj, PostDeleteHookType)
|
||||
}
|
||||
|
||||
// hasGitOpsEngineSyncPhaseHook is true when gitops-engine would run the resource during a sync
|
||||
// phase (PreSync, Sync, PostSync, SyncFail). PreDelete/PostDelete are not sync phases;
|
||||
// without this check, state reconciliation drops such resources
|
||||
// entirely because isPreDeleteHook/isPostDeleteHook match any comma-separated value.
|
||||
// HookTypeSkip is omitted as it is not a sync phase.
|
||||
func hasGitOpsEngineSyncPhaseHook(obj *unstructured.Unstructured) bool {
|
||||
for _, t := range hook.Types(obj) {
|
||||
switch t {
|
||||
case common.HookTypePreSync, common.HookTypeSync, common.HookTypePostSync, common.HookTypeSyncFail:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// executeHooks is a generic function to execute hooks of a specified type
|
||||
func (ctrl *ApplicationController) executeHooks(hookType HookType, app *appv1.Application, proj *appv1.AppProject, liveObjs map[kube.ResourceKey]*unstructured.Unstructured, config *rest.Config, logCtx *log.Entry) (bool, error) {
|
||||
appLabelKey, err := ctrl.settingsMgr.GetAppInstanceLabelKey()
|
||||
|
|
|
|||
|
|
@ -192,6 +192,92 @@ func TestIsPostDeleteHook(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestPartitionTargetObjsForSync covers partitionTargetObjsForSync in state.go.
|
||||
func TestPartitionTargetObjsForSync(t *testing.T) {
|
||||
newObj := func(name string, annot map[string]string) *unstructured.Unstructured {
|
||||
u := &unstructured.Unstructured{}
|
||||
u.SetName(name)
|
||||
u.SetAnnotations(annot)
|
||||
return u
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in []*unstructured.Unstructured
|
||||
wantNames []string
|
||||
wantPreDelete bool
|
||||
wantPostDelete bool
|
||||
}{
|
||||
{
|
||||
name: "PostSync with PreDelete and PostDelete in same annotation stays in sync set",
|
||||
in: []*unstructured.Unstructured{
|
||||
newObj("combined", map[string]string{"argocd.argoproj.io/hook": "PostSync,PreDelete,PostDelete"}),
|
||||
},
|
||||
wantNames: []string{"combined"},
|
||||
wantPreDelete: true,
|
||||
wantPostDelete: true,
|
||||
},
|
||||
{
|
||||
name: "PreDelete-only manifest excluded from sync",
|
||||
in: []*unstructured.Unstructured{
|
||||
newObj("pre-del", map[string]string{"argocd.argoproj.io/hook": "PreDelete"}),
|
||||
},
|
||||
wantNames: nil,
|
||||
wantPreDelete: true,
|
||||
wantPostDelete: false,
|
||||
},
|
||||
{
|
||||
name: "PostDelete-only manifest excluded from sync",
|
||||
in: []*unstructured.Unstructured{
|
||||
newObj("post-del", map[string]string{"argocd.argoproj.io/hook": "PostDelete"}),
|
||||
},
|
||||
wantNames: nil,
|
||||
wantPreDelete: false,
|
||||
wantPostDelete: true,
|
||||
},
|
||||
{
|
||||
name: "Helm pre-delete only excluded from sync",
|
||||
in: []*unstructured.Unstructured{
|
||||
newObj("helm-pre-del", map[string]string{"helm.sh/hook": "pre-delete"}),
|
||||
},
|
||||
wantNames: nil,
|
||||
wantPreDelete: true,
|
||||
wantPostDelete: false,
|
||||
},
|
||||
{
|
||||
name: "Helm pre-install with pre-delete stays in sync (sync-phase hook wins)",
|
||||
in: []*unstructured.Unstructured{
|
||||
newObj("helm-mixed", map[string]string{"helm.sh/hook": "pre-install,pre-delete"}),
|
||||
},
|
||||
wantNames: []string{"helm-mixed"},
|
||||
wantPreDelete: true,
|
||||
wantPostDelete: false,
|
||||
},
|
||||
{
|
||||
name: "Non-hook resource unchanged",
|
||||
in: []*unstructured.Unstructured{
|
||||
newObj("pod", map[string]string{"app": "x"}),
|
||||
},
|
||||
wantNames: []string{"pod"},
|
||||
wantPreDelete: false,
|
||||
wantPostDelete: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, hasPre, hasPost := partitionTargetObjsForSync(tt.in)
|
||||
var names []string
|
||||
for _, o := range got {
|
||||
names = append(names, o.GetName())
|
||||
}
|
||||
assert.Equal(t, tt.wantNames, names)
|
||||
assert.Equal(t, tt.wantPreDelete, hasPre, "hasPreDeleteHooks")
|
||||
assert.Equal(t, tt.wantPostDelete, hasPost, "hasPostDeleteHooks")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiHookOfType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -536,6 +536,28 @@ func isManagedNamespace(ns *unstructured.Unstructured, app *v1alpha1.Application
|
|||
return ns != nil && ns.GetKind() == kubeutil.NamespaceKind && ns.GetName() == app.Spec.Destination.Namespace && app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.ManagedNamespaceMetadata != nil
|
||||
}
|
||||
|
||||
// partitionTargetObjsForSync returns the manifest subset passed to gitops-engine sync, and whether
|
||||
// the full manifest set declared PreDelete and/or PostDelete hooks (for finalizer handling).
|
||||
// Uses isPreDeleteHook / isPostDeleteHook / hasGitOpsEngineSyncPhaseHook from hook.go.
|
||||
func partitionTargetObjsForSync(targetObjs []*unstructured.Unstructured) (syncObjs []*unstructured.Unstructured, hasPreDeleteHooks, hasPostDeleteHooks bool) {
|
||||
for _, obj := range targetObjs {
|
||||
if isPreDeleteHook(obj) {
|
||||
hasPreDeleteHooks = true
|
||||
if !hasGitOpsEngineSyncPhaseHook(obj) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if isPostDeleteHook(obj) {
|
||||
hasPostDeleteHooks = true
|
||||
if !hasGitOpsEngineSyncPhaseHook(obj) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
syncObjs = append(syncObjs, obj)
|
||||
}
|
||||
return syncObjs, hasPreDeleteHooks, hasPostDeleteHooks
|
||||
}
|
||||
|
||||
// CompareAppState compares application git state to the live app state, using the specified
|
||||
// revision and supplied source. If revision or overrides are empty, then compares against
|
||||
// revision and overrides in the app spec.
|
||||
|
|
@ -763,24 +785,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
|
|||
}
|
||||
}
|
||||
}
|
||||
hasPreDeleteHooks := false
|
||||
hasPostDeleteHooks := false
|
||||
// Filter out PreDelete and PostDelete hooks from targetObjs since they should not be synced
|
||||
// as regular resources. They are only executed during deletion.
|
||||
var targetObjsForSync []*unstructured.Unstructured
|
||||
for _, obj := range targetObjs {
|
||||
if isPreDeleteHook(obj) {
|
||||
hasPreDeleteHooks = true
|
||||
// Skip PreDelete hooks - they are not synced, only executed during deletion
|
||||
continue
|
||||
}
|
||||
if isPostDeleteHook(obj) {
|
||||
hasPostDeleteHooks = true
|
||||
// Skip PostDelete hooks - they are not synced, only executed after deletion
|
||||
continue
|
||||
}
|
||||
targetObjsForSync = append(targetObjsForSync, obj)
|
||||
}
|
||||
targetObjsForSync, hasPreDeleteHooks, hasPostDeleteHooks := partitionTargetObjsForSync(targetObjs)
|
||||
|
||||
reconciliation := sync.Reconcile(targetObjsForSync, liveObjByKey, app.Spec.Destination.Namespace, infoProvider)
|
||||
ts.AddCheckpoint("live_ms")
|
||||
|
|
|
|||
Loading…
Reference in a new issue