mirror of
https://github.com/argoproj/argo-cd
synced 2026-04-21 17:07:16 +00:00
feat: add SSA field manager migration options (#23337)
Signed-off-by: Peter Jiang <peterjiang823@gmail.com> Signed-off-by: Peter Jiang <35584807+pjiang-dev@users.noreply.github.com>
This commit is contained in:
parent
7a064000a0
commit
dc1d148a5d
4 changed files with 155 additions and 0 deletions
|
|
@ -196,6 +196,9 @@ const (
|
|||
// AnnotationCompareOptions is a comma-separated list of options for comparison
|
||||
AnnotationCompareOptions = "argocd.argoproj.io/compare-options"
|
||||
|
||||
// AnnotationClientSideApplyMigrationManager specifies a custom field manager for client-side apply migration
|
||||
AnnotationClientSideApplyMigrationManager = "argocd.argoproj.io/client-side-apply-migration-manager"
|
||||
|
||||
// AnnotationIgnoreHealthCheck when set on an Application's immediate child indicates that its health check
|
||||
// can be disregarded.
|
||||
AnnotationIgnoreHealthCheck = "argocd.argoproj.io/ignore-healthcheck"
|
||||
|
|
|
|||
|
|
@ -284,6 +284,12 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
|||
prunePropagationPolicy = metav1.DeletePropagationOrphan
|
||||
}
|
||||
|
||||
clientSideApplyManager := common.DefaultClientSideApplyMigrationManager
|
||||
// Check for custom field manager from application annotation
|
||||
if managerValue := app.GetAnnotation(cdcommon.AnnotationClientSideApplyMigrationManager); managerValue != "" {
|
||||
clientSideApplyManager = managerValue
|
||||
}
|
||||
|
||||
openAPISchema, err := m.getOpenAPISchema(destCluster)
|
||||
if err != nil {
|
||||
state.Phase = common.OperationError
|
||||
|
|
@ -376,6 +382,10 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
|||
sync.WithReplace(syncOp.SyncOptions.HasOption(common.SyncOptionReplace)),
|
||||
sync.WithServerSideApply(syncOp.SyncOptions.HasOption(common.SyncOptionServerSideApply)),
|
||||
sync.WithServerSideApplyManager(cdcommon.ArgoCDSSAManager),
|
||||
sync.WithClientSideApplyMigration(
|
||||
!syncOp.SyncOptions.HasOption(common.SyncOptionDisableClientSideApplyMigration),
|
||||
clientSideApplyManager,
|
||||
),
|
||||
sync.WithPruneConfirmed(app.IsDeletionConfirmed(state.StartedAt.Time)),
|
||||
sync.WithSkipDryRunOnMissingResource(syncOp.SyncOptions.HasOption(common.SyncOptionSkipDryRunOnMissingResource)),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1412,6 +1412,110 @@ func TestSyncWithImpersonate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestClientSideApplyMigration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type fixture struct {
|
||||
application *v1alpha1.Application
|
||||
controller *ApplicationController
|
||||
}
|
||||
|
||||
setup := func(disableMigration bool, customManager string) *fixture {
|
||||
app := newFakeApp()
|
||||
app.Status.OperationState = nil
|
||||
app.Status.History = nil
|
||||
|
||||
// Add sync options
|
||||
if disableMigration {
|
||||
app.Spec.SyncPolicy.SyncOptions = append(app.Spec.SyncPolicy.SyncOptions, "DisableClientSideApplyMigration=true")
|
||||
}
|
||||
|
||||
// Add custom manager annotation if specified
|
||||
if customManager != "" {
|
||||
app.Annotations = map[string]string{
|
||||
"argocd.argoproj.io/client-side-apply-migration-manager": customManager,
|
||||
}
|
||||
}
|
||||
|
||||
project := &v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
Name: "default",
|
||||
},
|
||||
}
|
||||
data := fakeData{
|
||||
apps: []runtime.Object{app, project},
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: test.FakeClusterURL,
|
||||
Revision: "abc123",
|
||||
},
|
||||
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
|
||||
}
|
||||
ctrl := newFakeController(&data, nil)
|
||||
|
||||
return &fixture{
|
||||
application: app,
|
||||
controller: ctrl,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("client-side apply migration enabled by default", func(t *testing.T) {
|
||||
// given
|
||||
t.Parallel()
|
||||
f := setup(false, "")
|
||||
|
||||
// when
|
||||
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{
|
||||
Source: &v1alpha1.ApplicationSource{},
|
||||
},
|
||||
}}
|
||||
f.controller.appStateManager.SyncAppState(f.application, opState)
|
||||
|
||||
// then
|
||||
assert.Equal(t, common.OperationSucceeded, opState.Phase)
|
||||
assert.Contains(t, opState.Message, "successfully synced")
|
||||
})
|
||||
|
||||
t.Run("client-side apply migration disabled", func(t *testing.T) {
|
||||
// given
|
||||
t.Parallel()
|
||||
f := setup(true, "")
|
||||
|
||||
// when
|
||||
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{
|
||||
Source: &v1alpha1.ApplicationSource{},
|
||||
},
|
||||
}}
|
||||
f.controller.appStateManager.SyncAppState(f.application, opState)
|
||||
|
||||
// then
|
||||
assert.Equal(t, common.OperationSucceeded, opState.Phase)
|
||||
assert.Contains(t, opState.Message, "successfully synced")
|
||||
})
|
||||
|
||||
t.Run("client-side apply migration with custom manager", func(t *testing.T) {
|
||||
// given
|
||||
t.Parallel()
|
||||
f := setup(false, "my-custom-manager")
|
||||
|
||||
// when
|
||||
opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{
|
||||
Source: &v1alpha1.ApplicationSource{},
|
||||
},
|
||||
}}
|
||||
f.controller.appStateManager.SyncAppState(f.application, opState)
|
||||
|
||||
// then
|
||||
assert.Equal(t, common.OperationSucceeded, opState.Phase)
|
||||
assert.Contains(t, opState.Message, "successfully synced")
|
||||
})
|
||||
}
|
||||
|
||||
func dig[T any](obj any, path []any) T {
|
||||
i := obj
|
||||
|
||||
|
|
|
|||
|
|
@ -293,6 +293,44 @@ to apply changes.
|
|||
|
||||
Note: [`Replace=true`](#replace-resource-instead-of-applying-changes) takes precedence over `ServerSideApply=true`.
|
||||
|
||||
### Client-Side Apply Migration
|
||||
|
||||
Argo CD supports client-side apply migration, which helps transitioning from client-side apply to server-side apply by moving a resource's managed fields from one manager to Argo CD's manager. This feature is particularly useful when you need to migrate existing resources that were created using kubectl client-side apply to server-side apply with Argo CD.
|
||||
|
||||
By default, client-side apply migration is enabled. You can disable it using the sync option:
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
spec:
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- DisableClientSideApplyMigration=true
|
||||
```
|
||||
|
||||
You can specify a custom field manager for the client-side apply migration using an annotation:
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/client-side-apply-migration-manager: "my-custom-manager"
|
||||
```
|
||||
|
||||
This is useful when you have other operators managing resources that are no longer in use and would like Argo CD to own all the fields for that operator.
|
||||
|
||||
### How it works
|
||||
|
||||
When client-side apply migration is enabled:
|
||||
1. Argo CD will use the specified field manager (or default if not specified) to perform migration
|
||||
2. During a server-side apply sync operation, it will:
|
||||
- Perfirm a client-side-apply with the specified field manager
|
||||
- Move the 'last-appled-configuration' annotation to be managed by the specified manager
|
||||
- Perform the server-side apply, which will auto migrate all the fields under the manager that owns the 'last-applied-configration' annotation.
|
||||
|
||||
This feature is based on Kubernetes' [client-side apply migration KEP](https://github.com/alexzielenski/enhancements/blob/03df8820b9feca6d2cab78e303c99b2c9c0c4c5c/keps/sig-cli/3517-kubectl-client-side-apply-migration/README.md), which provides the auto migration from client-side to server-side apply.
|
||||
|
||||
## Fail the sync if a shared resource is found
|
||||
|
||||
By default, Argo CD will apply all manifests found in the git path configured in the Application regardless if the resources defined in the yamls are already applied by another Application. If the `FailOnSharedResource` sync option is set, Argo CD will fail the sync whenever it finds a resource in the current Application that is already applied in the cluster by another Application.
|
||||
|
|
|
|||
Loading…
Reference in a new issue