Skip to content

Commit c5b7114

Browse files
authored
selective sync (argoproj#213)
Signed-off-by: kshamajain99 <[email protected]>
1 parent 814d79d commit c5b7114

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
.vscode
44
.idea
55
coverage.out
6+
vendor/

pkg/sync/sync_context.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package sync
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"sort"
78
"strconv"
@@ -23,6 +24,7 @@ import (
2324
"k8s.io/klog/v2/klogr"
2425
cmdutil "k8s.io/kubectl/pkg/cmd/util"
2526

27+
"github.com/argoproj/gitops-engine/pkg/diff"
2628
"github.com/argoproj/gitops-engine/pkg/health"
2729
"github.com/argoproj/gitops-engine/pkg/sync/common"
2830
"github.com/argoproj/gitops-engine/pkg/sync/hook"
@@ -129,6 +131,18 @@ func WithPruneLast(enabled bool) SyncOpt {
129131
}
130132
}
131133

134+
// WithResourceModificationChecker sets resource modification result
135+
func WithResourceModificationChecker(enabled bool, diffResults *diff.DiffResultList) SyncOpt {
136+
return func(ctx *syncContext) {
137+
ctx.applyOutOfSyncOnly = enabled
138+
if enabled {
139+
ctx.modificationResult = groupDiffResults(diffResults)
140+
} else {
141+
ctx.modificationResult = nil
142+
}
143+
}
144+
}
145+
132146
// WithNamespaceCreation will create non-exist namespace
133147
func WithNamespaceCreation(createNamespace bool, namespaceModifier func(*unstructured.Unstructured) bool) SyncOpt {
134148
return func(ctx *syncContext) {
@@ -217,6 +231,25 @@ func groupResources(reconciliationResult ReconciliationResult) map[kubeutil.Reso
217231
return resources
218232
}
219233

234+
// generates a map of resource and its modification result based on diffResultList
235+
func groupDiffResults(diffResultList *diff.DiffResultList) map[kubeutil.ResourceKey]bool {
236+
modifiedResources := make(map[kube.ResourceKey]bool)
237+
for _, res := range diffResultList.Diffs {
238+
var obj unstructured.Unstructured
239+
var err error
240+
if string(res.NormalizedLive) != "null" {
241+
err = json.Unmarshal(res.NormalizedLive, &obj)
242+
} else {
243+
err = json.Unmarshal(res.PredictedLive, &obj)
244+
}
245+
if err != nil {
246+
continue
247+
}
248+
modifiedResources[kube.GetResourceKey(&obj)] = res.Modified
249+
}
250+
return modifiedResources
251+
}
252+
220253
const (
221254
crdReadinessTimeout = time.Duration(3) * time.Second
222255
)
@@ -281,6 +314,10 @@ type syncContext struct {
281314
namespaceModifier func(*unstructured.Unstructured) bool
282315

283316
syncWaveHook common.SyncWaveHook
317+
318+
applyOutOfSyncOnly bool
319+
// stores whether the resource is modified or not
320+
modificationResult map[kube.ResourceKey]bool
284321
}
285322

286323
func (sc *syncContext) setRunningPhase(tasks []*syncTask, isPendingDeletion bool) {
@@ -516,6 +553,13 @@ func (sc *syncContext) getSyncTasks() (_ syncTasks, successful bool) {
516553
continue
517554
}
518555

556+
if sc.applyOutOfSyncOnly {
557+
if modified, ok := sc.modificationResult[k]; !modified && ok {
558+
sc.log.WithValues("resource key", k).V(1).Info("Skipping as resource was not modified")
559+
continue
560+
}
561+
}
562+
519563
for _, phase := range syncPhases(obj) {
520564
resourceTasks = append(resourceTasks, &syncTask{phase: phase, targetObj: resource.Target, liveObj: resource.Live})
521565
}

pkg/sync/sync_context_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package sync
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
67
"reflect"
@@ -18,6 +19,7 @@ import (
1819
testcore "k8s.io/client-go/testing"
1920
"k8s.io/klog/v2/klogr"
2021

22+
"github.com/argoproj/gitops-engine/pkg/diff"
2123
"github.com/argoproj/gitops-engine/pkg/health"
2224
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
2325
"github.com/argoproj/gitops-engine/pkg/utils/kube"
@@ -1056,3 +1058,78 @@ func TestPruneLast(t *testing.T) {
10561058
assert.Equal(t, 3, tasks.lastWave())
10571059
})
10581060
}
1061+
1062+
func diffResultList() *diff.DiffResultList {
1063+
pod1 := NewPod()
1064+
pod1.SetName("pod-1")
1065+
pod2 := NewPod()
1066+
pod2.SetName("pod-2")
1067+
pod3 := NewPod()
1068+
pod3.SetName("pod-3")
1069+
1070+
diffResultList := diff.DiffResultList{
1071+
Modified: true,
1072+
Diffs: []diff.DiffResult{},
1073+
}
1074+
1075+
podBytes, _ := json.Marshal(pod1)
1076+
diffResultList.Diffs = append(diffResultList.Diffs, diff.DiffResult{NormalizedLive: []byte("null"), PredictedLive: podBytes, Modified: true})
1077+
1078+
podBytes, _ = json.Marshal(pod2)
1079+
diffResultList.Diffs = append(diffResultList.Diffs, diff.DiffResult{NormalizedLive: podBytes, PredictedLive: []byte("null"), Modified: true})
1080+
1081+
podBytes, _ = json.Marshal(pod3)
1082+
diffResultList.Diffs = append(diffResultList.Diffs, diff.DiffResult{NormalizedLive: podBytes, PredictedLive: podBytes, Modified: false})
1083+
1084+
return &diffResultList
1085+
}
1086+
1087+
func TestApplyOutOfSyncOnly(t *testing.T) {
1088+
pod1 := NewPod()
1089+
pod1.SetName("pod-1")
1090+
pod2 := NewPod()
1091+
pod2.SetName("pod-2")
1092+
pod3 := NewPod()
1093+
pod3.SetName("pod-3")
1094+
syncCtx := newTestSyncCtx()
1095+
1096+
t.Run("applyOutOfSyncOnly=false", func(t *testing.T) {
1097+
syncCtx.applyOutOfSyncOnly = true
1098+
syncCtx.modificationResult = nil
1099+
syncCtx.resources = groupResources(ReconciliationResult{
1100+
Live: []*unstructured.Unstructured{nil, pod2, pod3},
1101+
Target: []*unstructured.Unstructured{pod1, nil, pod3},
1102+
})
1103+
tasks, successful := syncCtx.getSyncTasks()
1104+
1105+
assert.True(t, successful)
1106+
assert.Len(t, tasks, 3)
1107+
})
1108+
1109+
syncCtx = newTestSyncCtx(WithResourceModificationChecker(true, diffResultList()))
1110+
t.Run("applyOutOfSyncOnly=true", func(t *testing.T) {
1111+
syncCtx.applyOutOfSyncOnly = true
1112+
syncCtx.resources = groupResources(ReconciliationResult{
1113+
Live: []*unstructured.Unstructured{nil, pod2, pod3},
1114+
Target: []*unstructured.Unstructured{pod1, nil, pod3},
1115+
})
1116+
tasks, successful := syncCtx.getSyncTasks()
1117+
1118+
assert.True(t, successful)
1119+
assert.Len(t, tasks, 2)
1120+
})
1121+
1122+
pod4 := NewPod()
1123+
pod4.SetName("pod-4")
1124+
t.Run("applyOutOfSyncOnly=true and missing resource key", func(t *testing.T) {
1125+
syncCtx.applyOutOfSyncOnly = true
1126+
syncCtx.resources = groupResources(ReconciliationResult{
1127+
Live: []*unstructured.Unstructured{nil, pod2, pod3, pod4},
1128+
Target: []*unstructured.Unstructured{pod1, nil, pod3, pod4},
1129+
})
1130+
tasks, successful := syncCtx.getSyncTasks()
1131+
1132+
assert.True(t, successful)
1133+
assert.Len(t, tasks, 3)
1134+
})
1135+
}

0 commit comments

Comments
 (0)