github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/admin/backup.go (about)

     1  package admin
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    12  	log "github.com/sirupsen/logrus"
    13  	"github.com/spf13/cobra"
    14  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    17  	"k8s.io/client-go/dynamic"
    18  	"k8s.io/client-go/tools/clientcmd"
    19  
    20  	"k8s.io/client-go/util/retry"
    21  	"sigs.k8s.io/yaml"
    22  
    23  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils"
    24  	"github.com/argoproj/argo-cd/v3/common"
    25  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    26  	"github.com/argoproj/argo-cd/v3/util/cli"
    27  	"github.com/argoproj/argo-cd/v3/util/errors"
    28  	"github.com/argoproj/argo-cd/v3/util/localconfig"
    29  	secutil "github.com/argoproj/argo-cd/v3/util/security"
    30  )
    31  
    32  // NewExportCommand defines a new command for exporting Kubernetes and Argo CD resources.
    33  func NewExportCommand() *cobra.Command {
    34  	var (
    35  		clientConfig             clientcmd.ClientConfig
    36  		out                      string
    37  		applicationNamespaces    []string
    38  		applicationsetNamespaces []string
    39  	)
    40  	command := cobra.Command{
    41  		Use:   "export",
    42  		Short: "Export all Argo CD data to stdout (default) or a file",
    43  		Run: func(c *cobra.Command, _ []string) {
    44  			ctx := c.Context()
    45  
    46  			config, err := clientConfig.ClientConfig()
    47  			errors.CheckError(err)
    48  			client, err := dynamic.NewForConfig(config)
    49  			errors.CheckError(err)
    50  			namespace, _, err := clientConfig.Namespace()
    51  			errors.CheckError(err)
    52  			acdClients := newArgoCDClientsets(config, namespace)
    53  
    54  			var writer io.Writer
    55  			if out == "-" {
    56  				writer = os.Stdout
    57  			} else {
    58  				f, err := os.Create(out)
    59  				errors.CheckError(err)
    60  				bw := bufio.NewWriter(f)
    61  				writer = bw
    62  				defer func() {
    63  					err = bw.Flush()
    64  					errors.CheckError(err)
    65  					err = f.Close()
    66  					errors.CheckError(err)
    67  				}()
    68  			}
    69  
    70  			if len(applicationNamespaces) == 0 || len(applicationsetNamespaces) == 0 {
    71  				defaultNs := getAdditionalNamespaces(ctx, acdClients.configMaps)
    72  				if len(applicationNamespaces) == 0 {
    73  					applicationNamespaces = defaultNs.applicationNamespaces
    74  				}
    75  				if len(applicationsetNamespaces) == 0 {
    76  					applicationsetNamespaces = defaultNs.applicationsetNamespaces
    77  				}
    78  			}
    79  			// To support applications and applicationsets in any namespace, we must list ALL namespaces and filter them afterwards
    80  			if len(applicationNamespaces) > 0 {
    81  				acdClients.applications = client.Resource(applicationsResource)
    82  			}
    83  			if len(applicationsetNamespaces) > 0 {
    84  				acdClients.applicationSets = client.Resource(appplicationSetResource)
    85  			}
    86  
    87  			acdConfigMap, err := acdClients.configMaps.Get(ctx, common.ArgoCDConfigMapName, metav1.GetOptions{})
    88  			errors.CheckError(err)
    89  			export(writer, *acdConfigMap, namespace)
    90  			acdRBACConfigMap, err := acdClients.configMaps.Get(ctx, common.ArgoCDRBACConfigMapName, metav1.GetOptions{})
    91  			errors.CheckError(err)
    92  			export(writer, *acdRBACConfigMap, namespace)
    93  			acdKnownHostsConfigMap, err := acdClients.configMaps.Get(ctx, common.ArgoCDKnownHostsConfigMapName, metav1.GetOptions{})
    94  			errors.CheckError(err)
    95  			export(writer, *acdKnownHostsConfigMap, namespace)
    96  			acdTLSCertsConfigMap, err := acdClients.configMaps.Get(ctx, common.ArgoCDTLSCertsConfigMapName, metav1.GetOptions{})
    97  			errors.CheckError(err)
    98  			export(writer, *acdTLSCertsConfigMap, namespace)
    99  
   100  			secrets, err := acdClients.secrets.List(ctx, metav1.ListOptions{})
   101  			errors.CheckError(err)
   102  			for _, secret := range secrets.Items {
   103  				if isArgoCDSecret(secret) {
   104  					export(writer, secret, namespace)
   105  				}
   106  			}
   107  
   108  			projects, err := acdClients.projects.List(ctx, metav1.ListOptions{})
   109  			errors.CheckError(err)
   110  			for _, proj := range projects.Items {
   111  				export(writer, proj, namespace)
   112  			}
   113  
   114  			applications, err := acdClients.applications.List(ctx, metav1.ListOptions{})
   115  			errors.CheckError(err)
   116  			for _, app := range applications.Items {
   117  				// Export application only if it is in one of the enabled namespaces
   118  				if secutil.IsNamespaceEnabled(app.GetNamespace(), namespace, applicationNamespaces) {
   119  					export(writer, app, namespace)
   120  				}
   121  			}
   122  			applicationSets, err := acdClients.applicationSets.List(ctx, metav1.ListOptions{})
   123  			if err != nil && !apierrors.IsNotFound(err) {
   124  				if apierrors.IsForbidden(err) {
   125  					log.Warn(err)
   126  				} else {
   127  					errors.CheckError(err)
   128  				}
   129  			}
   130  			if applicationSets != nil {
   131  				for _, appSet := range applicationSets.Items {
   132  					if secutil.IsNamespaceEnabled(appSet.GetNamespace(), namespace, applicationsetNamespaces) {
   133  						export(writer, appSet, namespace)
   134  					}
   135  				}
   136  			}
   137  		},
   138  	}
   139  
   140  	clientConfig = cli.AddKubectlFlagsToCmd(&command)
   141  	command.Flags().StringVarP(&out, "out", "o", "-", "Output to the specified file instead of stdout")
   142  	command.Flags().StringSliceVarP(&applicationNamespaces, "application-namespaces", "", []string{}, fmt.Sprintf("Comma-separated list of namespace globs to export applications from, in addition to the control plane namespace (Argo CD namespace). "+
   143  		"By default, all applications from the control plane namespace are always exported. "+
   144  		"If this flag is provided, applications from the specified namespaces are exported along with the control plane namespace. "+
   145  		"If not specified, the value from '%s' in %s is used (if defined in the ConfigMap). "+
   146  		"If the ConfigMap value is not set, only applications from the control plane namespace are exported.",
   147  		applicationNamespacesCmdParamsKey, common.ArgoCDCmdParamsConfigMapName))
   148  	command.Flags().StringSliceVarP(&applicationsetNamespaces, "applicationset-namespaces", "", []string{}, fmt.Sprintf("Comma-separated list of namespace globs to export ApplicationSets from, in addition to the control plane namespace (Argo CD namespace). "+
   149  		"By default, all ApplicationSets from the control plane namespace are always exported. "+
   150  		"If this flag is provided, ApplicationSets from the specified namespaces are exported along with the control plane namespace. "+
   151  		"If not specified, the value from '%s' in %s is used (if defined in the ConfigMap). "+
   152  		"If the ConfigMap value is not set, only ApplicationSets from the control plane namespace are exported.",
   153  		applicationsetNamespacesCmdParamsKey, common.ArgoCDCmdParamsConfigMapName))
   154  	return &command
   155  }
   156  
   157  // NewImportCommand defines a new command for exporting Kubernetes and Argo CD resources.
   158  func NewImportCommand() *cobra.Command {
   159  	var (
   160  		clientConfig             clientcmd.ClientConfig
   161  		prune                    bool
   162  		dryRun                   bool
   163  		verbose                  bool
   164  		stopOperation            bool
   165  		ignoreTracking           bool
   166  		overrideOnConflict       bool
   167  		promptsEnabled           bool
   168  		skipResourcesWithLabel   string
   169  		applicationNamespaces    []string
   170  		applicationsetNamespaces []string
   171  	)
   172  	command := cobra.Command{
   173  		Use:   "import SOURCE",
   174  		Short: "Import Argo CD data from stdin (specify `-') or a file",
   175  		Run: func(c *cobra.Command, args []string) {
   176  			ctx := c.Context()
   177  
   178  			if len(args) != 1 {
   179  				c.HelpFunc()(c, args)
   180  				os.Exit(1)
   181  			}
   182  			config, err := clientConfig.ClientConfig()
   183  			errors.CheckError(err)
   184  			config.QPS = 100
   185  			config.Burst = 50
   186  			namespace, _, err := clientConfig.Namespace()
   187  			errors.CheckError(err)
   188  			acdClients := newArgoCDClientsets(config, namespace)
   189  			client, err := dynamic.NewForConfig(config)
   190  			errors.CheckError(err)
   191  			fmt.Printf("import process started %s\n", namespace)
   192  			tt := time.Now()
   193  			var input []byte
   194  			if in := args[0]; in == "-" {
   195  				input, err = io.ReadAll(os.Stdin)
   196  			} else {
   197  				input, err = os.ReadFile(in)
   198  			}
   199  			errors.CheckError(err)
   200  			var dryRunMsg string
   201  			if dryRun {
   202  				dryRunMsg = " (dry run)"
   203  			}
   204  
   205  			if len(applicationNamespaces) == 0 || len(applicationsetNamespaces) == 0 {
   206  				defaultNs := getAdditionalNamespaces(ctx, acdClients.configMaps)
   207  				if len(applicationNamespaces) == 0 {
   208  					applicationNamespaces = defaultNs.applicationNamespaces
   209  				}
   210  				if len(applicationsetNamespaces) == 0 {
   211  					applicationsetNamespaces = defaultNs.applicationsetNamespaces
   212  				}
   213  			}
   214  			// To support applications and applicationsets in any namespace, we must list ALL namespaces and filter them afterwards
   215  			if len(applicationNamespaces) > 0 {
   216  				acdClients.applications = client.Resource(applicationsResource)
   217  			}
   218  			if len(applicationsetNamespaces) > 0 {
   219  				acdClients.applicationSets = client.Resource(appplicationSetResource)
   220  			}
   221  
   222  			// pruneObjects tracks live objects, and it's current resource version. any remaining
   223  			// items in this map indicates the resource should be pruned since it no longer appears
   224  			// in the backup
   225  			pruneObjects := make(map[kube.ResourceKey]unstructured.Unstructured)
   226  			configMaps, err := acdClients.configMaps.List(ctx, metav1.ListOptions{})
   227  
   228  			errors.CheckError(err)
   229  			for _, cm := range configMaps.Items {
   230  				if isArgoCDConfigMap(cm.GetName()) {
   231  					pruneObjects[kube.ResourceKey{Group: "", Kind: "ConfigMap", Name: cm.GetName(), Namespace: cm.GetNamespace()}] = cm
   232  				}
   233  			}
   234  
   235  			secrets, err := acdClients.secrets.List(ctx, metav1.ListOptions{})
   236  			errors.CheckError(err)
   237  			for _, secret := range secrets.Items {
   238  				if isArgoCDSecret(secret) {
   239  					pruneObjects[kube.ResourceKey{Group: "", Kind: "Secret", Name: secret.GetName(), Namespace: secret.GetNamespace()}] = secret
   240  				}
   241  			}
   242  			applications, err := acdClients.applications.List(ctx, metav1.ListOptions{})
   243  			errors.CheckError(err)
   244  			for _, app := range applications.Items {
   245  				if secutil.IsNamespaceEnabled(app.GetNamespace(), namespace, applicationNamespaces) {
   246  					pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.ApplicationKind, Name: app.GetName(), Namespace: app.GetNamespace()}] = app
   247  				}
   248  			}
   249  			projects, err := acdClients.projects.List(ctx, metav1.ListOptions{})
   250  			errors.CheckError(err)
   251  			for _, proj := range projects.Items {
   252  				pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.AppProjectKind, Name: proj.GetName(), Namespace: proj.GetNamespace()}] = proj
   253  			}
   254  			applicationSets, err := acdClients.applicationSets.List(ctx, metav1.ListOptions{})
   255  			if apierrors.IsForbidden(err) || apierrors.IsNotFound(err) {
   256  				log.Warnf("argoproj.io/ApplicationSet: %v\n", err)
   257  			} else {
   258  				errors.CheckError(err)
   259  			}
   260  			if applicationSets != nil {
   261  				for _, appSet := range applicationSets.Items {
   262  					if secutil.IsNamespaceEnabled(appSet.GetNamespace(), namespace, applicationsetNamespaces) {
   263  						pruneObjects[kube.ResourceKey{Group: application.Group, Kind: application.ApplicationSetKind, Name: appSet.GetName(), Namespace: appSet.GetNamespace()}] = appSet
   264  					}
   265  				}
   266  			}
   267  			// Create or replace existing object
   268  			backupObjects, err := kube.SplitYAML(input)
   269  
   270  			errors.CheckError(err)
   271  			for _, bakObj := range backupObjects {
   272  				gvk := bakObj.GroupVersionKind()
   273  				// For objects without namespace, assume they belong in ArgoCD namespace
   274  				if bakObj.GetNamespace() == "" {
   275  					bakObj.SetNamespace(namespace)
   276  				}
   277  				key := kube.ResourceKey{Group: gvk.Group, Kind: gvk.Kind, Name: bakObj.GetName(), Namespace: bakObj.GetNamespace()}
   278  				liveObj, exists := pruneObjects[key]
   279  				delete(pruneObjects, key)
   280  
   281  				// If the resource in backup matches the skip label, do not import it
   282  				if isSkipLabelMatches(bakObj, skipResourcesWithLabel) {
   283  					fmt.Printf("Skipping %s/%s %s in namespace %s\n", bakObj.GroupVersionKind().Group, bakObj.GroupVersionKind().Kind, bakObj.GetName(), bakObj.GetNamespace())
   284  					continue
   285  				}
   286  
   287  				var dynClient dynamic.ResourceInterface
   288  				switch bakObj.GetKind() {
   289  				case "Secret":
   290  					dynClient = client.Resource(secretResource).Namespace(bakObj.GetNamespace())
   291  				case "ConfigMap":
   292  					dynClient = client.Resource(configMapResource).Namespace(bakObj.GetNamespace())
   293  				case application.AppProjectKind:
   294  					dynClient = client.Resource(appprojectsResource).Namespace(bakObj.GetNamespace())
   295  				case application.ApplicationKind:
   296  					// If application is not in one of the allowed namespaces do not import it
   297  					if !secutil.IsNamespaceEnabled(bakObj.GetNamespace(), namespace, applicationNamespaces) {
   298  						continue
   299  					}
   300  					dynClient = client.Resource(applicationsResource).Namespace(bakObj.GetNamespace())
   301  				case application.ApplicationSetKind:
   302  					// If applicationset is not in one of the allowed namespaces do not import it
   303  					if !secutil.IsNamespaceEnabled(bakObj.GetNamespace(), namespace, applicationsetNamespaces) {
   304  						continue
   305  					}
   306  					dynClient = client.Resource(appplicationSetResource).Namespace(bakObj.GetNamespace())
   307  				}
   308  
   309  				// If there is a live object, remove the tracking annotations/label that might conflict
   310  				// when argo is managed with an application.
   311  				if ignoreTracking && exists {
   312  					updateTracking(bakObj, &liveObj)
   313  				}
   314  
   315  				switch {
   316  				case !exists:
   317  					isForbidden := false
   318  					if !dryRun {
   319  						_, err = dynClient.Create(ctx, bakObj, metav1.CreateOptions{})
   320  						if apierrors.IsForbidden(err) || apierrors.IsNotFound(err) {
   321  							isForbidden = true
   322  							log.Warnf("%s/%s %s: %v", gvk.Group, gvk.Kind, bakObj.GetName(), err)
   323  						} else {
   324  							errors.CheckError(err)
   325  						}
   326  					}
   327  					if !isForbidden {
   328  						fmt.Printf("%s/%s %s in namespace %s created%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), bakObj.GetNamespace(), dryRunMsg)
   329  					}
   330  				case specsEqual(*bakObj, liveObj) && checkAppHasNoNeedToStopOperation(liveObj, stopOperation):
   331  					if verbose {
   332  						fmt.Printf("%s/%s %s unchanged%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), dryRunMsg)
   333  					}
   334  				default:
   335  					isForbidden := false
   336  					if !dryRun {
   337  						newLive := updateLive(bakObj, &liveObj, stopOperation)
   338  						_, err = dynClient.Update(ctx, newLive, metav1.UpdateOptions{})
   339  						if apierrors.IsConflict(err) {
   340  							fmt.Printf("Failed to update %s/%s %s in namespace %s: %v\n", gvk.Group, gvk.Kind, bakObj.GetName(), bakObj.GetNamespace(), err)
   341  							if overrideOnConflict {
   342  								err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
   343  									fmt.Printf("Resource conflict: retrying update for Group: %s, Kind: %s, Name: %s, Namespace: %s\n", gvk.Group, gvk.Kind, bakObj.GetName(), bakObj.GetNamespace())
   344  									liveObj, getErr := dynClient.Get(ctx, newLive.GetName(), metav1.GetOptions{})
   345  									if getErr != nil {
   346  										errors.CheckError(getErr)
   347  									}
   348  									newLive.SetResourceVersion(liveObj.GetResourceVersion())
   349  									_, err = dynClient.Update(ctx, newLive, metav1.UpdateOptions{})
   350  									return err
   351  								})
   352  							}
   353  						}
   354  						if apierrors.IsForbidden(err) || apierrors.IsNotFound(err) {
   355  							isForbidden = true
   356  							log.Warnf("%s/%s %s: %v", gvk.Group, gvk.Kind, bakObj.GetName(), err)
   357  						} else {
   358  							errors.CheckError(err)
   359  						}
   360  					}
   361  					if !isForbidden {
   362  						fmt.Printf("%s/%s %s in namespace %s updated%s\n", gvk.Group, gvk.Kind, bakObj.GetName(), bakObj.GetNamespace(), dryRunMsg)
   363  					}
   364  				}
   365  			}
   366  
   367  			promptUtil := utils.NewPrompt(promptsEnabled)
   368  
   369  			// Delete objects not in backup
   370  			for key, liveObj := range pruneObjects {
   371  				// If a live resource has a label to skip the import, it should never be pruned
   372  				if isSkipLabelMatches(&liveObj, skipResourcesWithLabel) {
   373  					fmt.Printf("Skipping pruning of %s/%s %s in namespace %s\n", key.Group, key.Kind, liveObj.GetName(), liveObj.GetNamespace())
   374  					continue
   375  				}
   376  
   377  				if prune {
   378  					var dynClient dynamic.ResourceInterface
   379  					switch key.Kind {
   380  					case "Secret":
   381  						dynClient = client.Resource(secretResource).Namespace(liveObj.GetNamespace())
   382  					case application.AppProjectKind:
   383  						dynClient = client.Resource(appprojectsResource).Namespace(liveObj.GetNamespace())
   384  					case application.ApplicationKind:
   385  						dynClient = client.Resource(applicationsResource).Namespace(liveObj.GetNamespace())
   386  						if !dryRun {
   387  							if finalizers := liveObj.GetFinalizers(); len(finalizers) > 0 {
   388  								newLive := liveObj.DeepCopy()
   389  								newLive.SetFinalizers(nil)
   390  								_, err = dynClient.Update(ctx, newLive, metav1.UpdateOptions{})
   391  								if err != nil && !apierrors.IsNotFound(err) {
   392  									errors.CheckError(err)
   393  								}
   394  							}
   395  						}
   396  					case application.ApplicationSetKind:
   397  						dynClient = client.Resource(appplicationSetResource).Namespace(liveObj.GetNamespace())
   398  					default:
   399  						log.Fatalf("Unexpected kind '%s' in prune list", key.Kind)
   400  					}
   401  					isForbidden := false
   402  
   403  					if !dryRun {
   404  						canPrune := promptUtil.Confirm(fmt.Sprintf("Are you sure you want to prune %s/%s %s ? [y/n]", key.Group, key.Kind, key.Name))
   405  						if canPrune {
   406  							err = dynClient.Delete(ctx, key.Name, metav1.DeleteOptions{})
   407  							if apierrors.IsForbidden(err) || apierrors.IsNotFound(err) {
   408  								isForbidden = true
   409  								log.Warnf("%s/%s %s: %v\n", key.Group, key.Kind, key.Name, err)
   410  							} else {
   411  								errors.CheckError(err)
   412  							}
   413  						} else {
   414  							fmt.Printf("The command to prune %s/%s %s was cancelled.\n", key.Group, key.Kind, key.Name)
   415  						}
   416  					}
   417  					if !isForbidden {
   418  						fmt.Printf("%s/%s %s pruned%s\n", key.Group, key.Kind, key.Name, dryRunMsg)
   419  					}
   420  				} else {
   421  					fmt.Printf("%s/%s %s needs pruning\n", key.Group, key.Kind, key.Name)
   422  				}
   423  			}
   424  			duration := time.Since(tt)
   425  			fmt.Printf("Import process completed successfully in namespace %s at %s, duration: %s\n", namespace, time.Now().Format(time.RFC3339), duration)
   426  		},
   427  	}
   428  
   429  	clientConfig = cli.AddKubectlFlagsToCmd(&command)
   430  	command.Flags().BoolVar(&dryRun, "dry-run", false, "Print what will be performed")
   431  	command.Flags().BoolVar(&prune, "prune", false, "Prune secrets, applications and projects which do not appear in the backup")
   432  	command.Flags().BoolVar(&ignoreTracking, "ignore-tracking", false, "Do not update the tracking annotation if the resource is already tracked")
   433  	command.Flags().BoolVar(&overrideOnConflict, "override-on-conflict", false, "Override the resource on conflict when updating resources")
   434  	command.Flags().BoolVar(&verbose, "verbose", false, "Verbose output (versus only changed output)")
   435  	command.Flags().BoolVar(&stopOperation, "stop-operation", false, "Stop any existing operations")
   436  	command.Flags().StringVarP(&skipResourcesWithLabel, "skip-resources-with-label", "", "", "Skip importing resources based on the label e.g. '--skip-resources-with-label my-label/example.io=true'")
   437  	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))
   438  	command.Flags().StringSliceVarP(&applicationsetNamespaces, "applicationset-namespaces", "", []string{}, fmt.Sprintf("Comma separated list of namespace globs which import of applicationsets is allowed. If not provided, value from '%s' in %s will be used. If it's not defined, only applicationsets without an explicit namespace will be imported to the Argo CD namespace", applicationsetNamespacesCmdParamsKey, common.ArgoCDCmdParamsConfigMapName))
   439  	command.PersistentFlags().BoolVar(&promptsEnabled, "prompts-enabled", localconfig.GetPromptsEnabled(true), "Force optional interactive prompts to be enabled or disabled, overriding local configuration. If not specified, the local configuration value will be used, which is false by default.")
   440  	return &command
   441  }
   442  
   443  // check app has no need to stop operation.
   444  func checkAppHasNoNeedToStopOperation(liveObj unstructured.Unstructured, stopOperation bool) bool {
   445  	if !stopOperation {
   446  		return true
   447  	}
   448  	if liveObj.GetKind() == application.ApplicationKind {
   449  		return liveObj.Object["operation"] == nil
   450  	}
   451  	return true
   452  }
   453  
   454  // export writes the unstructured object and removes extraneous cruft from output before writing
   455  func export(w io.Writer, un unstructured.Unstructured, argocdNamespace string) {
   456  	name := un.GetName()
   457  	finalizers := un.GetFinalizers()
   458  	apiVersion := un.GetAPIVersion()
   459  	kind := un.GetKind()
   460  	labels := un.GetLabels()
   461  	annotations := un.GetAnnotations()
   462  	namespace := un.GetNamespace()
   463  	unstructured.RemoveNestedField(un.Object, "metadata")
   464  	un.SetName(name)
   465  	un.SetFinalizers(finalizers)
   466  	un.SetAPIVersion(apiVersion)
   467  	un.SetKind(kind)
   468  	un.SetLabels(labels)
   469  	un.SetAnnotations(annotations)
   470  	if namespace != argocdNamespace {
   471  		// Explicitly add the namespace for appset and apps in any namespace
   472  		un.SetNamespace(namespace)
   473  	}
   474  	data, err := yaml.Marshal(un.Object)
   475  	errors.CheckError(err)
   476  	_, err = w.Write(data)
   477  	errors.CheckError(err)
   478  	_, err = w.Write([]byte(yamlSeparator))
   479  	errors.CheckError(err)
   480  }
   481  
   482  // updateLive replaces the live object's finalizers, spec, annotations, labels, and data from the
   483  // backup object but leaves all other fields intact (status, other metadata, etc...)
   484  func updateLive(bak, live *unstructured.Unstructured, stopOperation bool) *unstructured.Unstructured {
   485  	newLive := live.DeepCopy()
   486  	newLive.SetAnnotations(bak.GetAnnotations())
   487  	newLive.SetLabels(bak.GetLabels())
   488  	newLive.SetFinalizers(bak.GetFinalizers())
   489  	switch live.GetKind() {
   490  	case "Secret", "ConfigMap":
   491  		newLive.Object["data"] = bak.Object["data"]
   492  	case application.AppProjectKind:
   493  		newLive.Object["spec"] = bak.Object["spec"]
   494  	case application.ApplicationKind:
   495  		newLive.Object["spec"] = bak.Object["spec"]
   496  		if _, ok := bak.Object["status"]; ok {
   497  			newLive.Object["status"] = bak.Object["status"]
   498  		}
   499  		if stopOperation {
   500  			newLive.Object["operation"] = nil
   501  		}
   502  
   503  	case "ApplicationSet":
   504  		newLive.Object["spec"] = bak.Object["spec"]
   505  	}
   506  	return newLive
   507  }
   508  
   509  // updateTracking will update the tracking label and annotation in the bak resources to the
   510  // value of the live resource.
   511  func updateTracking(bak, live *unstructured.Unstructured) {
   512  	// update the common annotation
   513  	bakAnnotations := bak.GetAnnotations()
   514  	liveAnnotations := live.GetAnnotations()
   515  	if liveAnnotations != nil && bakAnnotations != nil {
   516  		if v, ok := liveAnnotations[common.AnnotationKeyAppInstance]; ok {
   517  			if _, ok := bakAnnotations[common.AnnotationKeyAppInstance]; ok {
   518  				bakAnnotations[common.AnnotationKeyAppInstance] = v
   519  				bak.SetAnnotations(bakAnnotations)
   520  			}
   521  		}
   522  	}
   523  
   524  	// update the common label
   525  	// A custom label can be set, but it is impossible to know which instance is managing the application
   526  	bakLabels := bak.GetLabels()
   527  	liveLabels := live.GetLabels()
   528  	if liveLabels != nil && bakLabels != nil {
   529  		if v, ok := liveLabels[common.LabelKeyAppInstance]; ok {
   530  			if _, ok := bakLabels[common.LabelKeyAppInstance]; ok {
   531  				bakLabels[common.LabelKeyAppInstance] = v
   532  				bak.SetLabels(bakLabels)
   533  			}
   534  		}
   535  	}
   536  }
   537  
   538  // isSkipLabelMatches return if the resource should be skipped based on the labels
   539  func isSkipLabelMatches(obj *unstructured.Unstructured, skipResourcesWithLabel string) bool {
   540  	if skipResourcesWithLabel == "" {
   541  		return false
   542  	}
   543  	parts := strings.SplitN(skipResourcesWithLabel, "=", 2)
   544  	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
   545  		return false
   546  	}
   547  	key, value := parts[0], parts[1]
   548  	if val, ok := obj.GetLabels()[key]; ok && val == value {
   549  		return true
   550  	}
   551  	return false
   552  }