Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): ignore tracking annotation on backup restore #18960

Merged
merged 11 commits into from
Sep 9, 2024
6 changes: 5 additions & 1 deletion cmd/argocd/commands/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,11 @@ func isArgoCDConfigMap(name string) bool {
// specsEqual returns if the spec, data, labels, annotations, and finalizers of the two
// supplied objects are equal, indicating that no update is necessary during importing
func specsEqual(left, right unstructured.Unstructured) bool {
if !reflect.DeepEqual(left.GetAnnotations(), right.GetAnnotations()) {
leftAnnotation := left.GetAnnotations()
rightAnnotation := right.GetAnnotations()
delete(leftAnnotation, apiv1.LastAppliedConfigAnnotation)
delete(rightAnnotation, apiv1.LastAppliedConfigAnnotation)
if !reflect.DeepEqual(leftAnnotation, rightAnnotation) {
return false
}
if !reflect.DeepEqual(left.GetLabels(), right.GetLabels()) {
Expand Down
38 changes: 38 additions & 0 deletions cmd/argocd/commands/admin/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func NewImportCommand() *cobra.Command {
dryRun bool
verbose bool
stopOperation bool
ignoreTracking bool
applicationNamespaces []string
applicationsetNamespaces []string
)
Expand Down Expand Up @@ -264,6 +265,13 @@ func NewImportCommand() *cobra.Command {
continue
}
}

// If there is a live object, remove the tracking annotations/label that might conflict
// when argo is managed with an application.
if ignoreTracking && exists {
updateTracking(bakObj, &liveObj)
}

if !exists {
isForbidden := false
if !dryRun {
Expand Down Expand Up @@ -349,6 +357,7 @@ func NewImportCommand() *cobra.Command {
clientConfig = cli.AddKubectlFlagsToCmd(&command)
command.Flags().BoolVar(&dryRun, "dry-run", false, "Print what will be performed")
command.Flags().BoolVar(&prune, "prune", false, "Prune secrets, applications and projects which do not appear in the backup")
command.Flags().BoolVar(&ignoreTracking, "ignore-tracking", false, "Do not update the tracking annotation if the resource is already tracked")
command.Flags().BoolVar(&verbose, "verbose", false, "Verbose output (versus only changed output)")
command.Flags().BoolVar(&stopOperation, "stop-operation", false, "Stop any existing operations")
command.Flags().StringSliceVarP(&applicationNamespaces, "application-namespaces", "", []string{}, fmt.Sprintf("Comma separated list of namespace globs to which import of applications is allowed. If not provided value from '%s' in %s will be used,if it's not defined only applications without an explicit namespace will be imported to the Argo CD namespace", applicationNamespacesCmdParamsKey, common.ArgoCDCmdParamsConfigMapName))
Expand Down Expand Up @@ -422,3 +431,32 @@ func updateLive(bak, live *unstructured.Unstructured, stopOperation bool) *unstr
}
return newLive
}

// updateTracking will update the tracking label and annotation in the bak resources to the
// value of the live resource.
func updateTracking(bak, live *unstructured.Unstructured) {
leoluz marked this conversation as resolved.
Show resolved Hide resolved
// update the common annotation
bakAnnotations := bak.GetAnnotations()
liveAnnotations := live.GetAnnotations()
if liveAnnotations != nil && bakAnnotations != nil {
if v, ok := liveAnnotations[common.AnnotationKeyAppInstance]; ok {
if _, ok := bakAnnotations[common.AnnotationKeyAppInstance]; ok {
bakAnnotations[common.AnnotationKeyAppInstance] = v
bak.SetAnnotations(bakAnnotations)
}
}
}

// update the common label
// A custom label can be set, but it is impossible to know which instance is managing the application
bakLabels := bak.GetLabels()
liveLabels := live.GetLabels()
if liveLabels != nil && bakLabels != nil {
if v, ok := liveLabels[common.LabelKeyAppInstance]; ok {
if _, ok := bakLabels[common.LabelKeyAppInstance]; ok {
bakLabels[common.LabelKeyAppInstance] = v
bak.SetLabels(bakLabels)
}
}
}
}
87 changes: 87 additions & 0 deletions cmd/argocd/commands/admin/backup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package admin

import (
"testing"

"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/argoproj/argo-cd/v2/common"
)

func newBackupObject(trackingValue string, trackingLabel bool, trackingAnnotation bool) *unstructured.Unstructured {
cm := v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "my-configmap",
Namespace: "namespace",
},
Data: map[string]string{
"foo": "bar",
},
}
if trackingLabel {
cm.SetLabels(map[string]string{
common.LabelKeyAppInstance: trackingValue,
})
}
if trackingAnnotation {
cm.SetAnnotations(map[string]string{
common.AnnotationKeyAppInstance: trackingValue,
})
}
return kube.MustToUnstructured(&cm)
}

func Test_updateTracking(t *testing.T) {
type args struct {
bak *unstructured.Unstructured
live *unstructured.Unstructured
}
tests := []struct {
name string
args args
expected *unstructured.Unstructured
}{
{
name: "update annotation when present in live",
args: args{
bak: newBackupObject("bak", false, true),
live: newBackupObject("live", false, true),
},
expected: newBackupObject("live", false, true),
},
{
name: "update default label when present in live",
args: args{
bak: newBackupObject("bak", true, true),
live: newBackupObject("live", true, true),
},
expected: newBackupObject("live", true, true),
},
{
name: "do not update if live object does not have tracking",
args: args{
bak: newBackupObject("bak", true, true),
live: newBackupObject("live", false, false),
},
expected: newBackupObject("bak", true, true),
},
{
name: "do not update if bak object does not have tracking",
args: args{
bak: newBackupObject("bak", false, false),
live: newBackupObject("live", true, true),
},
expected: newBackupObject("bak", false, false),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
updateTracking(tt.args.bak, tt.args.live)
assert.Equal(t, tt.expected, tt.args.bak)
})
}
}
1 change: 1 addition & 0 deletions docs/user-guide/commands/argocd_admin_import.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion util/argo/resource_tracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func (rt *resourceTracking) ParseAppInstanceValue(value string) (*AppInstanceVal
return &appInstanceValue, nil
}

// Normalize updates live resource and removes diff caused but missing annotation or extra tracking label.
// Normalize updates live resource and removes diff caused by missing annotation or extra tracking label.
// The normalization is required to ensure smooth transition to new tracking method.
func (rt *resourceTracking) Normalize(config, live *unstructured.Unstructured, labelKey, trackingMethod string) error {
if IsOldTrackingMethod(trackingMethod) {
Expand Down