Skip to content

Commit

Permalink
feat: add support for server-side apply
Browse files Browse the repository at this point in the history
See argoproj/argo-cd#2267

Signed-off-by: Mathieu Parent <[email protected]>
  • Loading branch information
sathieu committed Jan 8, 2022
1 parent f649502 commit 7119676
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 7 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 --server-side flag instead of client-side
SyncOptionServerSideApply = "ServerSideApply=true"
)

type PermissionValidator func(un *unstructured.Unstructured, res *metav1.APIResource) error
Expand Down
12 changes: 10 additions & 2 deletions pkg/sync/sync_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ func WithReplace(replace bool) SyncOpt {
}
}

func WithServerSideApply(serverSideApply bool) SyncOpt {
return func(ctx *syncContext) {
ctx.serverSideApply = serverSideApply
}
}

// NewSyncContext creates new instance of a SyncContext
func NewSyncContext(
revision string,
Expand Down Expand Up @@ -320,6 +326,7 @@ type syncContext struct {
resourcesFilter func(key kube.ResourceKey, target *unstructured.Unstructured, live *unstructured.Unstructured) bool
prune bool
replace bool
serverSideApply bool
pruneLast bool
prunePropagationPolicy *metav1.DeletionPropagation

Expand Down Expand Up @@ -847,7 +854,7 @@ func (sc *syncContext) ensureCRDReady(name string) {
})
}

func (sc *syncContext) applyObject(t *syncTask, dryRun bool, force bool, validate bool) (common.ResultCode, string) {
func (sc *syncContext) applyObject(t *syncTask, dryRun, force, validate bool) (common.ResultCode, string) {
dryRunStrategy := cmdutil.DryRunNone
if dryRun {
dryRunStrategy = cmdutil.DryRunClient
Expand All @@ -856,6 +863,7 @@ func (sc *syncContext) applyObject(t *syncTask, dryRun bool, force bool, validat
var err error
var message string
shouldReplace := sc.replace || resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionReplace)
serverSideApply := sc.serverSideApply || resourceutil.HasAnnotationOption(t.targetObj, common.AnnotationSyncOptions, common.SyncOptionServerSideApply)
if shouldReplace {
if t.liveObj != nil {
// Avoid using `kubectl replace` for CRDs since 'replace' might recreate resource and so delete all CRD instances
Expand All @@ -875,7 +883,7 @@ func (sc *syncContext) applyObject(t *syncTask, dryRun bool, force bool, validat
message, err = sc.resourceOps.CreateResource(context.TODO(), t.targetObj, dryRunStrategy, validate)
}
} else {
message, err = sc.resourceOps.ApplyResource(context.TODO(), t.targetObj, dryRunStrategy, force, validate)
message, err = sc.resourceOps.ApplyResource(context.TODO(), t.targetObj, dryRunStrategy, force, validate, serverSideApply)
}
if err != nil {
return common.ResultCodeSyncFailed, err.Error()
Expand Down
46 changes: 46 additions & 0 deletions pkg/sync/sync_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,52 @@ func TestSync_Replace(t *testing.T) {
}
}

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

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

func TestSync_ServerSideApply(t *testing.T) {
testCases := []struct {
name string
target *unstructured.Unstructured
live *unstructured.Unstructured
commandUsed string
serverSideApply bool
}{
{"NoAnnotation", NewPod(), NewPod(), "apply", false},
{"ServerSideApplyAnnotationIsSet", withServerSideApplyAnnotation(NewPod()), NewPod(), "apply", true},
{"ServerSideApplyAndReplaceAnnotationsAreSet", withReplaceAndServerSideApplyAnnotations(NewPod()), NewPod(), "replace", false},
{"LiveObjectMissing", withReplaceAnnotation(NewPod()), nil, "create", false},
}

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

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

kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
assert.Equal(t, tc.commandUsed, kubectl.GetLastResourceCommand(kube.GetResourceKey(tc.target)))
assert.Equal(t, tc.serverSideApply, kubectl.GetLastServerSideApply())
})
}
}

func TestSelectiveSyncOnly(t *testing.T) {
pod1 := NewPod()
pod1.SetName("pod-1")
Expand Down
17 changes: 16 additions & 1 deletion pkg/utils/kube/kubetest/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type MockKubectlCmd struct {

lastCommandPerResource map[kube.ResourceKey]string
lastValidate bool
serverSideApply bool
recordLock sync.RWMutex
}

Expand Down Expand Up @@ -65,6 +66,19 @@ func (k *MockKubectlCmd) GetLastValidate() bool {
return validate
}

func (k *MockKubectlCmd) SetLastServerSideApply(serverSideApply bool) {
k.recordLock.Lock()
k.serverSideApply = serverSideApply
k.recordLock.Unlock()
}

func (k *MockKubectlCmd) GetLastServerSideApply() bool {
k.recordLock.RLock()
serverSideApply := k.serverSideApply
k.recordLock.RUnlock()
return serverSideApply
}

func (k *MockKubectlCmd) NewDynamicClient(config *rest.Config) (dynamic.Interface, error) {
return k.DynamicClient, nil
}
Expand Down Expand Up @@ -107,8 +121,9 @@ func (k *MockKubectlCmd) UpdateResource(ctx context.Context, obj *unstructured.U
return obj, command.Err
}

func (k *MockKubectlCmd) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate bool) (string, error) {
func (k *MockKubectlCmd) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool) (string, error) {
k.SetLastValidate(validate)
k.SetLastServerSideApply(serverSideApply)
k.SetLastResourceCommand(kube.GetResourceKey(obj), "apply")
command, ok := k.Commands[obj.GetName()]
if !ok {
Expand Down
9 changes: 5 additions & 4 deletions pkg/utils/kube/resource_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (

// ResourceOperations provides methods to manage k8s resources
type ResourceOperations interface {
ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate bool) (string, error)
ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool) (string, error)
ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool) (string, error)
CreateResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, validate bool) (string, error)
UpdateResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy) (*unstructured.Unstructured, error)
Expand Down Expand Up @@ -220,7 +220,7 @@ func (k *kubectlResourceOperations) UpdateResource(ctx context.Context, obj *uns
}

// ApplyResource performs an apply of a unstructured resource
func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate bool) (string, error) {
func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool) (string, error) {
span := k.tracer.StartSpan("ApplyResource")
span.SetBaggageItem("kind", obj.GetKind())
span.SetBaggageItem("name", obj.GetName())
Expand All @@ -233,15 +233,15 @@ func (k *kubectlResourceOperations) ApplyResource(ctx context.Context, obj *unst
}
defer cleanup()

applyOpts, err := k.newApplyOptions(ioStreams, obj, fileName, validate, force, dryRunStrategy)
applyOpts, err := k.newApplyOptions(ioStreams, obj, fileName, validate, force, serverSideApply, dryRunStrategy)
if err != nil {
return err
}
return applyOpts.Run()
})
}

func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericclioptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force bool, dryRunStrategy cmdutil.DryRunStrategy) (*apply.ApplyOptions, error) {
func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericclioptions.IOStreams, obj *unstructured.Unstructured, fileName string, validate bool, force, serverSideApply bool, dryRunStrategy cmdutil.DryRunStrategy) (*apply.ApplyOptions, error) {
o := apply.NewApplyOptions(ioStreams)
dynamicClient, err := dynamic.NewForConfig(k.config)
if err != nil {
Expand Down Expand Up @@ -287,6 +287,7 @@ func (k *kubectlResourceOperations) newApplyOptions(ioStreams genericclioptions.
o.DeleteOptions.FilenameOptions.Filenames = []string{fileName}
o.Namespace = obj.GetNamespace()
o.DeleteOptions.ForceDeletion = force
o.ServerSideApply = serverSideApply
o.DryRunStrategy = dryRunStrategy
return o, nil
}
Expand Down

0 comments on commit 7119676

Please sign in to comment.