Skip to content

Commit

Permalink
feat: sync-options annotation with Force=true (#414)
Browse files Browse the repository at this point in the history
  • Loading branch information
kkk777-7 committed Dec 9, 2023
1 parent c0c2dd1 commit c39a776
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 4 deletions.
2 changes: 2 additions & 0 deletions pkg/sync/common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const (
SyncOptionPruneLast = "PruneLast=true"
// Sync option that enables use of replace or create command instead of apply
SyncOptionReplace = "Replace=true"
// Sync option that enables use of --force flag, delete and re-create
SyncOptionForce = "Force=true"
// Sync option that enables use of --server-side flag instead of client-side
SyncOptionServerSideApply = "ServerSideApply=true"
// Sync option that disables resource deletion
Expand Down
5 changes: 3 additions & 2 deletions pkg/sync/sync_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -913,14 +913,15 @@ func getDryRunStrategy(serverSideApply, dryRun bool) cmdutil.DryRunStrategy {
return cmdutil.DryRunClient
}

func (sc *syncContext) applyObject(t *syncTask, dryRun, force, validate bool) (common.ResultCode, string) {
func (sc *syncContext) applyObject(t *syncTask, dryRun, validate bool) (common.ResultCode, string) {
serverSideApply := sc.serverSideApply || resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionServerSideApply)

dryRunStrategy := getDryRunStrategy(serverSideApply, dryRun)

var err error
var message string
shouldReplace := sc.replace || resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionReplace)
force := sc.force || resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionForce)
applyFn := func(dryRunStrategy cmdutil.DryRunStrategy) (string, error) {
if !shouldReplace {
return sc.resourceOps.ApplyResource(context.TODO(), t.targetObj, dryRunStrategy, force, validate, serverSideApply, sc.serverSideApplyManager)
Expand Down Expand Up @@ -1205,7 +1206,7 @@ func (sc *syncContext) processCreateTasks(state runState, tasks syncTasks, dryRu
logCtx := sc.log.WithValues("dryRun", dryRun, "task", t)
logCtx.V(1).Info("Applying")
validate := sc.validate && !resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionsDisableValidation)
result, message := sc.applyObject(t, dryRun, sc.force, validate)
result, message := sc.applyObject(t, dryRun, validate)
if result == common.ResultCodeSyncFailed {
logCtx.WithValues("message", message).Info("Apply failed")
state = failed
Expand Down
49 changes: 48 additions & 1 deletion pkg/sync/sync_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"encoding/json"
"errors"
"fmt"
"k8s.io/kubectl/pkg/cmd/util"
"net/http"
"net/http/httptest"
"reflect"
"testing"

"k8s.io/kubectl/pkg/cmd/util"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"

Expand Down Expand Up @@ -859,6 +860,52 @@ func TestSync_ServerSideApply(t *testing.T) {
}
}

func withForceAnnotation(un *unstructured.Unstructured) *unstructured.Unstructured {
un.SetAnnotations(map[string]string{synccommon.AnnotationSyncOptions: synccommon.SyncOptionForce})
return un
}

func withForceAndReplaceAnnotations(un *unstructured.Unstructured) *unstructured.Unstructured {
un.SetAnnotations(map[string]string{synccommon.AnnotationSyncOptions: "Force=true,Replace=true"})
return un
}

func TestSync_Force(t *testing.T) {
testCases := []struct {
name string
target *unstructured.Unstructured
live *unstructured.Unstructured
commandUsed string
force bool
}{
{"NoAnnotation", NewPod(), NewPod(), "apply", false},
{"ForceApplyAnnotationIsSet", withForceAnnotation(NewPod()), NewPod(), "apply", true},
{"ForceReplaceAnnotationIsSet", withForceAndReplaceAnnotations(NewPod()), NewPod(), "replace", true},
{"LiveObjectMissing", withReplaceAnnotation(NewPod()), nil, "create", false},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
syncCtx := newTestSyncCtx(nil)

tc.target.SetNamespace(FakeArgoCDNamespace)
if tc.live != nil {
tc.live.SetNamespace(FakeArgoCDNamespace)
}
syncCtx.resources = groupResources(ReconciliationResult{
Live: []*unstructured.Unstructured{tc.live},
Target: []*unstructured.Unstructured{tc.target},
})

syncCtx.Sync()

resourceOps, _ := syncCtx.resourceOps.(*kubetest.MockResourceOps)
assert.Equal(t, tc.commandUsed, resourceOps.GetLastResourceCommand(kube.GetResourceKey(tc.target)))
assert.Equal(t, tc.force, resourceOps.GetLastForce())
})
}
}

func TestSelectiveSyncOnly(t *testing.T) {
pod1 := NewPod()
pod1.SetName("pod-1")
Expand Down
17 changes: 16 additions & 1 deletion pkg/utils/kube/kubetest/mock_resource_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type MockResourceOps struct {
lastValidate bool
serverSideApply bool
serverSideApplyManager string
lastForce bool

recordLock sync.RWMutex

Expand Down Expand Up @@ -73,6 +74,19 @@ func (r *MockResourceOps) SetLastServerSideApplyManager(manager string) {
r.recordLock.Unlock()
}

func (r *MockResourceOps) SetLastForce(force bool) {
r.recordLock.Lock()
r.lastForce = force
r.recordLock.Unlock()
}

func (r *MockResourceOps) GetLastForce() bool {
r.recordLock.RLock()
force := r.lastForce
r.recordLock.RUnlock()
return force
}

func (r *MockResourceOps) SetLastResourceCommand(key kube.ResourceKey, cmd string) {
r.recordLock.Lock()
if r.lastCommandPerResource == nil {
Expand All @@ -95,6 +109,7 @@ func (r *MockResourceOps) ApplyResource(ctx context.Context, obj *unstructured.U
r.SetLastValidate(validate)
r.SetLastServerSideApply(serverSideApply)
r.SetLastServerSideApplyManager(manager)
r.SetLastForce(force)
r.SetLastResourceCommand(kube.GetResourceKey(obj), "apply")
command, ok := r.Commands[obj.GetName()]
if !ok {
Expand All @@ -105,9 +120,9 @@ func (r *MockResourceOps) ApplyResource(ctx context.Context, obj *unstructured.U
}

func (r *MockResourceOps) ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool) (string, error) {
r.SetLastForce(force)
command, ok := r.Commands[obj.GetName()]
r.SetLastResourceCommand(kube.GetResourceKey(obj), "replace")

if !ok {
return "", nil
}
Expand Down

0 comments on commit c39a776

Please sign in to comment.