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

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	stderrors "errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"reflect"
    11  	"slices"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"text/tabwriter"
    17  	"time"
    18  	"unicode/utf8"
    19  
    20  	"github.com/argoproj/gitops-engine/pkg/health"
    21  	"github.com/argoproj/gitops-engine/pkg/sync/common"
    22  	"github.com/argoproj/gitops-engine/pkg/sync/hook"
    23  	"github.com/argoproj/gitops-engine/pkg/sync/ignore"
    24  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    25  	grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
    26  	"github.com/mattn/go-isatty"
    27  	log "github.com/sirupsen/logrus"
    28  	"github.com/spf13/cobra"
    29  	"google.golang.org/grpc/codes"
    30  	"google.golang.org/grpc/status"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	k8swatch "k8s.io/apimachinery/pkg/watch"
    36  	"k8s.io/utils/ptr"
    37  	"sigs.k8s.io/yaml"
    38  
    39  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
    40  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils"
    41  	cmdutil "github.com/argoproj/argo-cd/v3/cmd/util"
    42  	argocommon "github.com/argoproj/argo-cd/v3/common"
    43  	"github.com/argoproj/argo-cd/v3/controller"
    44  	argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
    45  	"github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
    46  
    47  	resourceutil "github.com/argoproj/gitops-engine/pkg/sync/resource"
    48  
    49  	clusterpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/cluster"
    50  	projectpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/project"
    51  	"github.com/argoproj/argo-cd/v3/pkg/apiclient/settings"
    52  	argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    53  	repoapiclient "github.com/argoproj/argo-cd/v3/reposerver/apiclient"
    54  	"github.com/argoproj/argo-cd/v3/reposerver/repository"
    55  	"github.com/argoproj/argo-cd/v3/util/argo"
    56  	argodiff "github.com/argoproj/argo-cd/v3/util/argo/diff"
    57  	"github.com/argoproj/argo-cd/v3/util/argo/normalizers"
    58  	"github.com/argoproj/argo-cd/v3/util/cli"
    59  	"github.com/argoproj/argo-cd/v3/util/errors"
    60  	"github.com/argoproj/argo-cd/v3/util/git"
    61  	"github.com/argoproj/argo-cd/v3/util/grpc"
    62  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    63  	logutils "github.com/argoproj/argo-cd/v3/util/log"
    64  	"github.com/argoproj/argo-cd/v3/util/manifeststream"
    65  	"github.com/argoproj/argo-cd/v3/util/templates"
    66  	"github.com/argoproj/argo-cd/v3/util/text/label"
    67  )
    68  
    69  // NewApplicationCommand returns a new instance of an `argocd app` command
    70  func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    71  	command := &cobra.Command{
    72  		Use:   "app",
    73  		Short: "Manage applications",
    74  		Example: `  # List all the applications.
    75    argocd app list
    76  
    77    # Get the details of a application
    78    argocd app get my-app
    79  
    80    # Set an override parameter
    81    argocd app set my-app -p image.tag=v1.0.1`,
    82  		Run: func(c *cobra.Command, args []string) {
    83  			c.HelpFunc()(c, args)
    84  			os.Exit(1)
    85  		},
    86  	}
    87  	command.AddCommand(NewApplicationCreateCommand(clientOpts))
    88  	command.AddCommand(NewApplicationGetCommand(clientOpts))
    89  	command.AddCommand(NewApplicationDiffCommand(clientOpts))
    90  	command.AddCommand(NewApplicationSetCommand(clientOpts))
    91  	command.AddCommand(NewApplicationUnsetCommand(clientOpts))
    92  	command.AddCommand(NewApplicationSyncCommand(clientOpts))
    93  	command.AddCommand(NewApplicationHistoryCommand(clientOpts))
    94  	command.AddCommand(NewApplicationRollbackCommand(clientOpts))
    95  	command.AddCommand(NewApplicationListCommand(clientOpts))
    96  	command.AddCommand(NewApplicationDeleteCommand(clientOpts))
    97  	command.AddCommand(NewApplicationWaitCommand(clientOpts))
    98  	command.AddCommand(NewApplicationManifestsCommand(clientOpts))
    99  	command.AddCommand(NewApplicationTerminateOpCommand(clientOpts))
   100  	command.AddCommand(NewApplicationEditCommand(clientOpts))
   101  	command.AddCommand(NewApplicationPatchCommand(clientOpts))
   102  	command.AddCommand(NewApplicationGetResourceCommand(clientOpts))
   103  	command.AddCommand(NewApplicationPatchResourceCommand(clientOpts))
   104  	command.AddCommand(NewApplicationDeleteResourceCommand(clientOpts))
   105  	command.AddCommand(NewApplicationResourceActionsCommand(clientOpts))
   106  	command.AddCommand(NewApplicationListResourcesCommand(clientOpts))
   107  	command.AddCommand(NewApplicationLogsCommand(clientOpts))
   108  	command.AddCommand(NewApplicationAddSourceCommand(clientOpts))
   109  	command.AddCommand(NewApplicationRemoveSourceCommand(clientOpts))
   110  	command.AddCommand(NewApplicationConfirmDeletionCommand(clientOpts))
   111  	return command
   112  }
   113  
   114  type watchOpts struct {
   115  	sync      bool
   116  	health    bool
   117  	operation bool
   118  	suspended bool
   119  	degraded  bool
   120  	delete    bool
   121  	hydrated  bool
   122  }
   123  
   124  // NewApplicationCreateCommand returns a new instance of an `argocd app create` command
   125  func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   126  	var (
   127  		appOpts      cmdutil.AppOptions
   128  		fileURL      string
   129  		appName      string
   130  		upsert       bool
   131  		labels       []string
   132  		annotations  []string
   133  		setFinalizer bool
   134  		appNamespace string
   135  	)
   136  	command := &cobra.Command{
   137  		Use:   "create APPNAME",
   138  		Short: "Create an application",
   139  		Example: `  # Create a directory app
   140    argocd app create guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --directory-recurse
   141  
   142    # Create a Jsonnet app
   143    argocd app create jsonnet-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path jsonnet-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --jsonnet-ext-str replicas=2
   144  
   145    # Create a Helm app
   146    argocd app create helm-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path helm-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --helm-set replicaCount=2
   147  
   148    # Create a Helm app from a Helm repo
   149    argocd app create nginx-ingress --repo https://charts.helm.sh/stable --helm-chart nginx-ingress --revision 1.24.3 --dest-namespace default --dest-server https://kubernetes.default.svc
   150  
   151    # Create a Kustomize app
   152    argocd app create kustomize-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path kustomize-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --kustomize-image quay.io/argoprojlabs/argocd-e2e-container:0.1
   153  
   154    # Create a MultiSource app while yaml file contains an application with multiple sources
   155    argocd app create guestbook --file <path-to-yaml-file>
   156  
   157    # Create a app using a custom tool:
   158    argocd app create kasane --repo https://github.com/argoproj/argocd-example-apps.git --path plugins/kasane --dest-namespace default --dest-server https://kubernetes.default.svc --config-management-plugin kasane`,
   159  		Run: func(c *cobra.Command, args []string) {
   160  			ctx := c.Context()
   161  
   162  			argocdClient := headless.NewClientOrDie(clientOpts, c)
   163  			apps, err := cmdutil.ConstructApps(fileURL, appName, labels, annotations, args, appOpts, c.Flags())
   164  			errors.CheckError(err)
   165  
   166  			for _, app := range apps {
   167  				if app.Name == "" {
   168  					c.HelpFunc()(c, args)
   169  					os.Exit(1)
   170  				}
   171  				if appNamespace != "" {
   172  					app.Namespace = appNamespace
   173  				}
   174  				if setFinalizer {
   175  					app.Finalizers = append(app.Finalizers, argoappv1.ResourcesFinalizerName)
   176  				}
   177  				conn, appIf := argocdClient.NewApplicationClientOrDie()
   178  				defer utilio.Close(conn)
   179  				appCreateRequest := application.ApplicationCreateRequest{
   180  					Application: app,
   181  					Upsert:      &upsert,
   182  					Validate:    &appOpts.Validate,
   183  				}
   184  
   185  				// Get app before creating to see if it is being updated or no change
   186  				existing, err := appIf.Get(ctx, &application.ApplicationQuery{Name: &app.Name})
   187  				unwrappedError := grpc.UnwrapGRPCStatus(err).Code()
   188  				// As part of the fix for CVE-2022-41354, the API will return Permission Denied when an app does not exist.
   189  				if unwrappedError != codes.NotFound && unwrappedError != codes.PermissionDenied {
   190  					errors.CheckError(err)
   191  				}
   192  
   193  				created, err := appIf.Create(ctx, &appCreateRequest)
   194  				errors.CheckError(err)
   195  
   196  				var action string
   197  				switch {
   198  				case existing == nil:
   199  					action = "created"
   200  				case !hasAppChanged(existing, created, upsert):
   201  					action = "unchanged"
   202  				default:
   203  					action = "updated"
   204  				}
   205  
   206  				fmt.Printf("application '%s' %s\n", created.Name, action)
   207  			}
   208  		},
   209  	}
   210  	command.Flags().StringVar(&appName, "name", "", "A name for the app, ignored if a file is set (DEPRECATED)")
   211  	command.Flags().BoolVar(&upsert, "upsert", false, "Allows to override application with the same name even if supplied application spec is different from existing spec")
   212  	command.Flags().StringVarP(&fileURL, "file", "f", "", "Filename or URL to Kubernetes manifests for the app")
   213  	command.Flags().StringArrayVarP(&labels, "label", "l", []string{}, "Labels to apply to the app")
   214  	command.Flags().StringArrayVarP(&annotations, "annotations", "", []string{}, "Set metadata annotations (e.g. example=value)")
   215  	command.Flags().BoolVar(&setFinalizer, "set-finalizer", false, "Sets deletion finalizer on the application, application resources will be cascaded on deletion")
   216  	// Only complete files with appropriate extension.
   217  	err := command.Flags().SetAnnotation("file", cobra.BashCompFilenameExt, []string{"json", "yaml", "yml"})
   218  	if err != nil {
   219  		log.Fatal(err)
   220  	}
   221  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace where the application will be created in")
   222  	cmdutil.AddAppFlags(command, &appOpts)
   223  	return command
   224  }
   225  
   226  // getInfos converts a list of string key=value pairs to a list of Info objects.
   227  func getInfos(infos []string) []*argoappv1.Info {
   228  	mapInfos, err := label.Parse(infos)
   229  	errors.CheckError(err)
   230  	sliceInfos := make([]*argoappv1.Info, len(mapInfos))
   231  	i := 0
   232  	for key, element := range mapInfos {
   233  		sliceInfos[i] = &argoappv1.Info{Name: key, Value: element}
   234  		i++
   235  	}
   236  	return sliceInfos
   237  }
   238  
   239  func getRefreshType(refresh bool, hardRefresh bool) *string {
   240  	if hardRefresh {
   241  		refreshType := string(argoappv1.RefreshTypeHard)
   242  		return &refreshType
   243  	}
   244  
   245  	if refresh {
   246  		refreshType := string(argoappv1.RefreshTypeNormal)
   247  		return &refreshType
   248  	}
   249  
   250  	return nil
   251  }
   252  
   253  func hasAppChanged(appReq, appRes *argoappv1.Application, upsert bool) bool {
   254  	// upsert==false, no change occurred from create command
   255  	if !upsert {
   256  		return false
   257  	}
   258  
   259  	// If no project, assume default project
   260  	if appReq.Spec.Project == "" {
   261  		appReq.Spec.Project = "default"
   262  	}
   263  	// Server will return nils for empty labels, annotations, finalizers
   264  	if len(appReq.Labels) == 0 {
   265  		appReq.Labels = nil
   266  	}
   267  	if len(appReq.Annotations) == 0 {
   268  		appReq.Annotations = nil
   269  	}
   270  	if len(appReq.Finalizers) == 0 {
   271  		appReq.Finalizers = nil
   272  	}
   273  
   274  	if reflect.DeepEqual(appRes.Spec, appReq.Spec) &&
   275  		reflect.DeepEqual(appRes.Labels, appReq.Labels) &&
   276  		reflect.DeepEqual(appRes.Annotations, appReq.Annotations) &&
   277  		reflect.DeepEqual(appRes.Finalizers, appReq.Finalizers) {
   278  		return false
   279  	}
   280  
   281  	return true
   282  }
   283  
   284  func parentChildDetails(ctx context.Context, appIf application.ApplicationServiceClient, appName string, appNs string) (map[string]argoappv1.ResourceNode, map[string][]string, map[string]struct{}) {
   285  	mapUIDToNode := make(map[string]argoappv1.ResourceNode)
   286  	mapParentToChild := make(map[string][]string)
   287  	parentNode := make(map[string]struct{})
   288  
   289  	resourceTree, err := appIf.ResourceTree(ctx, &application.ResourcesQuery{Name: &appName, AppNamespace: &appNs, ApplicationName: &appName})
   290  	errors.CheckError(err)
   291  
   292  	for _, node := range resourceTree.Nodes {
   293  		mapUIDToNode[node.UID] = node
   294  
   295  		if len(node.ParentRefs) > 0 {
   296  			_, ok := mapParentToChild[node.ParentRefs[0].UID]
   297  			if !ok {
   298  				var temp []string
   299  				mapParentToChild[node.ParentRefs[0].UID] = temp
   300  			}
   301  			mapParentToChild[node.ParentRefs[0].UID] = append(mapParentToChild[node.ParentRefs[0].UID], node.UID)
   302  		} else {
   303  			parentNode[node.UID] = struct{}{}
   304  		}
   305  	}
   306  	return mapUIDToNode, mapParentToChild, parentNode
   307  }
   308  
   309  func printHeader(ctx context.Context, acdClient argocdclient.Client, app *argoappv1.Application, windows *argoappv1.SyncWindows, showOperation bool, showParams bool, sourcePosition int) {
   310  	appURL := getAppURL(ctx, acdClient, app.Name)
   311  	printAppSummaryTable(app, appURL, windows)
   312  
   313  	if len(app.Status.Conditions) > 0 {
   314  		fmt.Println()
   315  		w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   316  		printAppConditions(w, app)
   317  		_ = w.Flush()
   318  		fmt.Println()
   319  	}
   320  	if showOperation && app.Status.OperationState != nil {
   321  		fmt.Println()
   322  		printOperationResult(app.Status.OperationState)
   323  	}
   324  	if showParams {
   325  		printParams(app, sourcePosition)
   326  	}
   327  }
   328  
   329  // getSourceNameToPositionMap returns a map of source name to position
   330  func getSourceNameToPositionMap(app *argoappv1.Application) map[string]int64 {
   331  	sourceNameToPosition := make(map[string]int64)
   332  	for i, s := range app.Spec.Sources {
   333  		if s.Name != "" {
   334  			sourceNameToPosition[s.Name] = int64(i + 1)
   335  		}
   336  	}
   337  	return sourceNameToPosition
   338  }
   339  
   340  // NewApplicationGetCommand returns a new instance of an `argocd app get` command
   341  func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   342  	var (
   343  		refresh        bool
   344  		hardRefresh    bool
   345  		output         string
   346  		timeout        uint
   347  		showParams     bool
   348  		showOperation  bool
   349  		appNamespace   string
   350  		sourcePosition int
   351  		sourceName     string
   352  	)
   353  	command := &cobra.Command{
   354  		Use:   "get APPNAME",
   355  		Short: "Get application details",
   356  		Example: templates.Examples(`
   357    # Get basic details about the application "my-app" in wide format
   358    argocd app get my-app -o wide
   359  
   360    # Get detailed information about the application "my-app" in YAML format
   361    argocd app get my-app -o yaml
   362  
   363    # Get details of the application "my-app" in JSON format
   364    argocd get my-app -o json
   365  
   366    # Get application details and include information about the current operation
   367    argocd app get my-app --show-operation
   368  
   369    # Show application parameters and overrides
   370    argocd app get my-app --show-params
   371  
   372    # Show application parameters and overrides for a source at position 1 under spec.sources of app my-app
   373    argocd app get my-app --show-params --source-position 1
   374  
   375    # Show application parameters and overrides for a source named "test"
   376    argocd app get my-app --show-params --source-name test
   377  
   378    # Refresh application data when retrieving
   379    argocd app get my-app --refresh
   380  
   381    # Perform a hard refresh, including refreshing application data and target manifests cache
   382    argocd app get my-app --hard-refresh
   383  
   384    # Get application details and display them in a tree format
   385    argocd app get my-app --output tree
   386  
   387    # Get application details and display them in a detailed tree format
   388    argocd app get my-app --output tree=detailed
   389    		`),
   390  
   391  		Run: func(c *cobra.Command, args []string) {
   392  			ctx, cancel := context.WithCancel(c.Context())
   393  			defer cancel()
   394  			if len(args) == 0 {
   395  				c.HelpFunc()(c, args)
   396  				os.Exit(1)
   397  			}
   398  			acdClient := headless.NewClientOrDie(clientOpts, c)
   399  			conn, appIf := acdClient.NewApplicationClientOrDie()
   400  			defer utilio.Close(conn)
   401  
   402  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
   403  
   404  			if timeout != 0 {
   405  				time.AfterFunc(time.Duration(timeout)*time.Second, func() {
   406  					if ctx.Err() != nil {
   407  						fmt.Println("Timeout function: context already cancelled:", ctx.Err())
   408  					} else {
   409  						fmt.Println("Timeout function: cancelling context manually")
   410  						cancel()
   411  					}
   412  				})
   413  			}
   414  			getAppStateWithRetry := func() (*argoappv1.Application, error) {
   415  				type getResponse struct {
   416  					app *argoappv1.Application
   417  					err error
   418  				}
   419  
   420  				ch := make(chan getResponse, 1)
   421  
   422  				go func() {
   423  					app, err := appIf.Get(ctx, &application.ApplicationQuery{
   424  						Name:         &appName,
   425  						Refresh:      getRefreshType(refresh, hardRefresh),
   426  						AppNamespace: &appNs,
   427  					})
   428  					ch <- getResponse{app: app, err: err}
   429  				}()
   430  
   431  				select {
   432  				case result := <-ch:
   433  					return result.app, result.err
   434  				case <-ctx.Done():
   435  					// Timeout occurred, try again without refresh flag
   436  					// Create new context for retry request
   437  					ctx := context.Background()
   438  					app, err := appIf.Get(ctx, &application.ApplicationQuery{
   439  						Name:         &appName,
   440  						AppNamespace: &appNs,
   441  					})
   442  					return app, err
   443  				}
   444  			}
   445  
   446  			app, err := getAppStateWithRetry()
   447  			errors.CheckError(err)
   448  
   449  			if ctx.Err() != nil {
   450  				ctx = context.Background() // Reset context for subsequent requests
   451  			}
   452  			if sourceName != "" && sourcePosition != -1 {
   453  				errors.Fatal(errors.ErrorGeneric, "Only one of source-position and source-name can be specified.")
   454  			}
   455  
   456  			if sourceName != "" {
   457  				sourceNameToPosition := getSourceNameToPositionMap(app)
   458  				pos, ok := sourceNameToPosition[sourceName]
   459  				if !ok {
   460  					log.Fatalf("Unknown source name '%s'", sourceName)
   461  				}
   462  				sourcePosition = int(pos)
   463  			}
   464  
   465  			// check for source position if --show-params is set
   466  			if app.Spec.HasMultipleSources() && showParams {
   467  				if sourcePosition <= 0 {
   468  					errors.Fatal(errors.ErrorGeneric, "Source position should be specified and must be greater than 0 for applications with multiple sources")
   469  				}
   470  				if len(app.Spec.GetSources()) < sourcePosition {
   471  					errors.Fatal(errors.ErrorGeneric, "Source position should be less than the number of sources in the application")
   472  				}
   473  			}
   474  
   475  			pConn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   476  			defer utilio.Close(pConn)
   477  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: app.Spec.Project})
   478  			errors.CheckError(err)
   479  
   480  			windows := proj.Spec.SyncWindows.Matches(app)
   481  
   482  			switch output {
   483  			case "yaml", "json":
   484  				err := PrintResource(app, output)
   485  				errors.CheckError(err)
   486  			case "wide", "":
   487  				printHeader(ctx, acdClient, app, windows, showOperation, showParams, sourcePosition)
   488  				if len(app.Status.Resources) > 0 {
   489  					fmt.Println()
   490  					w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   491  					printAppResources(w, app)
   492  					_ = w.Flush()
   493  				}
   494  			case "tree":
   495  				printHeader(ctx, acdClient, app, windows, showOperation, showParams, sourcePosition)
   496  				mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState := resourceParentChild(ctx, acdClient, appName, appNs)
   497  				if len(mapUIDToNode) > 0 {
   498  					fmt.Println()
   499  					printTreeView(mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState)
   500  				}
   501  			case "tree=detailed":
   502  				printHeader(ctx, acdClient, app, windows, showOperation, showParams, sourcePosition)
   503  				mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState := resourceParentChild(ctx, acdClient, appName, appNs)
   504  				if len(mapUIDToNode) > 0 {
   505  					fmt.Println()
   506  					printTreeViewDetailed(mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState)
   507  				}
   508  			default:
   509  				errors.CheckError(fmt.Errorf("unknown output format: %s", output))
   510  			}
   511  		},
   512  	}
   513  	command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree")
   514  	command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
   515  	command.Flags().BoolVar(&showOperation, "show-operation", false, "Show application operation")
   516  	command.Flags().BoolVar(&showParams, "show-params", false, "Show application parameters and overrides")
   517  	command.Flags().BoolVar(&refresh, "refresh", false, "Refresh application data when retrieving")
   518  	command.Flags().BoolVar(&hardRefresh, "hard-refresh", false, "Refresh application data as well as target manifests cache")
   519  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only get application from namespace")
   520  	command.Flags().IntVar(&sourcePosition, "source-position", -1, "Position of the source from the list of sources of the app. Counting starts at 1.")
   521  	command.Flags().StringVar(&sourceName, "source-name", "", "Name of the source from the list of sources of the app.")
   522  	return command
   523  }
   524  
   525  // NewApplicationLogsCommand returns logs of application pods
   526  func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   527  	var (
   528  		group        string
   529  		kind         string
   530  		namespace    string
   531  		resourceName string
   532  		follow       bool
   533  		tail         int64
   534  		sinceSeconds int64
   535  		untilTime    string
   536  		filter       string
   537  		container    string
   538  		previous     bool
   539  		matchCase    bool
   540  	)
   541  	command := &cobra.Command{
   542  		Use:   "logs APPNAME",
   543  		Short: "Get logs of application pods",
   544  		Example: templates.Examples(`
   545    # Get logs of pods associated with the application "my-app"
   546    argocd app logs my-app
   547  
   548    # Get logs of pods associated with the application "my-app" in a specific resource group
   549    argocd app logs my-app --group my-group
   550  
   551    # Get logs of pods associated with the application "my-app" in a specific resource kind
   552    argocd app logs my-app --kind my-kind
   553  
   554    # Get logs of pods associated with the application "my-app" in a specific namespace
   555    argocd app logs my-app --namespace my-namespace
   556  
   557    # Get logs of pods associated with the application "my-app" for a specific resource name
   558    argocd app logs my-app --name my-resource
   559  
   560    # Stream logs in real-time for the application "my-app"
   561    argocd app logs my-app -f
   562  
   563    # Get the last N lines of logs for the application "my-app"
   564    argocd app logs my-app --tail 100
   565  
   566    # Get logs since a specified number of seconds ago
   567    argocd app logs my-app --since-seconds 3600
   568  
   569    # Get logs until a specified time (format: "2023-10-10T15:30:00Z")
   570    argocd app logs my-app --until-time "2023-10-10T15:30:00Z"
   571  
   572    # Filter logs to show only those containing a specific string
   573    argocd app logs my-app --filter "error"
   574  
   575    # Filter logs to show only those containing a specific string and match case
   576    argocd app logs my-app --filter "error" --match-case
   577  
   578    # Get logs for a specific container within the pods
   579    argocd app logs my-app -c my-container
   580  
   581    # Get previously terminated container logs
   582    argocd app logs my-app -p
   583    		`),
   584  
   585  		Run: func(c *cobra.Command, args []string) {
   586  			ctx := c.Context()
   587  
   588  			if len(args) == 0 {
   589  				c.HelpFunc()(c, args)
   590  				os.Exit(1)
   591  			}
   592  			acdClient := headless.NewClientOrDie(clientOpts, c)
   593  			conn, appIf := acdClient.NewApplicationClientOrDie()
   594  			defer utilio.Close(conn)
   595  			appName, appNs := argo.ParseFromQualifiedName(args[0], "")
   596  
   597  			retry := true
   598  			for retry {
   599  				retry = false
   600  				stream, err := appIf.PodLogs(ctx, &application.ApplicationPodLogsQuery{
   601  					Name:         &appName,
   602  					Group:        &group,
   603  					Namespace:    ptr.To(namespace),
   604  					Kind:         &kind,
   605  					ResourceName: &resourceName,
   606  					Follow:       ptr.To(follow),
   607  					TailLines:    ptr.To(tail),
   608  					SinceSeconds: ptr.To(sinceSeconds),
   609  					UntilTime:    &untilTime,
   610  					Filter:       &filter,
   611  					MatchCase:    ptr.To(matchCase),
   612  					Container:    ptr.To(container),
   613  					Previous:     ptr.To(previous),
   614  					AppNamespace: &appNs,
   615  				})
   616  				if err != nil {
   617  					log.Fatalf("failed to get pod logs: %v", err)
   618  				}
   619  				for {
   620  					msg, err := stream.Recv()
   621  					if err != nil {
   622  						if stderrors.Is(err, io.EOF) {
   623  							return
   624  						}
   625  						st, ok := status.FromError(err)
   626  						if !ok {
   627  							log.Fatalf("stream read failed: %v", err)
   628  						}
   629  						if st.Code() == codes.Unavailable && follow {
   630  							retry = true
   631  							sinceSeconds = 1
   632  							break
   633  						}
   634  						log.Fatalf("stream read failed: %v", err)
   635  					}
   636  					if msg.GetLast() {
   637  						return
   638  					}
   639  					fmt.Println(msg.GetContent())
   640  				} // Done with receive message
   641  			} // Done with retry
   642  		},
   643  	}
   644  
   645  	command.Flags().StringVar(&group, "group", "", "Resource group")
   646  	command.Flags().StringVar(&kind, "kind", "", "Resource kind")
   647  	command.Flags().StringVar(&namespace, "namespace", "", "Resource namespace")
   648  	command.Flags().StringVar(&resourceName, "name", "", "Resource name")
   649  	command.Flags().BoolVarP(&follow, "follow", "f", false, "Specify if the logs should be streamed")
   650  	command.Flags().Int64Var(&tail, "tail", 0, "The number of lines from the end of the logs to show")
   651  	command.Flags().Int64Var(&sinceSeconds, "since-seconds", 0, "A relative time in seconds before the current time from which to show logs")
   652  	command.Flags().StringVar(&untilTime, "until-time", "", "Show logs until this time")
   653  	command.Flags().StringVar(&filter, "filter", "", "Show logs contain this string")
   654  	command.Flags().StringVarP(&container, "container", "c", "", "Optional container name")
   655  	command.Flags().BoolVarP(&previous, "previous", "p", false, "Specify if the previously terminated container logs should be returned")
   656  	command.Flags().BoolVarP(&matchCase, "match-case", "m", false, "Specify if the filter should be case-sensitive")
   657  
   658  	return command
   659  }
   660  
   661  func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *argoappv1.SyncWindows) {
   662  	fmt.Printf(printOpFmtStr, "Name:", app.QualifiedName())
   663  	fmt.Printf(printOpFmtStr, "Project:", app.Spec.GetProject())
   664  	fmt.Printf(printOpFmtStr, "Server:", getServer(app))
   665  	fmt.Printf(printOpFmtStr, "Namespace:", app.Spec.Destination.Namespace)
   666  	fmt.Printf(printOpFmtStr, "URL:", appURL)
   667  	if !app.Spec.HasMultipleSources() {
   668  		fmt.Println("Source:")
   669  	} else {
   670  		fmt.Println("Sources:")
   671  	}
   672  	for _, source := range app.Spec.GetSources() {
   673  		printAppSourceDetails(&source)
   674  	}
   675  	var wds []string
   676  	var status string
   677  	var allow, deny, inactiveAllows bool
   678  	if windows.HasWindows() {
   679  		active, err := windows.Active()
   680  		if err == nil && active.HasWindows() {
   681  			for _, w := range *active {
   682  				if w.Kind == "deny" {
   683  					deny = true
   684  				} else {
   685  					allow = true
   686  				}
   687  			}
   688  		}
   689  		inactiveAllowWindows, err := windows.InactiveAllows()
   690  		if err == nil && inactiveAllowWindows.HasWindows() {
   691  			inactiveAllows = true
   692  		}
   693  
   694  		if deny || !deny && !allow && inactiveAllows {
   695  			s, err := windows.CanSync(true)
   696  			if err == nil && s {
   697  				status = "Manual Allowed"
   698  			} else {
   699  				status = "Sync Denied"
   700  			}
   701  		} else {
   702  			status = "Sync Allowed"
   703  		}
   704  		for _, w := range *windows {
   705  			s := w.Kind + ":" + w.Schedule + ":" + w.Duration
   706  			wds = append(wds, s)
   707  		}
   708  	} else {
   709  		status = "Sync Allowed"
   710  	}
   711  	fmt.Printf(printOpFmtStr, "SyncWindow:", status)
   712  	if len(wds) > 0 {
   713  		fmt.Printf(printOpFmtStr, "Assigned Windows:", strings.Join(wds, ","))
   714  	}
   715  
   716  	var syncPolicy string
   717  	if app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.IsAutomatedSyncEnabled() {
   718  		syncPolicy = "Automated"
   719  		if app.Spec.SyncPolicy.Automated.Prune {
   720  			syncPolicy += " (Prune)"
   721  		}
   722  	} else {
   723  		syncPolicy = "Manual"
   724  	}
   725  	fmt.Printf(printOpFmtStr, "Sync Policy:", syncPolicy)
   726  	syncStatusStr := string(app.Status.Sync.Status)
   727  	switch app.Status.Sync.Status {
   728  	case argoappv1.SyncStatusCodeSynced:
   729  		syncStatusStr += " to " + app.Spec.GetSource().TargetRevision
   730  	case argoappv1.SyncStatusCodeOutOfSync:
   731  		syncStatusStr += " from " + app.Spec.GetSource().TargetRevision
   732  	}
   733  	if !git.IsCommitSHA(app.Spec.GetSource().TargetRevision) && !git.IsTruncatedCommitSHA(app.Spec.GetSource().TargetRevision) && len(app.Status.Sync.Revision) > 7 {
   734  		syncStatusStr += fmt.Sprintf(" (%s)", app.Status.Sync.Revision[0:7])
   735  	}
   736  	fmt.Printf(printOpFmtStr, "Sync Status:", syncStatusStr)
   737  	healthStr := string(app.Status.Health.Status)
   738  	fmt.Printf(printOpFmtStr, "Health Status:", healthStr)
   739  }
   740  
   741  func printAppSourceDetails(appSrc *argoappv1.ApplicationSource) {
   742  	fmt.Printf(printOpFmtStr, "- Repo:", appSrc.RepoURL)
   743  	fmt.Printf(printOpFmtStr, "  Target:", appSrc.TargetRevision)
   744  	if appSrc.Path != "" {
   745  		fmt.Printf(printOpFmtStr, "  Path:", appSrc.Path)
   746  	}
   747  	if appSrc.IsRef() {
   748  		fmt.Printf(printOpFmtStr, "  Ref:", appSrc.Ref)
   749  	}
   750  	if appSrc.Helm != nil && len(appSrc.Helm.ValueFiles) > 0 {
   751  		fmt.Printf(printOpFmtStr, "  Helm Values:", strings.Join(appSrc.Helm.ValueFiles, ","))
   752  	}
   753  	if appSrc.Kustomize != nil && appSrc.Kustomize.NamePrefix != "" {
   754  		fmt.Printf(printOpFmtStr, "  Name Prefix:", appSrc.Kustomize.NamePrefix)
   755  	}
   756  }
   757  
   758  func printAppConditions(w io.Writer, app *argoappv1.Application) {
   759  	_, _ = fmt.Fprintf(w, "CONDITION\tMESSAGE\tLAST TRANSITION\n")
   760  	for _, item := range app.Status.Conditions {
   761  		_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", item.Type, item.Message, item.LastTransitionTime)
   762  	}
   763  }
   764  
   765  // appURLDefault returns the default URL of an application
   766  func appURLDefault(acdClient argocdclient.Client, appName string) string {
   767  	var scheme string
   768  	opts := acdClient.ClientOptions()
   769  	server := opts.ServerAddr
   770  	if opts.PlainText {
   771  		scheme = "http"
   772  	} else {
   773  		scheme = "https"
   774  		if strings.HasSuffix(opts.ServerAddr, ":443") {
   775  			server = server[0 : len(server)-4]
   776  		}
   777  	}
   778  	return fmt.Sprintf("%s://%s/applications/%s", scheme, server, appName)
   779  }
   780  
   781  // getAppURL returns the URL of an application
   782  func getAppURL(ctx context.Context, acdClient argocdclient.Client, appName string) string {
   783  	conn, settingsIf := acdClient.NewSettingsClientOrDie()
   784  	defer utilio.Close(conn)
   785  	argoSettings, err := settingsIf.Get(ctx, &settings.SettingsQuery{})
   786  	errors.CheckError(err)
   787  
   788  	if argoSettings.URL != "" {
   789  		return fmt.Sprintf("%s/applications/%s", argoSettings.URL, appName)
   790  	}
   791  	return appURLDefault(acdClient, appName)
   792  }
   793  
   794  func truncateString(str string, num int) string {
   795  	bnoden := str
   796  	if utf8.RuneCountInString(str) > num {
   797  		if num > 3 {
   798  			num -= 3
   799  		}
   800  		bnoden = string([]rune(str)[0:num]) + "..."
   801  	}
   802  	return bnoden
   803  }
   804  
   805  // printParams prints parameters and overrides
   806  func printParams(app *argoappv1.Application, sourcePosition int) {
   807  	var source *argoappv1.ApplicationSource
   808  
   809  	if app.Spec.HasMultipleSources() {
   810  		// Get the source by the sourcePosition whose params you'd like to print
   811  		source = app.Spec.GetSourcePtrByPosition(sourcePosition)
   812  		if source == nil {
   813  			source = &argoappv1.ApplicationSource{}
   814  		}
   815  	} else {
   816  		src := app.Spec.GetSource()
   817  		source = &src
   818  	}
   819  
   820  	if source.Helm != nil {
   821  		printHelmParams(source.Helm)
   822  	}
   823  }
   824  
   825  func printHelmParams(helm *argoappv1.ApplicationSourceHelm) {
   826  	paramLenLimit := 80
   827  	fmt.Println()
   828  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   829  	if helm != nil {
   830  		fmt.Println()
   831  		_, _ = fmt.Fprintf(w, "NAME\tVALUE\n")
   832  		for _, p := range helm.Parameters {
   833  			_, _ = fmt.Fprintf(w, "%s\t%s\n", p.Name, truncateString(p.Value, paramLenLimit))
   834  		}
   835  	}
   836  	_ = w.Flush()
   837  }
   838  
   839  func getServer(app *argoappv1.Application) string {
   840  	if app.Spec.Destination.Server == "" {
   841  		return app.Spec.Destination.Name
   842  	}
   843  
   844  	return app.Spec.Destination.Server
   845  }
   846  
   847  // NewApplicationSetCommand returns a new instance of an `argocd app set` command
   848  func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   849  	var (
   850  		appOpts        cmdutil.AppOptions
   851  		appNamespace   string
   852  		sourcePosition int
   853  		sourceName     string
   854  	)
   855  	command := &cobra.Command{
   856  		Use:   "set APPNAME",
   857  		Short: "Set application parameters",
   858  		Example: templates.Examples(`
   859    # Set application parameters for the application "my-app"
   860    argocd app set my-app --parameter key1=value1 --parameter key2=value2
   861  
   862    # Set and validate application parameters for "my-app"
   863    argocd app set my-app --parameter key1=value1 --parameter key2=value2 --validate
   864  
   865    # Set and override application parameters for a source at position 1 under spec.sources of app my-app. source-position starts at 1.
   866    argocd app set my-app --source-position 1 --repo https://github.com/argoproj/argocd-example-apps.git
   867  
   868    # Set and override application parameters for a source named "test" under spec.sources of app my-app.
   869    argocd app set my-app --source-name test --repo https://github.com/argoproj/argocd-example-apps.git
   870  
   871    # Set application parameters and specify the namespace
   872    argocd app set my-app --parameter key1=value1 --parameter key2=value2 --namespace my-namespace
   873    		`),
   874  
   875  		Run: func(c *cobra.Command, args []string) {
   876  			ctx := c.Context()
   877  
   878  			if len(args) != 1 {
   879  				c.HelpFunc()(c, args)
   880  				os.Exit(1)
   881  			}
   882  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
   883  			argocdClient := headless.NewClientOrDie(clientOpts, c)
   884  			conn, appIf := argocdClient.NewApplicationClientOrDie()
   885  			defer utilio.Close(conn)
   886  			app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: &appName, AppNamespace: &appNs})
   887  			errors.CheckError(err)
   888  
   889  			sourceName = appOpts.SourceName
   890  			if sourceName != "" && sourcePosition != -1 {
   891  				errors.Fatal(errors.ErrorGeneric, "Only one of source-position and source-name can be specified.")
   892  			}
   893  
   894  			if sourceName != "" {
   895  				sourceNameToPosition := getSourceNameToPositionMap(app)
   896  				pos, ok := sourceNameToPosition[sourceName]
   897  				if !ok {
   898  					log.Fatalf("Unknown source name '%s'", sourceName)
   899  				}
   900  				sourcePosition = int(pos)
   901  			}
   902  
   903  			if app.Spec.HasMultipleSources() {
   904  				if sourcePosition <= 0 {
   905  					errors.Fatal(errors.ErrorGeneric, "Source position should be specified and must be greater than 0 for applications with multiple sources")
   906  				}
   907  				if len(app.Spec.GetSources()) < sourcePosition {
   908  					errors.Fatal(errors.ErrorGeneric, "Source position should be less than the number of sources in the application")
   909  				}
   910  			}
   911  
   912  			visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, sourcePosition)
   913  			if visited == 0 {
   914  				log.Error("Please set at least one option to update")
   915  				c.HelpFunc()(c, args)
   916  				os.Exit(1)
   917  			}
   918  
   919  			setParameterOverrides(app, appOpts.Parameters, sourcePosition)
   920  			_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
   921  				Name:         &app.Name,
   922  				Spec:         &app.Spec,
   923  				Validate:     &appOpts.Validate,
   924  				AppNamespace: &appNs,
   925  			})
   926  			errors.CheckError(err)
   927  		},
   928  	}
   929  	cmdutil.AddAppFlags(command, &appOpts)
   930  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Set application parameters in namespace")
   931  	command.Flags().IntVar(&sourcePosition, "source-position", -1, "Position of the source from the list of sources of the app. Counting starts at 1.")
   932  	return command
   933  }
   934  
   935  // unsetOpts describe what to unset in an Application.
   936  type unsetOpts struct {
   937  	namePrefix              bool
   938  	nameSuffix              bool
   939  	kustomizeVersion        bool
   940  	kustomizeNamespace      bool
   941  	kustomizeImages         []string
   942  	kustomizeReplicas       []string
   943  	ignoreMissingComponents bool
   944  	parameters              []string
   945  	valuesFiles             []string
   946  	valuesLiteral           bool
   947  	ignoreMissingValueFiles bool
   948  	pluginEnvs              []string
   949  	passCredentials         bool
   950  	ref                     bool
   951  }
   952  
   953  // IsZero returns true when the Application options for kustomize are considered empty
   954  func (o *unsetOpts) KustomizeIsZero() bool {
   955  	return o == nil ||
   956  		!o.namePrefix &&
   957  			!o.nameSuffix &&
   958  			!o.kustomizeVersion &&
   959  			!o.kustomizeNamespace &&
   960  			!o.ignoreMissingComponents &&
   961  			len(o.kustomizeImages) == 0 &&
   962  			len(o.kustomizeReplicas) == 0
   963  }
   964  
   965  // NewApplicationUnsetCommand returns a new instance of an `argocd app unset` command
   966  func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   967  	var sourcePosition int
   968  	var sourceName string
   969  	appOpts := cmdutil.AppOptions{}
   970  	opts := unsetOpts{}
   971  	var appNamespace string
   972  	command := &cobra.Command{
   973  		Use:   "unset APPNAME parameters",
   974  		Short: "Unset application parameters",
   975  		Example: `  # Unset kustomize override kustomize image
   976    argocd app unset my-app --kustomize-image=alpine
   977  
   978    # Unset kustomize override suffix
   979    argocd app unset my-app --namesuffix
   980  
   981    # Unset kustomize override suffix for source at position 1 under spec.sources of app my-app. source-position starts at 1.
   982    argocd app unset my-app --source-position 1 --namesuffix
   983  
   984    # Unset kustomize override suffix for source named "test" under spec.sources of app my-app.
   985    argocd app unset my-app --source-name test --namesuffix
   986  
   987    # Unset parameter override
   988    argocd app unset my-app -p COMPONENT=PARAM`,
   989  
   990  		Run: func(c *cobra.Command, args []string) {
   991  			ctx := c.Context()
   992  
   993  			if len(args) != 1 {
   994  				c.HelpFunc()(c, args)
   995  				os.Exit(1)
   996  			}
   997  
   998  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
   999  			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
  1000  			defer utilio.Close(conn)
  1001  			app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: &appName, AppNamespace: &appNs})
  1002  			errors.CheckError(err)
  1003  
  1004  			sourceName = appOpts.SourceName
  1005  			if sourceName != "" && sourcePosition != -1 {
  1006  				errors.Fatal(errors.ErrorGeneric, "Only one of source-position and source-name can be specified.")
  1007  			}
  1008  
  1009  			if sourceName != "" {
  1010  				sourceNameToPosition := getSourceNameToPositionMap(app)
  1011  				pos, ok := sourceNameToPosition[sourceName]
  1012  				if !ok {
  1013  					log.Fatalf("Unknown source name '%s'", sourceName)
  1014  				}
  1015  				sourcePosition = int(pos)
  1016  			}
  1017  
  1018  			if app.Spec.HasMultipleSources() {
  1019  				if sourcePosition <= 0 {
  1020  					errors.Fatal(errors.ErrorGeneric, "Source position should be specified and must be greater than 0 for applications with multiple sources")
  1021  				}
  1022  				if len(app.Spec.GetSources()) < sourcePosition {
  1023  					errors.Fatal(errors.ErrorGeneric, "Source position should be less than the number of sources in the application")
  1024  				}
  1025  			}
  1026  
  1027  			source := app.Spec.GetSourcePtrByPosition(sourcePosition)
  1028  
  1029  			updated, nothingToUnset := unset(source, opts)
  1030  			if nothingToUnset {
  1031  				c.HelpFunc()(c, args)
  1032  				os.Exit(1)
  1033  			}
  1034  			if !updated {
  1035  				return
  1036  			}
  1037  
  1038  			cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, sourcePosition)
  1039  
  1040  			promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
  1041  			canUnset := promptUtil.Confirm("Are you sure you want to unset the parameters? [y/n]")
  1042  			if canUnset {
  1043  				_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
  1044  					Name:         &app.Name,
  1045  					Spec:         &app.Spec,
  1046  					Validate:     &appOpts.Validate,
  1047  					AppNamespace: &appNs,
  1048  				})
  1049  				errors.CheckError(err)
  1050  			} else {
  1051  				fmt.Println("The command to unset the parameters has been cancelled.")
  1052  			}
  1053  		},
  1054  	}
  1055  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Unset application parameters in namespace")
  1056  	command.Flags().StringArrayVarP(&opts.parameters, "parameter", "p", []string{}, "Unset a parameter override (e.g. -p guestbook=image)")
  1057  	command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Unset one or more Helm values files")
  1058  	command.Flags().BoolVar(&opts.valuesLiteral, "values-literal", false, "Unset literal Helm values block")
  1059  	command.Flags().BoolVar(&opts.ignoreMissingValueFiles, "ignore-missing-value-files", false, "Unset the helm ignore-missing-value-files option (revert to false)")
  1060  	command.Flags().BoolVar(&opts.nameSuffix, "namesuffix", false, "Kustomize namesuffix")
  1061  	command.Flags().BoolVar(&opts.namePrefix, "nameprefix", false, "Kustomize nameprefix")
  1062  	command.Flags().BoolVar(&opts.kustomizeVersion, "kustomize-version", false, "Kustomize version")
  1063  	command.Flags().BoolVar(&opts.kustomizeNamespace, "kustomize-namespace", false, "Kustomize namespace")
  1064  	command.Flags().StringArrayVar(&opts.kustomizeImages, "kustomize-image", []string{}, "Kustomize images name (e.g. --kustomize-image node --kustomize-image mysql)")
  1065  	command.Flags().StringArrayVar(&opts.kustomizeReplicas, "kustomize-replica", []string{}, "Kustomize replicas name (e.g. --kustomize-replica my-deployment --kustomize-replica my-statefulset)")
  1066  	command.Flags().BoolVar(&opts.ignoreMissingComponents, "ignore-missing-components", false, "Unset the kustomize ignore-missing-components option (revert to false)")
  1067  	command.Flags().StringArrayVar(&opts.pluginEnvs, "plugin-env", []string{}, "Unset plugin env variables (e.g --plugin-env name)")
  1068  	command.Flags().BoolVar(&opts.passCredentials, "pass-credentials", false, "Unset passCredentials")
  1069  	command.Flags().BoolVar(&opts.ref, "ref", false, "Unset ref on the source")
  1070  	command.Flags().IntVar(&sourcePosition, "source-position", -1, "Position of the source from the list of sources of the app. Counting starts at 1.")
  1071  	return command
  1072  }
  1073  
  1074  func unset(source *argoappv1.ApplicationSource, opts unsetOpts) (updated bool, nothingToUnset bool) {
  1075  	needToUnsetRef := false
  1076  	if opts.ref && source.IsRef() {
  1077  		source.Ref = ""
  1078  		updated = true
  1079  		needToUnsetRef = true
  1080  	}
  1081  
  1082  	if source.Kustomize != nil {
  1083  		if opts.KustomizeIsZero() {
  1084  			return updated, !needToUnsetRef
  1085  		}
  1086  
  1087  		if opts.namePrefix && source.Kustomize.NamePrefix != "" {
  1088  			updated = true
  1089  			source.Kustomize.NamePrefix = ""
  1090  		}
  1091  
  1092  		if opts.nameSuffix && source.Kustomize.NameSuffix != "" {
  1093  			updated = true
  1094  			source.Kustomize.NameSuffix = ""
  1095  		}
  1096  
  1097  		if opts.kustomizeVersion && source.Kustomize.Version != "" {
  1098  			updated = true
  1099  			source.Kustomize.Version = ""
  1100  		}
  1101  
  1102  		if opts.kustomizeNamespace && source.Kustomize.Namespace != "" {
  1103  			updated = true
  1104  			source.Kustomize.Namespace = ""
  1105  		}
  1106  
  1107  		if opts.ignoreMissingComponents && source.Kustomize.IgnoreMissingComponents {
  1108  			source.Kustomize.IgnoreMissingComponents = false
  1109  			updated = true
  1110  		}
  1111  
  1112  		for _, kustomizeImage := range opts.kustomizeImages {
  1113  			for i, item := range source.Kustomize.Images {
  1114  				if !argoappv1.KustomizeImage(kustomizeImage).Match(item) {
  1115  					continue
  1116  				}
  1117  				updated = true
  1118  				// remove i
  1119  				a := source.Kustomize.Images
  1120  				copy(a[i:], a[i+1:]) // Shift a[i+1:] left one index.
  1121  				a[len(a)-1] = ""     // Erase last element (write zero value).
  1122  				a = a[:len(a)-1]     // Truncate slice.
  1123  				source.Kustomize.Images = a
  1124  			}
  1125  		}
  1126  
  1127  		for _, kustomizeReplica := range opts.kustomizeReplicas {
  1128  			kustomizeReplicas := source.Kustomize.Replicas
  1129  			for i, item := range kustomizeReplicas {
  1130  				if kustomizeReplica == item.Name {
  1131  					source.Kustomize.Replicas = append(kustomizeReplicas[0:i], kustomizeReplicas[i+1:]...)
  1132  					updated = true
  1133  					break
  1134  				}
  1135  			}
  1136  		}
  1137  	}
  1138  	if source.Helm != nil {
  1139  		if len(opts.parameters) == 0 && len(opts.valuesFiles) == 0 && !opts.valuesLiteral && !opts.ignoreMissingValueFiles && !opts.passCredentials {
  1140  			return updated, !needToUnsetRef
  1141  		}
  1142  		for _, paramStr := range opts.parameters {
  1143  			helmParams := source.Helm.Parameters
  1144  			for i, p := range helmParams {
  1145  				if p.Name == paramStr {
  1146  					source.Helm.Parameters = append(helmParams[0:i], helmParams[i+1:]...)
  1147  					updated = true
  1148  					break
  1149  				}
  1150  			}
  1151  		}
  1152  		if opts.valuesLiteral && !source.Helm.ValuesIsEmpty() {
  1153  			err := source.Helm.SetValuesString("")
  1154  			if err == nil {
  1155  				updated = true
  1156  			}
  1157  		}
  1158  		for _, valuesFile := range opts.valuesFiles {
  1159  			specValueFiles := source.Helm.ValueFiles
  1160  			for i, vf := range specValueFiles {
  1161  				if vf == valuesFile {
  1162  					source.Helm.ValueFiles = append(specValueFiles[0:i], specValueFiles[i+1:]...)
  1163  					updated = true
  1164  					break
  1165  				}
  1166  			}
  1167  		}
  1168  		if opts.ignoreMissingValueFiles && source.Helm.IgnoreMissingValueFiles {
  1169  			source.Helm.IgnoreMissingValueFiles = false
  1170  			updated = true
  1171  		}
  1172  		if opts.passCredentials && source.Helm.PassCredentials {
  1173  			source.Helm.PassCredentials = false
  1174  			updated = true
  1175  		}
  1176  	}
  1177  
  1178  	if source.Plugin != nil {
  1179  		if len(opts.pluginEnvs) == 0 {
  1180  			return false, !needToUnsetRef
  1181  		}
  1182  		for _, env := range opts.pluginEnvs {
  1183  			err := source.Plugin.RemoveEnvEntry(env)
  1184  			if err == nil {
  1185  				updated = true
  1186  			}
  1187  		}
  1188  	}
  1189  	return updated, false
  1190  }
  1191  
  1192  // targetObjects deserializes the list of target states into unstructured objects
  1193  func targetObjects(resources []*argoappv1.ResourceDiff) ([]*unstructured.Unstructured, error) {
  1194  	objs := make([]*unstructured.Unstructured, len(resources))
  1195  	for i, resState := range resources {
  1196  		obj, err := resState.TargetObject()
  1197  		if err != nil {
  1198  			return nil, err
  1199  		}
  1200  		objs[i] = obj
  1201  	}
  1202  	return objs, nil
  1203  }
  1204  
  1205  func getLocalObjects(ctx context.Context, app *argoappv1.Application, proj *argoappv1.AppProject, local, localRepoRoot, appLabelKey, kubeVersion string, apiVersions []string, kustomizeOptions *argoappv1.KustomizeOptions,
  1206  	trackingMethod string,
  1207  ) []*unstructured.Unstructured {
  1208  	manifestStrings := getLocalObjectsString(ctx, app, proj, local, localRepoRoot, appLabelKey, kubeVersion, apiVersions, kustomizeOptions, trackingMethod)
  1209  	objs := make([]*unstructured.Unstructured, len(manifestStrings))
  1210  	for i := range manifestStrings {
  1211  		obj := unstructured.Unstructured{}
  1212  		err := json.Unmarshal([]byte(manifestStrings[i]), &obj)
  1213  		errors.CheckError(err)
  1214  		objs[i] = &obj
  1215  	}
  1216  	return objs
  1217  }
  1218  
  1219  func getLocalObjectsString(ctx context.Context, app *argoappv1.Application, proj *argoappv1.AppProject, local, localRepoRoot, appLabelKey, kubeVersion string, apiVersions []string, kustomizeOptions *argoappv1.KustomizeOptions,
  1220  	trackingMethod string,
  1221  ) []string {
  1222  	source := app.Spec.GetSource()
  1223  	res, err := repository.GenerateManifests(ctx, local, localRepoRoot, source.TargetRevision, &repoapiclient.ManifestRequest{
  1224  		Repo:                            &argoappv1.Repository{Repo: source.RepoURL},
  1225  		AppLabelKey:                     appLabelKey,
  1226  		AppName:                         app.Name,
  1227  		Namespace:                       app.Spec.Destination.Namespace,
  1228  		ApplicationSource:               &source,
  1229  		KustomizeOptions:                kustomizeOptions,
  1230  		KubeVersion:                     kubeVersion,
  1231  		ApiVersions:                     apiVersions,
  1232  		TrackingMethod:                  trackingMethod,
  1233  		ProjectName:                     proj.Name,
  1234  		ProjectSourceRepos:              proj.Spec.SourceRepos,
  1235  		AnnotationManifestGeneratePaths: app.GetAnnotation(argoappv1.AnnotationKeyManifestGeneratePaths),
  1236  	}, true, &git.NoopCredsStore{}, resource.MustParse("0"), nil)
  1237  	errors.CheckError(err)
  1238  
  1239  	return res.Manifests
  1240  }
  1241  
  1242  type resourceInfoProvider struct {
  1243  	namespacedByGk map[schema.GroupKind]bool
  1244  }
  1245  
  1246  // Infer if obj is namespaced or not from corresponding live objects list. If corresponding live object has namespace then target object is also namespaced.
  1247  // If live object is missing then it does not matter if target is namespaced or not.
  1248  func (p *resourceInfoProvider) IsNamespaced(gk schema.GroupKind) (bool, error) {
  1249  	return p.namespacedByGk[gk], nil
  1250  }
  1251  
  1252  func groupObjsByKey(localObs []*unstructured.Unstructured, liveObjs []*unstructured.Unstructured, appNamespace string) map[kube.ResourceKey]*unstructured.Unstructured {
  1253  	namespacedByGk := make(map[schema.GroupKind]bool)
  1254  	for i := range liveObjs {
  1255  		if liveObjs[i] != nil {
  1256  			key := kube.GetResourceKey(liveObjs[i])
  1257  			namespacedByGk[schema.GroupKind{Group: key.Group, Kind: key.Kind}] = key.Namespace != ""
  1258  		}
  1259  	}
  1260  	localObs, _, err := controller.DeduplicateTargetObjects(appNamespace, localObs, &resourceInfoProvider{namespacedByGk: namespacedByGk})
  1261  	errors.CheckError(err)
  1262  	objByKey := make(map[kube.ResourceKey]*unstructured.Unstructured)
  1263  	for i := range localObs {
  1264  		obj := localObs[i]
  1265  		if !hook.IsHook(obj) && !ignore.Ignore(obj) {
  1266  			objByKey[kube.GetResourceKey(obj)] = obj
  1267  		}
  1268  	}
  1269  	return objByKey
  1270  }
  1271  
  1272  type objKeyLiveTarget struct {
  1273  	key    kube.ResourceKey
  1274  	live   *unstructured.Unstructured
  1275  	target *unstructured.Unstructured
  1276  }
  1277  
  1278  // NewApplicationDiffCommand returns a new instance of an `argocd app diff` command
  1279  func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  1280  	var (
  1281  		refresh              bool
  1282  		hardRefresh          bool
  1283  		exitCode             bool
  1284  		diffExitCode         int
  1285  		local                string
  1286  		revision             string
  1287  		localRepoRoot        string
  1288  		serverSideGenerate   bool
  1289  		serverSideDiff       bool
  1290  		localIncludes        []string
  1291  		appNamespace         string
  1292  		revisions            []string
  1293  		sourcePositions      []int64
  1294  		sourceNames          []string
  1295  		ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
  1296  	)
  1297  	shortDesc := "Perform a diff against the target and live state."
  1298  	command := &cobra.Command{
  1299  		Use:   "diff APPNAME",
  1300  		Short: shortDesc,
  1301  		Long:  shortDesc + "\nUses 'diff' to render the difference. KUBECTL_EXTERNAL_DIFF environment variable can be used to select your own diff tool.\nReturns the following exit codes: 2 on general errors, 1 when a diff is found, and 0 when no diff is found\nKubernetes Secrets are ignored from this diff.",
  1302  		Run: func(c *cobra.Command, args []string) {
  1303  			ctx := c.Context()
  1304  
  1305  			if len(args) != 1 {
  1306  				c.HelpFunc()(c, args)
  1307  				os.Exit(2)
  1308  			}
  1309  
  1310  			if len(sourceNames) > 0 && len(sourcePositions) > 0 {
  1311  				errors.Fatal(errors.ErrorGeneric, "Only one of source-positions and source-names can be specified.")
  1312  			}
  1313  
  1314  			if len(sourcePositions) > 0 && len(revisions) != len(sourcePositions) {
  1315  				errors.Fatal(errors.ErrorGeneric, "While using --revisions and --source-positions, length of values for both flags should be same.")
  1316  			}
  1317  
  1318  			if len(sourceNames) > 0 && len(revisions) != len(sourceNames) {
  1319  				errors.Fatal(errors.ErrorGeneric, "While using --revisions and --source-names, length of values for both flags should be same.")
  1320  			}
  1321  
  1322  			clientset := headless.NewClientOrDie(clientOpts, c)
  1323  			conn, appIf := clientset.NewApplicationClientOrDie()
  1324  			defer utilio.Close(conn)
  1325  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
  1326  			app, err := appIf.Get(ctx, &application.ApplicationQuery{
  1327  				Name:         &appName,
  1328  				Refresh:      getRefreshType(refresh, hardRefresh),
  1329  				AppNamespace: &appNs,
  1330  			})
  1331  			errors.CheckError(err)
  1332  
  1333  			if len(sourceNames) > 0 {
  1334  				sourceNameToPosition := getSourceNameToPositionMap(app)
  1335  
  1336  				for _, name := range sourceNames {
  1337  					pos, ok := sourceNameToPosition[name]
  1338  					if !ok {
  1339  						log.Fatalf("Unknown source name '%s'", name)
  1340  					}
  1341  					sourcePositions = append(sourcePositions, pos)
  1342  				}
  1343  			}
  1344  
  1345  			resources, err := appIf.ManagedResources(ctx, &application.ResourcesQuery{ApplicationName: &appName, AppNamespace: &appNs})
  1346  			errors.CheckError(err)
  1347  			conn, settingsIf := clientset.NewSettingsClientOrDie()
  1348  			defer utilio.Close(conn)
  1349  			argoSettings, err := settingsIf.Get(ctx, &settings.SettingsQuery{})
  1350  			errors.CheckError(err)
  1351  			diffOption := &DifferenceOption{}
  1352  
  1353  			hasServerSideDiffAnnotation := resourceutil.HasAnnotationOption(app, argocommon.AnnotationCompareOptions, "ServerSideDiff=true")
  1354  
  1355  			// Use annotation if flag not explicitly set
  1356  			if !c.Flags().Changed("server-side-diff") {
  1357  				serverSideDiff = hasServerSideDiffAnnotation
  1358  			} else if serverSideDiff && !hasServerSideDiffAnnotation {
  1359  				// Flag explicitly set to true, but app annotation is not set
  1360  				fmt.Fprintf(os.Stderr, "Warning: Application does not have ServerSideDiff=true annotation.\n")
  1361  			}
  1362  
  1363  			// Server side diff with local requires server side generate to be set as there will be a mismatch with client-generated manifests.
  1364  			if serverSideDiff && local != "" && !serverSideGenerate {
  1365  				log.Fatal("--server-side-diff with --local requires --server-side-generate.")
  1366  			}
  1367  
  1368  			switch {
  1369  			case app.Spec.HasMultipleSources() && len(revisions) > 0 && len(sourcePositions) > 0:
  1370  				numOfSources := int64(len(app.Spec.GetSources()))
  1371  				for _, pos := range sourcePositions {
  1372  					if pos <= 0 || pos > numOfSources {
  1373  						log.Fatal("source-position cannot be less than 1 or more than number of sources in the app. Counting starts at 1.")
  1374  					}
  1375  				}
  1376  
  1377  				q := application.ApplicationManifestQuery{
  1378  					Name:            &appName,
  1379  					AppNamespace:    &appNs,
  1380  					Revisions:       revisions,
  1381  					SourcePositions: sourcePositions,
  1382  					NoCache:         &hardRefresh,
  1383  				}
  1384  				res, err := appIf.GetManifests(ctx, &q)
  1385  				errors.CheckError(err)
  1386  
  1387  				diffOption.res = res
  1388  				diffOption.revisions = revisions
  1389  			case revision != "":
  1390  				q := application.ApplicationManifestQuery{
  1391  					Name:         &appName,
  1392  					Revision:     &revision,
  1393  					AppNamespace: &appNs,
  1394  					NoCache:      &hardRefresh,
  1395  				}
  1396  				res, err := appIf.GetManifests(ctx, &q)
  1397  				errors.CheckError(err)
  1398  				diffOption.res = res
  1399  				diffOption.revision = revision
  1400  			case local != "":
  1401  				if serverSideGenerate {
  1402  					client, err := appIf.GetManifestsWithFiles(ctx, grpc_retry.Disable())
  1403  					errors.CheckError(err)
  1404  
  1405  					err = manifeststream.SendApplicationManifestQueryWithFiles(ctx, client, appName, appNs, local, localIncludes)
  1406  					errors.CheckError(err)
  1407  
  1408  					res, err := client.CloseAndRecv()
  1409  					errors.CheckError(err)
  1410  
  1411  					diffOption.serversideRes = res
  1412  				} else {
  1413  					fmt.Fprintf(os.Stderr, "Warning: local diff without --server-side-generate is deprecated and does not work with plugins. Server-side generation will be the default in v2.7.")
  1414  					conn, clusterIf := clientset.NewClusterClientOrDie()
  1415  					defer utilio.Close(conn)
  1416  					cluster, err := clusterIf.Get(ctx, &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
  1417  					errors.CheckError(err)
  1418  
  1419  					diffOption.local = local
  1420  					diffOption.localRepoRoot = localRepoRoot
  1421  					diffOption.cluster = cluster
  1422  				}
  1423  			}
  1424  			proj := getProject(ctx, c, clientOpts, app.Spec.Project)
  1425  
  1426  			foundDiffs := findAndPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption, ignoreNormalizerOpts, serverSideDiff, appIf, app.GetName(), app.GetNamespace())
  1427  			if foundDiffs && exitCode {
  1428  				os.Exit(diffExitCode)
  1429  			}
  1430  		},
  1431  	}
  1432  	command.Flags().BoolVar(&refresh, "refresh", false, "Refresh application data when retrieving")
  1433  	command.Flags().BoolVar(&hardRefresh, "hard-refresh", false, "Refresh application data as well as target manifests cache")
  1434  	command.Flags().BoolVar(&exitCode, "exit-code", true, "Return non-zero exit code when there is a diff. May also return non-zero exit code if there is an error.")
  1435  	command.Flags().IntVar(&diffExitCode, "diff-exit-code", 1, "Return specified exit code when there is a diff. Typical error code is 20 but use another exit code if you want to differentiate from the generic exit code (20) returned by all CLI commands.")
  1436  	command.Flags().StringVar(&local, "local", "", "Compare live app to a local manifests")
  1437  	command.Flags().StringVar(&revision, "revision", "", "Compare live app to a particular revision")
  1438  	command.Flags().StringVar(&localRepoRoot, "local-repo-root", "/", "Path to the repository root. Used together with --local allows setting the repository root")
  1439  	command.Flags().BoolVar(&serverSideGenerate, "server-side-generate", false, "Used with --local, this will send your manifests to the server for diffing")
  1440  	command.Flags().BoolVar(&serverSideDiff, "server-side-diff", false, "Use server-side diff to calculate the diff. This will default to true if the ServerSideDiff annotation is set on the application.")
  1441  	command.Flags().StringArrayVar(&localIncludes, "local-include", []string{"*.yaml", "*.yml", "*.json"}, "Used with --server-side-generate, specify patterns of filenames to send. Matching is based on filename and not path.")
  1442  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only render the difference in namespace")
  1443  	command.Flags().StringArrayVar(&revisions, "revisions", []string{}, "Show manifests at specific revisions for source position in source-positions")
  1444  	command.Flags().Int64SliceVar(&sourcePositions, "source-positions", []int64{}, "List of source positions. Default is empty array. Counting start at 1.")
  1445  	command.Flags().StringArrayVar(&sourceNames, "source-names", []string{}, "List of source names. Default is an empty array.")
  1446  	command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
  1447  	return command
  1448  }
  1449  
  1450  // printResourceDiff prints the diff header and calls cli.PrintDiff for a resource
  1451  func printResourceDiff(group, kind, namespace, name string, live, target *unstructured.Unstructured) {
  1452  	fmt.Printf("\n===== %s/%s %s/%s ======\n", group, kind, namespace, name)
  1453  	_ = cli.PrintDiff(name, live, target)
  1454  }
  1455  
  1456  // findAndPrintServerSideDiff performs a server-side diff by making requests to the api server and prints the response
  1457  func findAndPrintServerSideDiff(ctx context.Context, app *argoappv1.Application, items []objKeyLiveTarget, resources *application.ManagedResourcesResponse, appIf application.ApplicationServiceClient, appName, appNs string) bool {
  1458  	// Process each item for server-side diff
  1459  	foundDiffs := false
  1460  	for _, item := range items {
  1461  		if item.target != nil && hook.IsHook(item.target) || item.live != nil && hook.IsHook(item.live) {
  1462  			continue
  1463  		}
  1464  
  1465  		// For server-side diff, we need to create aligned arrays for this specific resource
  1466  		var liveResource *argoappv1.ResourceDiff
  1467  		var targetManifest string
  1468  
  1469  		if item.live != nil {
  1470  			for _, res := range resources.Items {
  1471  				if res.Group == item.key.Group && res.Kind == item.key.Kind &&
  1472  					res.Namespace == item.key.Namespace && res.Name == item.key.Name {
  1473  					liveResource = res
  1474  					break
  1475  				}
  1476  			}
  1477  		}
  1478  
  1479  		if liveResource == nil {
  1480  			// Create empty live resource for creation case
  1481  			liveResource = &argoappv1.ResourceDiff{
  1482  				Group:       item.key.Group,
  1483  				Kind:        item.key.Kind,
  1484  				Namespace:   item.key.Namespace,
  1485  				Name:        item.key.Name,
  1486  				LiveState:   "",
  1487  				TargetState: "",
  1488  				Modified:    true,
  1489  			}
  1490  		}
  1491  
  1492  		if item.target != nil {
  1493  			jsonBytes, err := json.Marshal(item.target)
  1494  			if err != nil {
  1495  				errors.CheckError(fmt.Errorf("error marshaling target object: %w", err))
  1496  			}
  1497  			targetManifest = string(jsonBytes)
  1498  		}
  1499  
  1500  		// Call server-side diff for this individual resource
  1501  		serverSideDiffQuery := &application.ApplicationServerSideDiffQuery{
  1502  			AppName:         &appName,
  1503  			AppNamespace:    &appNs,
  1504  			Project:         &app.Spec.Project,
  1505  			LiveResources:   []*argoappv1.ResourceDiff{liveResource},
  1506  			TargetManifests: []string{targetManifest},
  1507  		}
  1508  
  1509  		serverSideDiffRes, err := appIf.ServerSideDiff(ctx, serverSideDiffQuery)
  1510  		if err != nil {
  1511  			errors.CheckError(err)
  1512  		}
  1513  
  1514  		// Extract diff for this resource
  1515  		for _, resultItem := range serverSideDiffRes.Items {
  1516  			if resultItem.Hook || (!resultItem.Modified && resultItem.TargetState != "" && resultItem.LiveState != "") {
  1517  				continue
  1518  			}
  1519  
  1520  			if resultItem.Modified || resultItem.TargetState == "" || resultItem.LiveState == "" {
  1521  				var live, target *unstructured.Unstructured
  1522  
  1523  				if resultItem.TargetState != "" && resultItem.TargetState != "null" {
  1524  					target = &unstructured.Unstructured{}
  1525  					err = json.Unmarshal([]byte(resultItem.TargetState), target)
  1526  					errors.CheckError(err)
  1527  				}
  1528  
  1529  				if resultItem.LiveState != "" && resultItem.LiveState != "null" {
  1530  					live = &unstructured.Unstructured{}
  1531  					err = json.Unmarshal([]byte(resultItem.LiveState), live)
  1532  					errors.CheckError(err)
  1533  				}
  1534  
  1535  				// Print resulting diff for this resource
  1536  				foundDiffs = true
  1537  				printResourceDiff(resultItem.Group, resultItem.Kind, resultItem.Namespace, resultItem.Name, live, target)
  1538  			}
  1539  		}
  1540  	}
  1541  
  1542  	return foundDiffs
  1543  }
  1544  
  1545  // DifferenceOption struct to store diff options
  1546  type DifferenceOption struct {
  1547  	local         string
  1548  	localRepoRoot string
  1549  	revision      string
  1550  	cluster       *argoappv1.Cluster
  1551  	res           *repoapiclient.ManifestResponse
  1552  	serversideRes *repoapiclient.ManifestResponse
  1553  	revisions     []string
  1554  }
  1555  
  1556  // findAndPrintDiff ... Prints difference between application current state and state stored in git or locally, returns boolean as true if difference is found else returns false
  1557  func findAndPrintDiff(ctx context.Context, app *argoappv1.Application, proj *argoappv1.AppProject, resources *application.ManagedResourcesResponse, argoSettings *settings.Settings, diffOptions *DifferenceOption, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts, useServerSideDiff bool, appIf application.ApplicationServiceClient, appName, appNs string) bool {
  1558  	var foundDiffs bool
  1559  
  1560  	items, err := prepareObjectsForDiff(ctx, app, proj, resources, argoSettings, diffOptions)
  1561  	errors.CheckError(err)
  1562  
  1563  	if useServerSideDiff {
  1564  		return findAndPrintServerSideDiff(ctx, app, items, resources, appIf, appName, appNs)
  1565  	}
  1566  
  1567  	for _, item := range items {
  1568  		if item.target != nil && hook.IsHook(item.target) || item.live != nil && hook.IsHook(item.live) {
  1569  			continue
  1570  		}
  1571  		overrides := make(map[string]argoappv1.ResourceOverride)
  1572  		for k := range argoSettings.ResourceOverrides {
  1573  			val := argoSettings.ResourceOverrides[k]
  1574  			overrides[k] = *val
  1575  		}
  1576  
  1577  		// TODO remove hardcoded IgnoreAggregatedRoles and retrieve the
  1578  		// compareOptions in the protobuf
  1579  		ignoreAggregatedRoles := false
  1580  		diffConfig, err := argodiff.NewDiffConfigBuilder().
  1581  			WithDiffSettings(app.Spec.IgnoreDifferences, overrides, ignoreAggregatedRoles, ignoreNormalizerOpts).
  1582  			WithTracking(argoSettings.AppLabelKey, argoSettings.TrackingMethod).
  1583  			WithNoCache().
  1584  			WithLogger(logutils.NewLogrusLogger(logutils.NewWithCurrentConfig())).
  1585  			Build()
  1586  		errors.CheckError(err)
  1587  		diffRes, err := argodiff.StateDiff(item.live, item.target, diffConfig)
  1588  		errors.CheckError(err)
  1589  
  1590  		if diffRes.Modified || item.target == nil || item.live == nil {
  1591  			var live *unstructured.Unstructured
  1592  			var target *unstructured.Unstructured
  1593  			if item.target != nil && item.live != nil {
  1594  				target = &unstructured.Unstructured{}
  1595  				live = item.live
  1596  				err = json.Unmarshal(diffRes.PredictedLive, target)
  1597  				errors.CheckError(err)
  1598  			} else {
  1599  				live = item.live
  1600  				target = item.target
  1601  			}
  1602  			foundDiffs = true
  1603  			printResourceDiff(item.key.Group, item.key.Kind, item.key.Namespace, item.key.Name, live, target)
  1604  		}
  1605  	}
  1606  	return foundDiffs
  1607  }
  1608  
  1609  func groupObjsForDiff(resources *application.ManagedResourcesResponse, objs map[kube.ResourceKey]*unstructured.Unstructured, items []objKeyLiveTarget, argoSettings *settings.Settings, appName, namespace string) []objKeyLiveTarget {
  1610  	resourceTracking := argo.NewResourceTracking()
  1611  	for _, res := range resources.Items {
  1612  		live := &unstructured.Unstructured{}
  1613  		err := json.Unmarshal([]byte(res.NormalizedLiveState), &live)
  1614  		errors.CheckError(err)
  1615  
  1616  		key := kube.ResourceKey{Name: res.Name, Namespace: res.Namespace, Group: res.Group, Kind: res.Kind}
  1617  		if key.Kind == kube.SecretKind && key.Group == "" {
  1618  			// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
  1619  			delete(objs, key)
  1620  			continue
  1621  		}
  1622  		if local, ok := objs[key]; ok || live != nil {
  1623  			if local != nil && !kube.IsCRD(local) {
  1624  				err = resourceTracking.SetAppInstance(local, argoSettings.AppLabelKey, appName, namespace, argoappv1.TrackingMethod(argoSettings.GetTrackingMethod()), argoSettings.GetInstallationID())
  1625  				errors.CheckError(err)
  1626  			}
  1627  
  1628  			items = append(items, objKeyLiveTarget{key, live, local})
  1629  			delete(objs, key)
  1630  		}
  1631  	}
  1632  	for key, local := range objs {
  1633  		if key.Kind == kube.SecretKind && key.Group == "" {
  1634  			// Don't bother comparing secrets, argo-cd doesn't have access to k8s secret data
  1635  			delete(objs, key)
  1636  			continue
  1637  		}
  1638  		items = append(items, objKeyLiveTarget{key, nil, local})
  1639  	}
  1640  	return items
  1641  }
  1642  
  1643  // NewApplicationDeleteCommand returns a new instance of an `argocd app delete` command
  1644  func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  1645  	var (
  1646  		cascade           bool
  1647  		noPrompt          bool
  1648  		propagationPolicy string
  1649  		selector          string
  1650  		wait              bool
  1651  		appNamespace      string
  1652  	)
  1653  	command := &cobra.Command{
  1654  		Use:   "delete APPNAME",
  1655  		Short: "Delete an application",
  1656  		Example: `  # Delete an app
  1657    argocd app delete my-app
  1658  
  1659    # Delete multiple apps
  1660    argocd app delete my-app other-app
  1661  
  1662    # Delete apps by label
  1663    argocd app delete -l app.kubernetes.io/instance=my-app
  1664    argocd app delete -l app.kubernetes.io/instance!=my-app
  1665    argocd app delete -l app.kubernetes.io/instance
  1666    argocd app delete -l '!app.kubernetes.io/instance'
  1667    argocd app delete -l 'app.kubernetes.io/instance notin (my-app,other-app)'`,
  1668  		Run: func(c *cobra.Command, args []string) {
  1669  			ctx := c.Context()
  1670  
  1671  			if len(args) == 0 && selector == "" {
  1672  				c.HelpFunc()(c, args)
  1673  				os.Exit(1)
  1674  			}
  1675  			acdClient := headless.NewClientOrDie(clientOpts, c)
  1676  			conn, appIf := acdClient.NewApplicationClientOrDie()
  1677  			defer utilio.Close(conn)
  1678  			isTerminal := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
  1679  			promptFlag := c.Flag("yes")
  1680  			if promptFlag.Changed && promptFlag.Value.String() == "true" {
  1681  				noPrompt = true
  1682  			}
  1683  
  1684  			appNames, err := getAppNamesBySelector(ctx, appIf, selector)
  1685  			errors.CheckError(err)
  1686  
  1687  			if len(appNames) == 0 {
  1688  				appNames = args
  1689  			}
  1690  
  1691  			numOfApps := len(appNames)
  1692  
  1693  			// This is for backward compatibility,
  1694  			// before we showed the prompts only when condition cascade && isTerminal && !noPrompt is true
  1695  			promptUtil := utils.NewPrompt(cascade && isTerminal && !noPrompt)
  1696  			var (
  1697  				confirmAll = false
  1698  				confirm    = false
  1699  			)
  1700  
  1701  			for _, appFullName := range appNames {
  1702  				appName, appNs := argo.ParseFromQualifiedName(appFullName, appNamespace)
  1703  				appDeleteReq := application.ApplicationDeleteRequest{
  1704  					Name:         &appName,
  1705  					AppNamespace: &appNs,
  1706  				}
  1707  				if c.Flag("cascade").Changed {
  1708  					appDeleteReq.Cascade = &cascade
  1709  				}
  1710  				if c.Flag("propagation-policy").Changed {
  1711  					appDeleteReq.PropagationPolicy = &propagationPolicy
  1712  				}
  1713  				messageForSingle := "Are you sure you want to delete '" + appFullName + "' and all its resources? [y/n] "
  1714  				messageForAll := "Are you sure you want to delete '" + appFullName + "' and all its resources? [y/n/a] where 'a' is to delete all specified apps and their resources without prompting "
  1715  
  1716  				if !confirmAll {
  1717  					confirm, confirmAll = promptUtil.ConfirmBaseOnCount(messageForSingle, messageForAll, numOfApps)
  1718  				}
  1719  				if confirm || confirmAll {
  1720  					_, err := appIf.Delete(ctx, &appDeleteReq)
  1721  					errors.CheckError(err)
  1722  					if wait {
  1723  						checkForDeleteEvent(ctx, acdClient, appFullName)
  1724  					}
  1725  					fmt.Printf("application '%s' deleted\n", appFullName)
  1726  				} else {
  1727  					fmt.Println("The command to delete '" + appFullName + "' was cancelled.")
  1728  				}
  1729  			}
  1730  		},
  1731  	}
  1732  	command.Flags().BoolVar(&cascade, "cascade", true, "Perform a cascaded deletion of all application resources")
  1733  	command.Flags().StringVarP(&propagationPolicy, "propagation-policy", "p", "foreground", "Specify propagation policy for deletion of application's resources. One of: foreground|background")
  1734  	command.Flags().BoolVarP(&noPrompt, "yes", "y", false, "Turn off prompting to confirm cascaded deletion of application resources")
  1735  	command.Flags().StringVarP(&selector, "selector", "l", "", "Delete all apps with matching label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.")
  1736  	command.Flags().BoolVar(&wait, "wait", false, "Wait until deletion of the application(s) completes")
  1737  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace where the application will be deleted from")
  1738  	return command
  1739  }
  1740  
  1741  func checkForDeleteEvent(ctx context.Context, acdClient argocdclient.Client, appFullName string) {
  1742  	appEventCh := acdClient.WatchApplicationWithRetry(ctx, appFullName, "")
  1743  	for appEvent := range appEventCh {
  1744  		if appEvent.Type == k8swatch.Deleted {
  1745  			return
  1746  		}
  1747  	}
  1748  }
  1749  
  1750  // Print simple list of application names
  1751  func printApplicationNames(apps []argoappv1.Application) {
  1752  	for _, app := range apps {
  1753  		fmt.Println(app.QualifiedName())
  1754  	}
  1755  }
  1756  
  1757  // Print table of application data
  1758  func printApplicationTable(apps []argoappv1.Application, output *string) {
  1759  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
  1760  	var fmtStr string
  1761  	headers := []any{"NAME", "CLUSTER", "NAMESPACE", "PROJECT", "STATUS", "HEALTH", "SYNCPOLICY", "CONDITIONS"}
  1762  	if *output == "wide" {
  1763  		fmtStr = "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
  1764  		headers = append(headers, "REPO", "PATH", "TARGET")
  1765  	} else {
  1766  		fmtStr = "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
  1767  	}
  1768  	_, _ = fmt.Fprintf(w, fmtStr, headers...)
  1769  	for _, app := range apps {
  1770  		vals := []any{
  1771  			app.QualifiedName(),
  1772  			getServer(&app),
  1773  			app.Spec.Destination.Namespace,
  1774  			app.Spec.GetProject(),
  1775  			app.Status.Sync.Status,
  1776  			app.Status.Health.Status,
  1777  			formatSyncPolicy(app),
  1778  			formatConditionsSummary(app),
  1779  		}
  1780  		if *output == "wide" {
  1781  			vals = append(vals, app.Spec.GetSource().RepoURL, app.Spec.GetSource().Path, app.Spec.GetSource().TargetRevision)
  1782  		}
  1783  		_, _ = fmt.Fprintf(w, fmtStr, vals...)
  1784  	}
  1785  	_ = w.Flush()
  1786  }
  1787  
  1788  // NewApplicationListCommand returns a new instance of an `argocd app list` command
  1789  func NewApplicationListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  1790  	var (
  1791  		output       string
  1792  		selector     string
  1793  		projects     []string
  1794  		repo         string
  1795  		appNamespace string
  1796  		cluster      string
  1797  	)
  1798  	command := &cobra.Command{
  1799  		Use:   "list",
  1800  		Short: "List applications",
  1801  		Example: `  # List all apps
  1802    argocd app list
  1803  
  1804    # List apps by label, in this example we listing apps that are children of another app (aka app-of-apps)
  1805    argocd app list -l app.kubernetes.io/instance=my-app
  1806    argocd app list -l app.kubernetes.io/instance!=my-app
  1807    argocd app list -l app.kubernetes.io/instance
  1808    argocd app list -l '!app.kubernetes.io/instance'
  1809    argocd app list -l 'app.kubernetes.io/instance notin (my-app,other-app)'`,
  1810  		Run: func(c *cobra.Command, _ []string) {
  1811  			ctx := c.Context()
  1812  
  1813  			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
  1814  			defer utilio.Close(conn)
  1815  			apps, err := appIf.List(ctx, &application.ApplicationQuery{
  1816  				Selector:     ptr.To(selector),
  1817  				AppNamespace: &appNamespace,
  1818  			})
  1819  
  1820  			errors.CheckError(err)
  1821  			appList := apps.Items
  1822  
  1823  			if len(projects) != 0 {
  1824  				appList = argo.FilterByProjects(appList, projects)
  1825  			}
  1826  			if repo != "" {
  1827  				appList = argo.FilterByRepo(appList, repo)
  1828  			}
  1829  			if cluster != "" {
  1830  				appList = argo.FilterByCluster(appList, cluster)
  1831  			}
  1832  
  1833  			switch output {
  1834  			case "yaml", "json":
  1835  				err := PrintResourceList(appList, output, false)
  1836  				errors.CheckError(err)
  1837  			case "name":
  1838  				printApplicationNames(appList)
  1839  			case "wide", "":
  1840  				printApplicationTable(appList, &output)
  1841  			default:
  1842  				errors.CheckError(fmt.Errorf("unknown output format: %s", output))
  1843  			}
  1844  		},
  1845  	}
  1846  	command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|name|json|yaml")
  1847  	command.Flags().StringVarP(&selector, "selector", "l", "", "List apps by label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.")
  1848  	command.Flags().StringArrayVarP(&projects, "project", "p", []string{}, "Filter by project name")
  1849  	command.Flags().StringVarP(&repo, "repo", "r", "", "List apps by source repo URL")
  1850  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only list applications in namespace")
  1851  	command.Flags().StringVarP(&cluster, "cluster", "c", "", "List apps by cluster name or url")
  1852  	return command
  1853  }
  1854  
  1855  func formatSyncPolicy(app argoappv1.Application) string {
  1856  	if app.Spec.SyncPolicy == nil || !app.Spec.SyncPolicy.IsAutomatedSyncEnabled() {
  1857  		return "Manual"
  1858  	}
  1859  	policy := "Auto"
  1860  	if app.Spec.SyncPolicy.Automated.Prune {
  1861  		policy = policy + "-Prune"
  1862  	}
  1863  	return policy
  1864  }
  1865  
  1866  func formatConditionsSummary(app argoappv1.Application) string {
  1867  	typeToCnt := make(map[string]int)
  1868  	for i := range app.Status.Conditions {
  1869  		condition := app.Status.Conditions[i]
  1870  		if cnt, ok := typeToCnt[condition.Type]; ok {
  1871  			typeToCnt[condition.Type] = cnt + 1
  1872  		} else {
  1873  			typeToCnt[condition.Type] = 1
  1874  		}
  1875  	}
  1876  	items := make([]string, 0)
  1877  	for cndType, cnt := range typeToCnt {
  1878  		if cnt > 1 {
  1879  			items = append(items, fmt.Sprintf("%s(%d)", cndType, cnt))
  1880  		} else {
  1881  			items = append(items, cndType)
  1882  		}
  1883  	}
  1884  
  1885  	// Sort the keys by name
  1886  	sort.Strings(items)
  1887  
  1888  	summary := "<none>"
  1889  	if len(items) > 0 {
  1890  		slices.Sort(items)
  1891  		summary = strings.Join(items, ",")
  1892  	}
  1893  	return summary
  1894  }
  1895  
  1896  const (
  1897  	resourceFieldDelimiter              = ":"
  1898  	resourceFieldCount                  = 3
  1899  	resourceFieldNamespaceDelimiter     = "/"
  1900  	resourceFieldNameWithNamespaceCount = 2
  1901  	resourceExcludeIndicator            = "!"
  1902  )
  1903  
  1904  // resource is GROUP:KIND:NAMESPACE/NAME or GROUP:KIND:NAME
  1905  func parseSelectedResources(resources []string) ([]*argoappv1.SyncOperationResource, error) {
  1906  	// retrieve name and namespace in case if format is GROUP:KIND:NAMESPACE/NAME, otherwise return name and empty namespace
  1907  	nameRetriever := func(resourceName, resource string) (string, string, error) {
  1908  		if !strings.Contains(resourceName, resourceFieldNamespaceDelimiter) {
  1909  			return resourceName, "", nil
  1910  		}
  1911  		nameFields := strings.Split(resourceName, resourceFieldNamespaceDelimiter)
  1912  		if len(nameFields) != resourceFieldNameWithNamespaceCount {
  1913  			return "", "", fmt.Errorf("resource with namespace should have GROUP%sKIND%sNAMESPACE%sNAME, but instead got: %s", resourceFieldDelimiter, resourceFieldDelimiter, resourceFieldNamespaceDelimiter, resource)
  1914  		}
  1915  		namespace := nameFields[0]
  1916  		name := nameFields[1]
  1917  		return name, namespace, nil
  1918  	}
  1919  
  1920  	var selectedResources []*argoappv1.SyncOperationResource
  1921  	if resources == nil {
  1922  		return selectedResources, nil
  1923  	}
  1924  
  1925  	for _, resource := range resources {
  1926  		isExcluded := false
  1927  		// check if the resource flag starts with a '!'
  1928  		if strings.HasPrefix(resource, resourceExcludeIndicator) {
  1929  			resource = strings.TrimPrefix(resource, resourceExcludeIndicator)
  1930  			isExcluded = true
  1931  		}
  1932  		fields := strings.Split(resource, resourceFieldDelimiter)
  1933  		if len(fields) != resourceFieldCount {
  1934  			return nil, fmt.Errorf("resource should have GROUP%sKIND%sNAME, but instead got: %s", resourceFieldDelimiter, resourceFieldDelimiter, resource)
  1935  		}
  1936  		name, namespace, err := nameRetriever(fields[2], resource)
  1937  		if err != nil {
  1938  			return nil, err
  1939  		}
  1940  		selectedResources = append(selectedResources, &argoappv1.SyncOperationResource{
  1941  			Group:     fields[0],
  1942  			Kind:      fields[1],
  1943  			Name:      name,
  1944  			Namespace: namespace,
  1945  			Exclude:   isExcluded,
  1946  		})
  1947  	}
  1948  	return selectedResources, nil
  1949  }
  1950  
  1951  func getWatchOpts(watch watchOpts) watchOpts {
  1952  	// if no opts are defined should wait for sync,health,operation
  1953  	if (watch == watchOpts{}) {
  1954  		return watchOpts{
  1955  			sync:      true,
  1956  			health:    true,
  1957  			operation: true,
  1958  		}
  1959  	}
  1960  	return watch
  1961  }
  1962  
  1963  // NewApplicationWaitCommand returns a new instance of an `argocd app wait` command
  1964  func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  1965  	var (
  1966  		watch        watchOpts
  1967  		timeout      uint
  1968  		selector     string
  1969  		resources    []string
  1970  		output       string
  1971  		appNamespace string
  1972  	)
  1973  	command := &cobra.Command{
  1974  		Use:   "wait [APPNAME.. | -l selector]",
  1975  		Short: "Wait for an application to reach a synced and healthy state",
  1976  		Example: `  # Wait for an app
  1977    argocd app wait my-app
  1978  
  1979    # Wait for multiple apps
  1980    argocd app wait my-app other-app
  1981  
  1982    # Wait for apps by resource
  1983    # Resource should be formatted as GROUP:KIND:NAME. If no GROUP is specified then :KIND:NAME.
  1984    argocd app wait my-app --resource :Service:my-service
  1985    argocd app wait my-app --resource argoproj.io:Rollout:my-rollout
  1986    argocd app wait my-app --resource '!apps:Deployment:my-service'
  1987    argocd app wait my-app --resource apps:Deployment:my-service --resource :Service:my-service
  1988    argocd app wait my-app --resource '!*:Service:*'
  1989    # Specify namespace if the application has resources with the same name in different namespaces
  1990    argocd app wait my-app --resource argoproj.io:Rollout:my-namespace/my-rollout
  1991  
  1992    # Wait for apps by label, in this example we waiting for apps that are children of another app (aka app-of-apps)
  1993    argocd app wait -l app.kubernetes.io/instance=my-app
  1994    argocd app wait -l app.kubernetes.io/instance!=my-app
  1995    argocd app wait -l app.kubernetes.io/instance
  1996    argocd app wait -l '!app.kubernetes.io/instance'
  1997    argocd app wait -l 'app.kubernetes.io/instance notin (my-app,other-app)'`,
  1998  		Run: func(c *cobra.Command, args []string) {
  1999  			ctx := c.Context()
  2000  
  2001  			if len(args) == 0 && selector == "" {
  2002  				c.HelpFunc()(c, args)
  2003  				os.Exit(1)
  2004  			}
  2005  			watch = getWatchOpts(watch)
  2006  			selectedResources, err := parseSelectedResources(resources)
  2007  			errors.CheckError(err)
  2008  			appNames := args
  2009  			acdClient := headless.NewClientOrDie(clientOpts, c)
  2010  			closer, appIf := acdClient.NewApplicationClientOrDie()
  2011  			defer utilio.Close(closer)
  2012  			if selector != "" {
  2013  				list, err := appIf.List(ctx, &application.ApplicationQuery{Selector: ptr.To(selector)})
  2014  				errors.CheckError(err)
  2015  				for _, i := range list.Items {
  2016  					appNames = append(appNames, i.QualifiedName())
  2017  				}
  2018  			}
  2019  			for _, appName := range appNames {
  2020  				// Construct QualifiedName
  2021  				if appNamespace != "" && !strings.Contains(appName, "/") {
  2022  					appName = appNamespace + "/" + appName
  2023  				}
  2024  				_, _, err := waitOnApplicationStatus(ctx, acdClient, appName, timeout, watch, selectedResources, output)
  2025  				errors.CheckError(err)
  2026  			}
  2027  		},
  2028  	}
  2029  	command.Flags().BoolVar(&watch.sync, "sync", false, "Wait for sync")
  2030  	command.Flags().BoolVar(&watch.health, "health", false, "Wait for health")
  2031  	command.Flags().BoolVar(&watch.suspended, "suspended", false, "Wait for suspended")
  2032  	command.Flags().BoolVar(&watch.degraded, "degraded", false, "Wait for degraded")
  2033  	command.Flags().BoolVar(&watch.delete, "delete", false, "Wait for delete")
  2034  	command.Flags().BoolVar(&watch.hydrated, "hydrated", false, "Wait for hydration operations")
  2035  	command.Flags().StringVarP(&selector, "selector", "l", "", "Wait for apps by label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.")
  2036  	command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%[1]sKIND%[1]sNAME or %[2]sGROUP%[1]sKIND%[1]sNAME. Fields may be blank and '*' can be used. This option may be specified repeatedly", resourceFieldDelimiter, resourceExcludeIndicator))
  2037  	command.Flags().BoolVar(&watch.operation, "operation", false, "Wait for pending operations")
  2038  	command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
  2039  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only wait for an application  in namespace")
  2040  	command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed")
  2041  	return command
  2042  }
  2043  
  2044  // printAppResources prints the resources of an application in a tabwriter table
  2045  func printAppResources(w io.Writer, app *argoappv1.Application) {
  2046  	_, _ = fmt.Fprintf(w, "GROUP\tKIND\tNAMESPACE\tNAME\tSTATUS\tHEALTH\tHOOK\tMESSAGE\n")
  2047  	for _, res := range getResourceStates(app, nil) {
  2048  		_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", res.Group, res.Kind, res.Namespace, res.Name, res.Status, res.Health, res.Hook, res.Message)
  2049  	}
  2050  }
  2051  
  2052  func printTreeView(nodeMapping map[string]argoappv1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, mapNodeNameToResourceState map[string]*resourceState) {
  2053  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
  2054  	_, _ = fmt.Fprintf(w, "KIND/NAME\tSTATUS\tHEALTH\tMESSAGE\n")
  2055  	for uid := range parentNodes {
  2056  		treeViewAppGet("", nodeMapping, parentChildMapping, nodeMapping[uid], mapNodeNameToResourceState, w)
  2057  	}
  2058  	_ = w.Flush()
  2059  }
  2060  
  2061  func printTreeViewDetailed(nodeMapping map[string]argoappv1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, mapNodeNameToResourceState map[string]*resourceState) {
  2062  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
  2063  	fmt.Fprintf(w, "KIND/NAME\tSTATUS\tHEALTH\tAGE\tMESSAGE\tREASON\n")
  2064  	for uid := range parentNodes {
  2065  		detailedTreeViewAppGet("", nodeMapping, parentChildMapping, nodeMapping[uid], mapNodeNameToResourceState, w)
  2066  	}
  2067  	_ = w.Flush()
  2068  }
  2069  
  2070  // NewApplicationSyncCommand returns a new instance of an `argocd app sync` command
  2071  func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  2072  	var (
  2073  		revision                string
  2074  		revisions               []string
  2075  		sourcePositions         []int64
  2076  		sourceNames             []string
  2077  		resources               []string
  2078  		labels                  []string
  2079  		selector                string
  2080  		prune                   bool
  2081  		dryRun                  bool
  2082  		timeout                 uint
  2083  		strategy                string
  2084  		force                   bool
  2085  		replace                 bool
  2086  		serverSideApply         bool
  2087  		applyOutOfSyncOnly      bool
  2088  		async                   bool
  2089  		retryLimit              int64
  2090  		retryRefresh            bool
  2091  		retryBackoffDuration    time.Duration
  2092  		retryBackoffMaxDuration time.Duration
  2093  		retryBackoffFactor      int64
  2094  		local                   string
  2095  		localRepoRoot           string
  2096  		infos                   []string
  2097  		diffChanges             bool
  2098  		diffChangesConfirm      bool
  2099  		projects                []string
  2100  		output                  string
  2101  		appNamespace            string
  2102  		ignoreNormalizerOpts    normalizers.IgnoreNormalizerOpts
  2103  	)
  2104  	command := &cobra.Command{
  2105  		Use:   "sync [APPNAME... | -l selector | --project project-name]",
  2106  		Short: "Sync an application to its target state",
  2107  		Example: `  # Sync an app
  2108    argocd app sync my-app
  2109  
  2110    # Sync multiples apps
  2111    argocd app sync my-app other-app
  2112  
  2113    # Sync apps by label, in this example we sync apps that are children of another app (aka app-of-apps)
  2114    argocd app sync -l app.kubernetes.io/instance=my-app
  2115    argocd app sync -l app.kubernetes.io/instance!=my-app
  2116    argocd app sync -l app.kubernetes.io/instance
  2117    argocd app sync -l '!app.kubernetes.io/instance'
  2118    argocd app sync -l 'app.kubernetes.io/instance notin (my-app,other-app)'
  2119  
  2120    # Sync a multi-source application for specific revision of specific sources
  2121    argocd app sync my-app --revisions 0.0.1 --source-positions 1 --revisions 0.0.2 --source-positions 2
  2122    argocd app sync my-app --revisions 0.0.1 --source-names my-chart --revisions 0.0.2 --source-names my-values
  2123  
  2124    # Sync a specific resource
  2125    # Resource should be formatted as GROUP:KIND:NAME. If no GROUP is specified then :KIND:NAME
  2126    argocd app sync my-app --resource :Service:my-service
  2127    argocd app sync my-app --resource argoproj.io:Rollout:my-rollout
  2128    argocd app sync my-app --resource '!apps:Deployment:my-service'
  2129    argocd app sync my-app --resource apps:Deployment:my-service --resource :Service:my-service
  2130    argocd app sync my-app --resource '!*:Service:*'
  2131    # Specify namespace if the application has resources with the same name in different namespaces
  2132    argocd app sync my-app --resource argoproj.io:Rollout:my-namespace/my-rollout`,
  2133  		Run: func(c *cobra.Command, args []string) {
  2134  			ctx := c.Context()
  2135  			if len(args) == 0 && selector == "" && len(projects) == 0 {
  2136  				c.HelpFunc()(c, args)
  2137  				os.Exit(1)
  2138  			}
  2139  			if len(args) > 1 && selector != "" {
  2140  				log.Fatal("Cannot use selector option when application name(s) passed as argument(s)")
  2141  			}
  2142  
  2143  			if len(args) != 1 && (len(revisions) > 0 || len(sourcePositions) > 0) {
  2144  				log.Fatal("Cannot use --revisions and --source-positions options when 0 or more than 1 application names are passed as argument(s)")
  2145  			}
  2146  
  2147  			if len(args) != 1 && (len(revisions) > 0 || len(sourceNames) > 0) {
  2148  				log.Fatal("Cannot use --revisions and --source-names options when 0 or more than 1 application names are passed as argument(s)")
  2149  			}
  2150  
  2151  			if len(sourceNames) > 0 && len(sourcePositions) > 0 {
  2152  				log.Fatal("Only one of source-positions and source-names can be specified.")
  2153  			}
  2154  
  2155  			if len(sourcePositions) > 0 && len(revisions) != len(sourcePositions) {
  2156  				log.Fatal("While using --revisions and --source-positions, length of values for both flags should be same.")
  2157  			}
  2158  
  2159  			if len(sourceNames) > 0 && len(revisions) != len(sourceNames) {
  2160  				log.Fatal("While using --revisions and --source-names, length of values for both flags should be same.")
  2161  			}
  2162  
  2163  			for _, pos := range sourcePositions {
  2164  				if pos <= 0 {
  2165  					log.Fatal("source-position cannot be less than or equal to 0, Counting starts at 1")
  2166  				}
  2167  			}
  2168  
  2169  			acdClient := headless.NewClientOrDie(clientOpts, c)
  2170  			conn, appIf := acdClient.NewApplicationClientOrDie()
  2171  			defer utilio.Close(conn)
  2172  
  2173  			selectedLabels, err := label.Parse(labels)
  2174  			errors.CheckError(err)
  2175  
  2176  			if len(args) == 1 && len(sourceNames) > 0 {
  2177  				appName, _ := argo.ParseFromQualifiedName(args[0], appNamespace)
  2178  				app, err := appIf.Get(context.Background(), &application.ApplicationQuery{Name: &appName})
  2179  				errors.CheckError(err)
  2180  
  2181  				sourceNameToPosition := getSourceNameToPositionMap(app)
  2182  
  2183  				for _, name := range sourceNames {
  2184  					pos, ok := sourceNameToPosition[name]
  2185  					if !ok {
  2186  						log.Fatalf("Unknown source name '%s'", name)
  2187  					}
  2188  					sourcePositions = append(sourcePositions, pos)
  2189  				}
  2190  			}
  2191  
  2192  			appNames := args
  2193  			if selector != "" || len(projects) > 0 {
  2194  				list, err := appIf.List(ctx, &application.ApplicationQuery{
  2195  					Selector:     ptr.To(selector),
  2196  					AppNamespace: &appNamespace,
  2197  					Projects:     projects,
  2198  				})
  2199  				errors.CheckError(err)
  2200  
  2201  				// unlike list, we'd want to fail if nothing was found
  2202  				if len(list.Items) == 0 {
  2203  					errMsg := "No matching apps found for filter:"
  2204  					if selector != "" {
  2205  						errMsg += " selector " + selector
  2206  					}
  2207  					if len(projects) != 0 {
  2208  						errMsg += fmt.Sprintf(" projects %v", projects)
  2209  					}
  2210  					log.Fatal(errMsg)
  2211  				}
  2212  
  2213  				for _, i := range list.Items {
  2214  					appNames = append(appNames, i.QualifiedName())
  2215  				}
  2216  			}
  2217  
  2218  			for _, appQualifiedName := range appNames {
  2219  				// Construct QualifiedName
  2220  				if appNamespace != "" && !strings.Contains(appQualifiedName, "/") {
  2221  					appQualifiedName = appNamespace + "/" + appQualifiedName
  2222  				}
  2223  				appName, appNs := argo.ParseFromQualifiedName(appQualifiedName, "")
  2224  
  2225  				if len(selectedLabels) > 0 {
  2226  					q := application.ApplicationManifestQuery{
  2227  						Name:            &appName,
  2228  						AppNamespace:    &appNs,
  2229  						Revision:        &revision,
  2230  						Revisions:       revisions,
  2231  						SourcePositions: sourcePositions,
  2232  					}
  2233  
  2234  					res, err := appIf.GetManifests(ctx, &q)
  2235  					if err != nil {
  2236  						log.Fatal(err)
  2237  					}
  2238  
  2239  					fmt.Println("The name of the app is ", appName)
  2240  
  2241  					for _, mfst := range res.Manifests {
  2242  						obj, err := argoappv1.UnmarshalToUnstructured(mfst)
  2243  						errors.CheckError(err)
  2244  						for key, selectedValue := range selectedLabels {
  2245  							if objectValue, ok := obj.GetLabels()[key]; ok && selectedValue == objectValue {
  2246  								gvk := obj.GroupVersionKind()
  2247  								resources = append(resources, fmt.Sprintf("%s:%s:%s", gvk.Group, gvk.Kind, obj.GetName()))
  2248  							}
  2249  						}
  2250  					}
  2251  
  2252  					// If labels are provided and none are found return error only if specific resources were also not
  2253  					// specified.
  2254  					if len(resources) == 0 {
  2255  						log.Fatalf("No matching resources found for labels: %v", labels)
  2256  						return
  2257  					}
  2258  				}
  2259  
  2260  				selectedResources, err := parseSelectedResources(resources)
  2261  				errors.CheckError(err)
  2262  
  2263  				var localObjsStrings []string
  2264  				diffOption := &DifferenceOption{}
  2265  
  2266  				app, err := appIf.Get(ctx, &application.ApplicationQuery{
  2267  					Name:         &appName,
  2268  					AppNamespace: &appNs,
  2269  				})
  2270  				errors.CheckError(err)
  2271  
  2272  				if app.Spec.HasMultipleSources() {
  2273  					if revision != "" {
  2274  						log.Fatal("argocd cli does not work on multi-source app with --revision flag. Use --revisions and --source-position instead.")
  2275  						return
  2276  					}
  2277  
  2278  					if local != "" {
  2279  						log.Fatal("argocd cli does not work on multi-source app with --local flag")
  2280  						return
  2281  					}
  2282  				}
  2283  
  2284  				// filters out only those resources that needs to be synced
  2285  				filteredResources := filterAppResources(app, selectedResources)
  2286  
  2287  				// if resources are provided and no app resources match, then return error
  2288  				if len(resources) > 0 && len(filteredResources) == 0 {
  2289  					log.Fatalf("No matching app resources found for resource filter: %v", strings.Join(resources, ", "))
  2290  				}
  2291  
  2292  				if local != "" {
  2293  					if app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.IsAutomatedSyncEnabled() && !dryRun {
  2294  						log.Fatal("Cannot use local sync when Automatic Sync Policy is enabled except with --dry-run")
  2295  					}
  2296  
  2297  					errors.CheckError(err)
  2298  					conn, settingsIf := acdClient.NewSettingsClientOrDie()
  2299  					argoSettings, err := settingsIf.Get(ctx, &settings.SettingsQuery{})
  2300  					errors.CheckError(err)
  2301  					utilio.Close(conn)
  2302  
  2303  					conn, clusterIf := acdClient.NewClusterClientOrDie()
  2304  					defer utilio.Close(conn)
  2305  					cluster, err := clusterIf.Get(ctx, &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
  2306  					errors.CheckError(err)
  2307  					utilio.Close(conn)
  2308  
  2309  					proj := getProject(ctx, c, clientOpts, app.Spec.Project)
  2310  					localObjsStrings = getLocalObjectsString(ctx, app, proj.Project, local, localRepoRoot, argoSettings.AppLabelKey, cluster.Info.ServerVersion, cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.TrackingMethod)
  2311  					errors.CheckError(err)
  2312  					diffOption.local = local
  2313  					diffOption.localRepoRoot = localRepoRoot
  2314  					diffOption.cluster = cluster
  2315  				}
  2316  
  2317  				syncOptionsFactory := func() *application.SyncOptions {
  2318  					syncOptions := application.SyncOptions{}
  2319  					items := make([]string, 0)
  2320  					if replace {
  2321  						items = append(items, common.SyncOptionReplace)
  2322  					}
  2323  					if serverSideApply {
  2324  						items = append(items, common.SyncOptionServerSideApply)
  2325  					}
  2326  					if applyOutOfSyncOnly {
  2327  						items = append(items, common.SyncOptionApplyOutOfSyncOnly)
  2328  					}
  2329  
  2330  					if len(items) == 0 {
  2331  						// for prevent send even empty array if not need
  2332  						return nil
  2333  					}
  2334  					syncOptions.Items = items
  2335  					return &syncOptions
  2336  				}
  2337  
  2338  				syncReq := application.ApplicationSyncRequest{
  2339  					Name:            &appName,
  2340  					AppNamespace:    &appNs,
  2341  					DryRun:          &dryRun,
  2342  					Revision:        &revision,
  2343  					Resources:       filteredResources,
  2344  					Prune:           &prune,
  2345  					Manifests:       localObjsStrings,
  2346  					Infos:           getInfos(infos),
  2347  					SyncOptions:     syncOptionsFactory(),
  2348  					Revisions:       revisions,
  2349  					SourcePositions: sourcePositions,
  2350  				}
  2351  
  2352  				switch strategy {
  2353  				case "apply":
  2354  					syncReq.Strategy = &argoappv1.SyncStrategy{Apply: &argoappv1.SyncStrategyApply{}}
  2355  					syncReq.Strategy.Apply.Force = force
  2356  				case "", "hook":
  2357  					syncReq.Strategy = &argoappv1.SyncStrategy{Hook: &argoappv1.SyncStrategyHook{}}
  2358  					syncReq.Strategy.Hook.Force = force
  2359  				default:
  2360  					log.Fatalf("Unknown sync strategy: '%s'", strategy)
  2361  				}
  2362  				if retryLimit != 0 {
  2363  					syncReq.RetryStrategy = &argoappv1.RetryStrategy{
  2364  						Limit:   retryLimit,
  2365  						Refresh: retryRefresh,
  2366  						Backoff: &argoappv1.Backoff{
  2367  							Duration:    retryBackoffDuration.String(),
  2368  							MaxDuration: retryBackoffMaxDuration.String(),
  2369  							Factor:      ptr.To(retryBackoffFactor),
  2370  						},
  2371  					}
  2372  				}
  2373  				if diffChanges {
  2374  					resources, err := appIf.ManagedResources(ctx, &application.ResourcesQuery{
  2375  						ApplicationName: &appName,
  2376  						AppNamespace:    &appNs,
  2377  					})
  2378  					errors.CheckError(err)
  2379  					conn, settingsIf := acdClient.NewSettingsClientOrDie()
  2380  					defer utilio.Close(conn)
  2381  					argoSettings, err := settingsIf.Get(ctx, &settings.SettingsQuery{})
  2382  					errors.CheckError(err)
  2383  					foundDiffs := false
  2384  					fmt.Printf("====== Previewing differences between live and desired state of application %s ======\n", appQualifiedName)
  2385  
  2386  					proj := getProject(ctx, c, clientOpts, app.Spec.Project)
  2387  
  2388  					// Check if application has ServerSideDiff annotation
  2389  					serverSideDiff := resourceutil.HasAnnotationOption(app, argocommon.AnnotationCompareOptions, "ServerSideDiff=true")
  2390  
  2391  					foundDiffs = findAndPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption, ignoreNormalizerOpts, serverSideDiff, appIf, appName, appNs)
  2392  					if !foundDiffs {
  2393  						fmt.Printf("====== No Differences found ======\n")
  2394  						// if no differences found, then no need to sync
  2395  						return
  2396  					}
  2397  					if !diffChangesConfirm {
  2398  						yesno := cli.AskToProceed(fmt.Sprintf("Please review changes to application %s shown above. Do you want to continue the sync process? (y/n): ", appQualifiedName))
  2399  						if !yesno {
  2400  							os.Exit(0)
  2401  						}
  2402  					}
  2403  				}
  2404  				_, err = appIf.Sync(ctx, &syncReq)
  2405  				errors.CheckError(err)
  2406  
  2407  				if !async {
  2408  					app, opState, err := waitOnApplicationStatus(ctx, acdClient, appQualifiedName, timeout, watchOpts{operation: true}, selectedResources, output)
  2409  					errors.CheckError(err)
  2410  
  2411  					if !dryRun {
  2412  						if !opState.Phase.Successful() {
  2413  							log.Fatalf("Operation has completed with phase: %s", opState.Phase)
  2414  						} else if len(selectedResources) == 0 && app.Status.Sync.Status != argoappv1.SyncStatusCodeSynced {
  2415  							// Only get resources to be pruned if sync was application-wide and final status is not synced
  2416  							pruningRequired := opState.SyncResult.Resources.PruningRequired()
  2417  							if pruningRequired > 0 {
  2418  								log.Fatalf("%d resources require pruning", pruningRequired)
  2419  							}
  2420  						}
  2421  					}
  2422  				}
  2423  			}
  2424  		},
  2425  	}
  2426  	command.Flags().BoolVar(&dryRun, "dry-run", false, "Preview apply without affecting cluster")
  2427  	command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
  2428  	command.Flags().StringVar(&revision, "revision", "", "Sync to a specific revision. Preserves parameter overrides")
  2429  	command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%[1]sKIND%[1]sNAME or %[2]sGROUP%[1]sKIND%[1]sNAME. Fields may be blank and '*' can be used. This option may be specified repeatedly", resourceFieldDelimiter, resourceExcludeIndicator))
  2430  	command.Flags().StringVarP(&selector, "selector", "l", "", "Sync apps that match this label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.")
  2431  	command.Flags().StringArrayVar(&labels, "label", []string{}, "Sync only specific resources with a label. This option may be specified repeatedly.")
  2432  	command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
  2433  	command.Flags().Int64Var(&retryLimit, "retry-limit", 0, "Max number of allowed sync retries")
  2434  	command.Flags().BoolVar(&retryRefresh, "retry-refresh", false, "Indicates if the latest revision should be used on retry instead of the initial one")
  2435  	command.Flags().DurationVar(&retryBackoffDuration, "retry-backoff-duration", argoappv1.DefaultSyncRetryDuration, "Retry backoff base duration. Input needs to be a duration (e.g. 2m, 1h)")
  2436  	command.Flags().DurationVar(&retryBackoffMaxDuration, "retry-backoff-max-duration", argoappv1.DefaultSyncRetryMaxDuration, "Max retry backoff duration. Input needs to be a duration (e.g. 2m, 1h)")
  2437  	command.Flags().Int64Var(&retryBackoffFactor, "retry-backoff-factor", argoappv1.DefaultSyncRetryFactor, "Factor multiplies the base duration after each failed retry")
  2438  	command.Flags().StringVar(&strategy, "strategy", "", "Sync strategy (one of: apply|hook)")
  2439  	command.Flags().BoolVar(&force, "force", false, "Use a force apply")
  2440  	command.Flags().BoolVar(&replace, "replace", false, "Use a kubectl create/replace instead apply")
  2441  	command.Flags().BoolVar(&serverSideApply, "server-side", false, "Use server-side apply while syncing the application")
  2442  	command.Flags().BoolVar(&applyOutOfSyncOnly, "apply-out-of-sync-only", false, "Sync only out-of-sync resources")
  2443  	command.Flags().BoolVar(&async, "async", false, "Do not wait for application to sync before continuing")
  2444  	command.Flags().StringVar(&local, "local", "", "Path to a local directory. When this flag is present no git queries will be made")
  2445  	command.Flags().StringVar(&localRepoRoot, "local-repo-root", "/", "Path to the repository root. Used together with --local allows setting the repository root")
  2446  	command.Flags().StringArrayVar(&infos, "info", []string{}, "A list of key-value pairs during sync process. These infos will be persisted in app.")
  2447  	command.Flags().BoolVar(&diffChangesConfirm, "assumeYes", false, "Assume yes as answer for all user queries or prompts")
  2448  	command.Flags().BoolVar(&diffChanges, "preview-changes", false, "Preview difference against the target and live state before syncing app and wait for user confirmation")
  2449  	command.Flags().StringArrayVar(&projects, "project", []string{}, "Sync apps that belong to the specified projects. This option may be specified repeatedly.")
  2450  	command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed")
  2451  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only sync an application in namespace")
  2452  	command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
  2453  	command.Flags().StringArrayVar(&revisions, "revisions", []string{}, "Show manifests at specific revisions for source position in source-positions")
  2454  	command.Flags().Int64SliceVar(&sourcePositions, "source-positions", []int64{}, "List of source positions. Default is empty array. Counting start at 1.")
  2455  	command.Flags().StringArrayVar(&sourceNames, "source-names", []string{}, "List of source names. Default is an empty array.")
  2456  	return command
  2457  }
  2458  
  2459  func getAppNamesBySelector(ctx context.Context, appIf application.ApplicationServiceClient, selector string) ([]string, error) {
  2460  	appNames := []string{}
  2461  	if selector != "" {
  2462  		list, err := appIf.List(ctx, &application.ApplicationQuery{Selector: ptr.To(selector)})
  2463  		if err != nil {
  2464  			return []string{}, err
  2465  		}
  2466  		// unlike list, we'd want to fail if nothing was found
  2467  		if len(list.Items) == 0 {
  2468  			return []string{}, fmt.Errorf("no apps match selector %v", selector)
  2469  		}
  2470  		for _, i := range list.Items {
  2471  			appNames = append(appNames, i.QualifiedName())
  2472  		}
  2473  	}
  2474  	return appNames, nil
  2475  }
  2476  
  2477  // ResourceState tracks the state of a resource when waiting on an application status.
  2478  type resourceState struct {
  2479  	Group     string
  2480  	Kind      string
  2481  	Namespace string
  2482  	Name      string
  2483  	Status    string
  2484  	Health    string
  2485  	Hook      string
  2486  	Message   string
  2487  }
  2488  
  2489  // Key returns a unique-ish key for the resource.
  2490  func (rs *resourceState) Key() string {
  2491  	return fmt.Sprintf("%s/%s/%s/%s", rs.Group, rs.Kind, rs.Namespace, rs.Name)
  2492  }
  2493  
  2494  func (rs *resourceState) FormatItems() []any {
  2495  	timeStr := time.Now().Format("2006-01-02T15:04:05-07:00")
  2496  	return []any{timeStr, rs.Group, rs.Kind, rs.Namespace, rs.Name, rs.Status, rs.Health, rs.Hook, rs.Message}
  2497  }
  2498  
  2499  // Merge merges the new state with any different contents from another resourceState.
  2500  // Blank fields in the receiver state will be updated to non-blank.
  2501  // Non-blank fields in the receiver state will never be updated to blank.
  2502  // Returns whether or not any keys were updated.
  2503  func (rs *resourceState) Merge(newState *resourceState) bool {
  2504  	updated := false
  2505  	for _, field := range []string{"Status", "Health", "Hook", "Message"} {
  2506  		v := reflect.ValueOf(rs).Elem().FieldByName(field)
  2507  		currVal := v.String()
  2508  		newVal := reflect.ValueOf(newState).Elem().FieldByName(field).String()
  2509  		if newVal != "" && currVal != newVal {
  2510  			v.SetString(newVal)
  2511  			updated = true
  2512  		}
  2513  	}
  2514  	return updated
  2515  }
  2516  
  2517  func getResourceStates(app *argoappv1.Application, selectedResources []*argoappv1.SyncOperationResource) []*resourceState {
  2518  	var states []*resourceState
  2519  	resourceByKey := make(map[kube.ResourceKey]argoappv1.ResourceStatus)
  2520  	for i := range app.Status.Resources {
  2521  		res := app.Status.Resources[i]
  2522  		resourceByKey[kube.NewResourceKey(res.Group, res.Kind, res.Namespace, res.Name)] = res
  2523  	}
  2524  
  2525  	// print most resources info along with most recent operation results
  2526  	if app.Status.OperationState != nil && app.Status.OperationState.SyncResult != nil {
  2527  		for _, res := range app.Status.OperationState.SyncResult.Resources {
  2528  			sync := string(res.HookPhase)
  2529  			health := string(res.Status)
  2530  			key := kube.NewResourceKey(res.Group, res.Kind, res.Namespace, res.Name)
  2531  			if resource, ok := resourceByKey[key]; ok && res.HookType == "" {
  2532  				health = ""
  2533  				if resource.Health != nil {
  2534  					health = string(resource.Health.Status)
  2535  				}
  2536  				sync = string(resource.Status)
  2537  			}
  2538  			states = append(states, &resourceState{
  2539  				Group: res.Group, Kind: res.Kind, Namespace: res.Namespace, Name: res.Name, Status: sync, Health: health, Hook: string(res.HookType), Message: res.Message,
  2540  			})
  2541  			delete(resourceByKey, kube.NewResourceKey(res.Group, res.Kind, res.Namespace, res.Name))
  2542  		}
  2543  	}
  2544  	resKeys := make([]kube.ResourceKey, 0)
  2545  	for k := range resourceByKey {
  2546  		resKeys = append(resKeys, k)
  2547  	}
  2548  	sort.Slice(resKeys, func(i, j int) bool {
  2549  		return resKeys[i].String() < resKeys[j].String()
  2550  	})
  2551  	// print rest of resources which were not part of most recent operation
  2552  	for _, resKey := range resKeys {
  2553  		res := resourceByKey[resKey]
  2554  		health := ""
  2555  		if res.Health != nil {
  2556  			health = string(res.Health.Status)
  2557  		}
  2558  		states = append(states, &resourceState{
  2559  			Group: res.Group, Kind: res.Kind, Namespace: res.Namespace, Name: res.Name, Status: string(res.Status), Health: health, Hook: "", Message: "",
  2560  		})
  2561  	}
  2562  	// filter out not selected resources
  2563  	if len(selectedResources) > 0 {
  2564  		for i := len(states) - 1; i >= 0; i-- {
  2565  			res := states[i]
  2566  			if !argo.IncludeResource(res.Name, res.Namespace, schema.GroupVersionKind{Group: res.Group, Kind: res.Kind}, selectedResources) {
  2567  				states = append(states[:i], states[i+1:]...)
  2568  			}
  2569  		}
  2570  	}
  2571  	return states
  2572  }
  2573  
  2574  // filterAppResources selects the app resources that match atleast one of the resource filters.
  2575  func filterAppResources(app *argoappv1.Application, selectedResources []*argoappv1.SyncOperationResource) []*argoappv1.SyncOperationResource {
  2576  	var filteredResources []*argoappv1.SyncOperationResource
  2577  	if app != nil && len(selectedResources) > 0 {
  2578  		for i := range app.Status.Resources {
  2579  			appResource := app.Status.Resources[i]
  2580  			if (argo.IncludeResource(appResource.Name, appResource.Namespace,
  2581  				schema.GroupVersionKind{Group: appResource.Group, Kind: appResource.Kind}, selectedResources)) {
  2582  				filteredResources = append(filteredResources, &argoappv1.SyncOperationResource{
  2583  					Group:     appResource.Group,
  2584  					Kind:      appResource.Kind,
  2585  					Name:      appResource.Name,
  2586  					Namespace: appResource.Namespace,
  2587  				})
  2588  			}
  2589  		}
  2590  	}
  2591  	return filteredResources
  2592  }
  2593  
  2594  func groupResourceStates(app *argoappv1.Application, selectedResources []*argoappv1.SyncOperationResource) map[string]*resourceState {
  2595  	resStates := make(map[string]*resourceState)
  2596  	for _, result := range getResourceStates(app, selectedResources) {
  2597  		key := result.Key()
  2598  		if prev, ok := resStates[key]; ok {
  2599  			prev.Merge(result)
  2600  		} else {
  2601  			resStates[key] = result
  2602  		}
  2603  	}
  2604  	return resStates
  2605  }
  2606  
  2607  // check if resource health, sync and operation statuses matches watch options
  2608  func checkResourceStatus(watch watchOpts, healthStatus string, syncStatus string, operationStatus *argoappv1.Operation, hydrationFinished bool) bool {
  2609  	if watch.delete {
  2610  		return false
  2611  	}
  2612  
  2613  	healthBeingChecked := watch.suspended || watch.health || watch.degraded
  2614  	healthCheckPassed := true
  2615  
  2616  	if healthBeingChecked {
  2617  		healthCheckPassed = false
  2618  		if watch.health {
  2619  			healthCheckPassed = healthCheckPassed || healthStatus == string(health.HealthStatusHealthy)
  2620  		}
  2621  		if watch.suspended {
  2622  			healthCheckPassed = healthCheckPassed || healthStatus == string(health.HealthStatusSuspended)
  2623  		}
  2624  		if watch.degraded {
  2625  			healthCheckPassed = healthCheckPassed || healthStatus == string(health.HealthStatusDegraded)
  2626  		}
  2627  	}
  2628  
  2629  	synced := !watch.sync || syncStatus == string(argoappv1.SyncStatusCodeSynced)
  2630  	operational := !watch.operation || operationStatus == nil
  2631  	hydrated := !watch.hydrated || hydrationFinished
  2632  	return synced && healthCheckPassed && operational && hydrated
  2633  }
  2634  
  2635  // resourceParentChild gets the latest state of the app and the latest state of the app's resource tree and then
  2636  // constructs the necessary data structures to print the app as a tree.
  2637  func resourceParentChild(ctx context.Context, acdClient argocdclient.Client, appName string, appNs string) (map[string]argoappv1.ResourceNode, map[string][]string, map[string]struct{}, map[string]*resourceState) {
  2638  	_, appIf := acdClient.NewApplicationClientOrDie()
  2639  	mapUIDToNode, mapParentToChild, parentNode := parentChildDetails(ctx, appIf, appName, appNs)
  2640  	app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: ptr.To(appName), AppNamespace: ptr.To(appNs)})
  2641  	errors.CheckError(err)
  2642  	mapNodeNameToResourceState := make(map[string]*resourceState)
  2643  	for _, res := range getResourceStates(app, nil) {
  2644  		mapNodeNameToResourceState[res.Kind+"/"+res.Name] = res
  2645  	}
  2646  	return mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState
  2647  }
  2648  
  2649  const waitFormatString = "%s\t%5s\t%10s\t%10s\t%20s\t%8s\t%7s\t%10s\t%s\n"
  2650  
  2651  // AppWithLock encapsulates the application and its lock
  2652  type AppWithLock struct {
  2653  	mu  sync.Mutex
  2654  	app *argoappv1.Application
  2655  }
  2656  
  2657  // NewAppWithLock creates a new AppWithLock instance
  2658  func NewAppWithLock() *AppWithLock {
  2659  	return &AppWithLock{}
  2660  }
  2661  
  2662  // SetApp safely updates the application
  2663  func (a *AppWithLock) SetApp(app *argoappv1.Application) {
  2664  	a.mu.Lock()
  2665  	defer a.mu.Unlock()
  2666  	a.app = app
  2667  }
  2668  
  2669  // GetApp safely retrieves the application
  2670  func (a *AppWithLock) GetApp() *argoappv1.Application {
  2671  	a.mu.Lock()
  2672  	defer a.mu.Unlock()
  2673  	return a.app
  2674  }
  2675  
  2676  // waitOnApplicationStatus watches an application and blocks until either the desired watch conditions
  2677  // are fulfilled or we reach the timeout. Returns the app once desired conditions have been filled.
  2678  // Additionally return the operationState at time of fulfilment (which may be different than returned app).
  2679  func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client, appName string, timeout uint, watch watchOpts, selectedResources []*argoappv1.SyncOperationResource, output string) (*argoappv1.Application, *argoappv1.OperationState, error) {
  2680  	ctx, cancel := context.WithCancel(ctx)
  2681  	defer cancel()
  2682  
  2683  	appWithLock := NewAppWithLock()
  2684  	// refresh controls whether or not we refresh the app before printing the final status.
  2685  	// We only want to do this when an operation is in progress, since operations are the only
  2686  	// time when the sync status lags behind when an operation completes
  2687  	refresh := false
  2688  
  2689  	// appURL is declared here so that it can be used in the printFinalStatus function when the context is cancelled
  2690  	appURL := getAppURL(ctx, acdClient, appName)
  2691  
  2692  	// printSummary controls whether we print the app summary table, OperationState, and ResourceState
  2693  	// We don't want to print these when output type is json or yaml, as the output would become unparsable.
  2694  	printSummary := output != "json" && output != "yaml"
  2695  
  2696  	appRealName, appNs := argo.ParseFromQualifiedName(appName, "")
  2697  
  2698  	printFinalStatus := func(app *argoappv1.Application) *argoappv1.Application {
  2699  		var err error
  2700  		if refresh {
  2701  			conn, appClient := acdClient.NewApplicationClientOrDie()
  2702  			refreshType := string(argoappv1.RefreshTypeNormal)
  2703  			app, err = appClient.Get(ctx, &application.ApplicationQuery{
  2704  				Name:         &appRealName,
  2705  				Refresh:      &refreshType,
  2706  				AppNamespace: &appNs,
  2707  			})
  2708  			errors.CheckError(err)
  2709  			_ = conn.Close()
  2710  		}
  2711  
  2712  		if printSummary {
  2713  			fmt.Println()
  2714  			printAppSummaryTable(app, appURL, nil)
  2715  			fmt.Println()
  2716  			if watch.operation {
  2717  				printOperationResult(app.Status.OperationState)
  2718  			}
  2719  		}
  2720  
  2721  		switch output {
  2722  		case "yaml", "json":
  2723  			err := PrintResource(app, output)
  2724  			errors.CheckError(err)
  2725  		case "wide", "":
  2726  			if len(app.Status.Resources) > 0 {
  2727  				fmt.Println()
  2728  				w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
  2729  				printAppResources(w, app)
  2730  				_ = w.Flush()
  2731  			}
  2732  		case "tree":
  2733  			mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState := resourceParentChild(ctx, acdClient, appRealName, appNs)
  2734  			if len(mapUIDToNode) > 0 {
  2735  				fmt.Println()
  2736  				printTreeView(mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState)
  2737  			}
  2738  		case "tree=detailed":
  2739  
  2740  			mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState := resourceParentChild(ctx, acdClient, appRealName, appNs)
  2741  			if len(mapUIDToNode) > 0 {
  2742  				fmt.Println()
  2743  				printTreeViewDetailed(mapUIDToNode, mapParentToChild, parentNode, mapNodeNameToResourceState)
  2744  			}
  2745  		default:
  2746  			errors.CheckError(fmt.Errorf("unknown output format: %s", output))
  2747  		}
  2748  		return app
  2749  	}
  2750  
  2751  	if timeout != 0 {
  2752  		time.AfterFunc(time.Duration(timeout)*time.Second, func() {
  2753  			conn, appClient := acdClient.NewApplicationClientOrDie()
  2754  			defer conn.Close()
  2755  			// We want to print the final status of the app even if the conditions are not met
  2756  			if printSummary {
  2757  				fmt.Println()
  2758  				fmt.Println("This is the state of the app after wait timed out:")
  2759  			}
  2760  			// Setting refresh = false because we don't want printFinalStatus to execute a refresh
  2761  			refresh = false
  2762  			// Updating the app object to the latest state
  2763  			app, err := appClient.Get(ctx, &application.ApplicationQuery{
  2764  				Name:         &appRealName,
  2765  				AppNamespace: &appNs,
  2766  			})
  2767  			errors.CheckError(err)
  2768  			// Update the app object
  2769  			appWithLock.SetApp(app)
  2770  			// Cancel the context to stop the watch
  2771  			cancel()
  2772  
  2773  			if printSummary {
  2774  				fmt.Println()
  2775  				fmt.Println("The command timed out waiting for the conditions to be met.")
  2776  			}
  2777  		})
  2778  	}
  2779  
  2780  	w := tabwriter.NewWriter(os.Stdout, 5, 0, 2, ' ', 0)
  2781  	if printSummary {
  2782  		_, _ = fmt.Fprintf(w, waitFormatString, "TIMESTAMP", "GROUP", "KIND", "NAMESPACE", "NAME", "STATUS", "HEALTH", "HOOK", "MESSAGE")
  2783  	}
  2784  
  2785  	prevStates := make(map[string]*resourceState)
  2786  	conn, appClient := acdClient.NewApplicationClientOrDie()
  2787  	defer utilio.Close(conn)
  2788  	app, err := appClient.Get(ctx, &application.ApplicationQuery{
  2789  		Name:         &appRealName,
  2790  		AppNamespace: &appNs,
  2791  	})
  2792  	errors.CheckError(err)
  2793  	appWithLock.SetApp(app) // Update the app object
  2794  
  2795  	// printFinalStatus() will refresh and update the app object, potentially causing the app's
  2796  	// status.operationState to be different than the version when we break out of the event loop.
  2797  	// This means the app.status is unreliable for determining the final state of the operation.
  2798  	// finalOperationState captures the operationState as it was seen when we met the conditions of
  2799  	// the wait, so the caller can rely on it to determine the outcome of the operation.
  2800  	// See: https://github.com/argoproj/argo-cd/issues/5592
  2801  	finalOperationState := appWithLock.GetApp().Status.OperationState
  2802  
  2803  	appEventCh := acdClient.WatchApplicationWithRetry(ctx, appName, appWithLock.GetApp().ResourceVersion)
  2804  	for appEvent := range appEventCh {
  2805  		appWithLock.SetApp(&appEvent.Application)
  2806  		app = appWithLock.GetApp()
  2807  
  2808  		finalOperationState = app.Status.OperationState
  2809  		operationInProgress := false
  2810  
  2811  		if watch.delete && appEvent.Type == k8swatch.Deleted {
  2812  			fmt.Printf("Application '%s' deleted\n", app.QualifiedName())
  2813  			return nil, nil, nil
  2814  		}
  2815  
  2816  		// consider the operation is in progress
  2817  		if app.Operation != nil {
  2818  			// if it just got requested
  2819  			operationInProgress = true
  2820  			if !app.Operation.DryRun() {
  2821  				refresh = true
  2822  			}
  2823  		} else if app.Status.OperationState != nil {
  2824  			if app.Status.OperationState.FinishedAt == nil {
  2825  				// if it is not finished yet
  2826  				operationInProgress = true
  2827  			} else if !app.Status.OperationState.Operation.DryRun() && (app.Status.ReconciledAt == nil || app.Status.ReconciledAt.Before(app.Status.OperationState.FinishedAt)) {
  2828  				// if it is just finished and we need to wait for controller to reconcile app once after syncing
  2829  				operationInProgress = true
  2830  			}
  2831  		}
  2832  
  2833  		hydrationFinished := app.Status.SourceHydrator.CurrentOperation != nil && app.Status.SourceHydrator.CurrentOperation.Phase == argoappv1.HydrateOperationPhaseHydrated && app.Status.SourceHydrator.CurrentOperation.SourceHydrator.DeepEquals(app.Status.SourceHydrator.LastSuccessfulOperation.SourceHydrator) && app.Status.SourceHydrator.CurrentOperation.DrySHA == app.Status.SourceHydrator.LastSuccessfulOperation.DrySHA
  2834  
  2835  		var selectedResourcesAreReady bool
  2836  
  2837  		// If selected resources are included, wait only on those resources, otherwise wait on the application as a whole.
  2838  		if len(selectedResources) > 0 {
  2839  			selectedResourcesAreReady = true
  2840  			for _, state := range getResourceStates(app, selectedResources) {
  2841  				resourceIsReady := checkResourceStatus(watch, state.Health, state.Status, appEvent.Application.Operation, hydrationFinished)
  2842  				if !resourceIsReady {
  2843  					selectedResourcesAreReady = false
  2844  					break
  2845  				}
  2846  			}
  2847  		} else {
  2848  			// Wait on the application as a whole
  2849  			selectedResourcesAreReady = checkResourceStatus(watch, string(app.Status.Health.Status), string(app.Status.Sync.Status), appEvent.Application.Operation, hydrationFinished)
  2850  		}
  2851  
  2852  		if selectedResourcesAreReady && (!operationInProgress || !watch.operation) {
  2853  			app = printFinalStatus(app)
  2854  			return app, finalOperationState, nil
  2855  		}
  2856  
  2857  		newStates := groupResourceStates(app, selectedResources)
  2858  		for _, newState := range newStates {
  2859  			var doPrint bool
  2860  			stateKey := newState.Key()
  2861  			if prevState, found := prevStates[stateKey]; found {
  2862  				if watch.health && prevState.Health != string(health.HealthStatusUnknown) && prevState.Health != string(health.HealthStatusDegraded) && newState.Health == string(health.HealthStatusDegraded) {
  2863  					_ = printFinalStatus(app)
  2864  					return nil, finalOperationState, fmt.Errorf("application '%s' health state has transitioned from %s to %s", appName, prevState.Health, newState.Health)
  2865  				}
  2866  				doPrint = prevState.Merge(newState)
  2867  			} else {
  2868  				prevStates[stateKey] = newState
  2869  				doPrint = true
  2870  			}
  2871  			if doPrint && printSummary {
  2872  				_, _ = fmt.Fprintf(w, waitFormatString, prevStates[stateKey].FormatItems()...)
  2873  			}
  2874  		}
  2875  		_ = w.Flush()
  2876  	}
  2877  	_ = printFinalStatus(appWithLock.GetApp())
  2878  	return nil, finalOperationState, fmt.Errorf("timed out (%ds) waiting for app %q match desired state", timeout, appName)
  2879  }
  2880  
  2881  // setParameterOverrides updates an existing or appends a new parameter override in the application
  2882  // the app is assumed to be a helm app and is expected to be in the form:
  2883  // param=value
  2884  func setParameterOverrides(app *argoappv1.Application, parameters []string, sourcePosition int) {
  2885  	if len(parameters) == 0 {
  2886  		return
  2887  	}
  2888  	source := app.Spec.GetSourcePtrByPosition(sourcePosition)
  2889  	var sourceType argoappv1.ApplicationSourceType
  2890  	if st, _ := source.ExplicitType(); st != nil {
  2891  		sourceType = *st
  2892  	} else if app.Status.SourceType != "" {
  2893  		sourceType = app.Status.SourceType
  2894  	} else if len(strings.SplitN(parameters[0], "=", 2)) == 2 {
  2895  		sourceType = argoappv1.ApplicationSourceTypeHelm
  2896  	}
  2897  
  2898  	switch sourceType {
  2899  	case argoappv1.ApplicationSourceTypeHelm:
  2900  		if source.Helm == nil {
  2901  			source.Helm = &argoappv1.ApplicationSourceHelm{}
  2902  		}
  2903  		for _, p := range parameters {
  2904  			newParam, err := argoappv1.NewHelmParameter(p, false)
  2905  			if err != nil {
  2906  				log.Error(err)
  2907  				continue
  2908  			}
  2909  			source.Helm.AddParameter(*newParam)
  2910  		}
  2911  	default:
  2912  		log.Fatalf("Parameters can only be set against Helm applications")
  2913  	}
  2914  }
  2915  
  2916  // Print list of history ID's for an application.
  2917  func printApplicationHistoryIDs(revHistory []argoappv1.RevisionHistory) {
  2918  	for _, depInfo := range revHistory {
  2919  		fmt.Println(depInfo.ID)
  2920  	}
  2921  }
  2922  
  2923  // Print a history table for an application.
  2924  func printApplicationHistoryTable(revHistory []argoappv1.RevisionHistory) {
  2925  	maxAllowedRevisions := 7
  2926  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
  2927  	type history struct {
  2928  		id       int64
  2929  		date     string
  2930  		revision string
  2931  	}
  2932  	varHistory := map[string][]history{}
  2933  	varHistoryKeys := []string{}
  2934  	for _, depInfo := range revHistory {
  2935  		if depInfo.Sources != nil {
  2936  			for i, sourceInfo := range depInfo.Sources {
  2937  				rev := sourceInfo.TargetRevision
  2938  				if len(depInfo.Revisions) == len(depInfo.Sources) && len(depInfo.Revisions[i]) >= maxAllowedRevisions {
  2939  					rev = fmt.Sprintf("%s (%s)", rev, depInfo.Revisions[i][0:maxAllowedRevisions])
  2940  				}
  2941  				if _, ok := varHistory[sourceInfo.RepoURL]; !ok {
  2942  					varHistoryKeys = append(varHistoryKeys, sourceInfo.RepoURL)
  2943  				}
  2944  				varHistory[sourceInfo.RepoURL] = append(varHistory[sourceInfo.RepoURL], history{
  2945  					id:       depInfo.ID,
  2946  					date:     depInfo.DeployedAt.String(),
  2947  					revision: rev,
  2948  				})
  2949  			}
  2950  		} else {
  2951  			rev := depInfo.Source.TargetRevision
  2952  			if len(depInfo.Revision) >= maxAllowedRevisions {
  2953  				rev = fmt.Sprintf("%s (%s)", rev, depInfo.Revision[0:maxAllowedRevisions])
  2954  			}
  2955  			if _, ok := varHistory[depInfo.Source.RepoURL]; !ok {
  2956  				varHistoryKeys = append(varHistoryKeys, depInfo.Source.RepoURL)
  2957  			}
  2958  			varHistory[depInfo.Source.RepoURL] = append(varHistory[depInfo.Source.RepoURL], history{
  2959  				id:       depInfo.ID,
  2960  				date:     depInfo.DeployedAt.String(),
  2961  				revision: rev,
  2962  			})
  2963  		}
  2964  	}
  2965  	for i, key := range varHistoryKeys {
  2966  		_, _ = fmt.Fprintf(w, "SOURCE\t%s\n", key)
  2967  		_, _ = fmt.Fprintf(w, "ID\tDATE\tREVISION\n")
  2968  		for _, history := range varHistory[key] {
  2969  			_, _ = fmt.Fprintf(w, "%d\t%s\t%s\n", history.id, history.date, history.revision)
  2970  		}
  2971  		// Add a newline if it's not the last iteration
  2972  		if i < len(varHistoryKeys)-1 {
  2973  			_, _ = fmt.Fprintf(w, "\n")
  2974  		}
  2975  	}
  2976  	_ = w.Flush()
  2977  }
  2978  
  2979  // NewApplicationHistoryCommand returns a new instance of an `argocd app history` command
  2980  func NewApplicationHistoryCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  2981  	var (
  2982  		output       string
  2983  		appNamespace string
  2984  	)
  2985  	command := &cobra.Command{
  2986  		Use:   "history APPNAME",
  2987  		Short: "Show application deployment history",
  2988  		Run: func(c *cobra.Command, args []string) {
  2989  			ctx := c.Context()
  2990  
  2991  			if len(args) != 1 {
  2992  				c.HelpFunc()(c, args)
  2993  				os.Exit(1)
  2994  			}
  2995  			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
  2996  			defer utilio.Close(conn)
  2997  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
  2998  			app, err := appIf.Get(ctx, &application.ApplicationQuery{
  2999  				Name:         &appName,
  3000  				AppNamespace: &appNs,
  3001  			})
  3002  			errors.CheckError(err)
  3003  
  3004  			if output == "id" {
  3005  				printApplicationHistoryIDs(app.Status.History)
  3006  			} else {
  3007  				printApplicationHistoryTable(app.Status.History)
  3008  			}
  3009  		},
  3010  	}
  3011  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only show application deployment history in namespace")
  3012  	command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: wide|id")
  3013  	return command
  3014  }
  3015  
  3016  func findRevisionHistory(application *argoappv1.Application, historyId int64) (*argoappv1.RevisionHistory, error) {
  3017  	// in case if history id not passed and need fetch previous history revision
  3018  	if historyId == -1 {
  3019  		l := len(application.Status.History)
  3020  		if l < 2 {
  3021  			return nil, fmt.Errorf("application '%s' should have at least two successful deployments", application.Name)
  3022  		}
  3023  		return &application.Status.History[l-2], nil
  3024  	}
  3025  	for _, di := range application.Status.History {
  3026  		if di.ID == historyId {
  3027  			return &di, nil
  3028  		}
  3029  	}
  3030  	return nil, fmt.Errorf("application '%s' does not have deployment id '%d' in history", application.Name, historyId)
  3031  }
  3032  
  3033  // NewApplicationRollbackCommand returns a new instance of an `argocd app rollback` command
  3034  func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  3035  	var (
  3036  		prune        bool
  3037  		timeout      uint
  3038  		output       string
  3039  		appNamespace string
  3040  	)
  3041  	command := &cobra.Command{
  3042  		Use:   "rollback APPNAME [ID]",
  3043  		Short: "Rollback application to a previous deployed version by History ID, omitted will Rollback to the previous version",
  3044  		Run: func(c *cobra.Command, args []string) {
  3045  			ctx := c.Context()
  3046  			if len(args) == 0 {
  3047  				c.HelpFunc()(c, args)
  3048  				os.Exit(1)
  3049  			}
  3050  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
  3051  			var err error
  3052  			depID := -1
  3053  			if len(args) > 1 {
  3054  				depID, err = strconv.Atoi(args[1])
  3055  				errors.CheckError(err)
  3056  			}
  3057  			acdClient := headless.NewClientOrDie(clientOpts, c)
  3058  			conn, appIf := acdClient.NewApplicationClientOrDie()
  3059  			defer utilio.Close(conn)
  3060  			app, err := appIf.Get(ctx, &application.ApplicationQuery{
  3061  				Name:         &appName,
  3062  				AppNamespace: &appNs,
  3063  			})
  3064  			errors.CheckError(err)
  3065  
  3066  			depInfo, err := findRevisionHistory(app, int64(depID))
  3067  			errors.CheckError(err)
  3068  
  3069  			_, err = appIf.Rollback(ctx, &application.ApplicationRollbackRequest{
  3070  				Name:         &appName,
  3071  				AppNamespace: &appNs,
  3072  				Id:           ptr.To(depInfo.ID),
  3073  				Prune:        ptr.To(prune),
  3074  			})
  3075  			errors.CheckError(err)
  3076  
  3077  			_, _, err = waitOnApplicationStatus(ctx, acdClient, app.QualifiedName(), timeout, watchOpts{
  3078  				operation: true,
  3079  			}, nil, output)
  3080  			errors.CheckError(err)
  3081  		},
  3082  	}
  3083  	command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
  3084  	command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
  3085  	command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed")
  3086  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Rollback application in namespace")
  3087  	return command
  3088  }
  3089  
  3090  const (
  3091  	printOpFmtStr              = "%-20s%s\n"
  3092  	defaultCheckTimeoutSeconds = 0
  3093  )
  3094  
  3095  func printOperationResult(opState *argoappv1.OperationState) {
  3096  	if opState == nil {
  3097  		return
  3098  	}
  3099  	if opState.SyncResult != nil {
  3100  		fmt.Printf(printOpFmtStr, "Operation:", "Sync")
  3101  		if opState.SyncResult.Sources != nil && opState.SyncResult.Revisions != nil {
  3102  			fmt.Printf(printOpFmtStr, "Sync Revision:", strings.Join(opState.SyncResult.Revisions, ", "))
  3103  		} else {
  3104  			fmt.Printf(printOpFmtStr, "Sync Revision:", opState.SyncResult.Revision)
  3105  		}
  3106  	}
  3107  	fmt.Printf(printOpFmtStr, "Phase:", opState.Phase)
  3108  	fmt.Printf(printOpFmtStr, "Start:", opState.StartedAt)
  3109  	fmt.Printf(printOpFmtStr, "Finished:", opState.FinishedAt)
  3110  	var duration time.Duration
  3111  	if !opState.FinishedAt.IsZero() {
  3112  		duration = time.Second * time.Duration(opState.FinishedAt.Unix()-opState.StartedAt.Unix())
  3113  	} else {
  3114  		duration = time.Second * time.Duration(time.Now().UTC().Unix()-opState.StartedAt.Unix())
  3115  	}
  3116  	fmt.Printf(printOpFmtStr, "Duration:", duration)
  3117  	if opState.Message != "" {
  3118  		fmt.Printf(printOpFmtStr, "Message:", opState.Message)
  3119  	}
  3120  }
  3121  
  3122  // NewApplicationManifestsCommand returns a new instance of an `argocd app manifests` command
  3123  func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  3124  	var (
  3125  		source          string
  3126  		revision        string
  3127  		revisions       []string
  3128  		sourcePositions []int64
  3129  		sourceNames     []string
  3130  		local           string
  3131  		localRepoRoot   string
  3132  	)
  3133  	command := &cobra.Command{
  3134  		Use:   "manifests APPNAME",
  3135  		Short: "Print manifests of an application",
  3136  		Example: templates.Examples(`
  3137    # Get manifests for an application
  3138    argocd app manifests my-app
  3139  
  3140    # Get manifests for an application at a specific revision
  3141    argocd app manifests my-app --revision 0.0.1
  3142  
  3143    # Get manifests for a multi-source application at specific revisions for specific sources
  3144    argocd app manifests my-app --revisions 0.0.1 --source-names src-base --revisions 0.0.2 --source-names src-values
  3145  
  3146    # Get manifests for a multi-source application at specific revisions for specific sources
  3147    argocd app manifests my-app --revisions 0.0.1 --source-positions 1 --revisions 0.0.2 --source-positions 2
  3148    		`),
  3149  		Run: func(c *cobra.Command, args []string) {
  3150  			ctx := c.Context()
  3151  
  3152  			if len(args) != 1 {
  3153  				c.HelpFunc()(c, args)
  3154  				os.Exit(1)
  3155  			}
  3156  
  3157  			if len(sourceNames) > 0 && len(sourcePositions) > 0 {
  3158  				errors.Fatal(errors.ErrorGeneric, "Only one of source-positions and source-names can be specified.")
  3159  			}
  3160  
  3161  			if len(sourcePositions) > 0 && len(revisions) != len(sourcePositions) {
  3162  				errors.Fatal(errors.ErrorGeneric, "While using --revisions and --source-positions, length of values for both flags should be same.")
  3163  			}
  3164  
  3165  			if len(sourceNames) > 0 && len(revisions) != len(sourceNames) {
  3166  				errors.Fatal(errors.ErrorGeneric, "While using --revisions and --source-names, length of values for both flags should be same.")
  3167  			}
  3168  
  3169  			for _, pos := range sourcePositions {
  3170  				if pos <= 0 {
  3171  					log.Fatal("source-position cannot be less than or equal to 0, Counting starts at 1")
  3172  				}
  3173  			}
  3174  
  3175  			appName, appNs := argo.ParseFromQualifiedName(args[0], "")
  3176  			clientset := headless.NewClientOrDie(clientOpts, c)
  3177  			conn, appIf := clientset.NewApplicationClientOrDie()
  3178  			defer utilio.Close(conn)
  3179  
  3180  			app, err := appIf.Get(context.Background(), &application.ApplicationQuery{
  3181  				Name:         &appName,
  3182  				AppNamespace: &appNs,
  3183  			})
  3184  			errors.CheckError(err)
  3185  
  3186  			if len(sourceNames) > 0 {
  3187  				sourceNameToPosition := getSourceNameToPositionMap(app)
  3188  
  3189  				for _, name := range sourceNames {
  3190  					pos, ok := sourceNameToPosition[name]
  3191  					if !ok {
  3192  						log.Fatalf("Unknown source name '%s'", name)
  3193  					}
  3194  					sourcePositions = append(sourcePositions, pos)
  3195  				}
  3196  			}
  3197  
  3198  			resources, err := appIf.ManagedResources(ctx, &application.ResourcesQuery{
  3199  				ApplicationName: &appName,
  3200  				AppNamespace:    &appNs,
  3201  			})
  3202  			errors.CheckError(err)
  3203  
  3204  			var unstructureds []*unstructured.Unstructured
  3205  			switch source {
  3206  			case "git":
  3207  				switch {
  3208  				case local != "":
  3209  					settingsConn, settingsIf := clientset.NewSettingsClientOrDie()
  3210  					defer utilio.Close(settingsConn)
  3211  					argoSettings, err := settingsIf.Get(context.Background(), &settings.SettingsQuery{})
  3212  					errors.CheckError(err)
  3213  
  3214  					clusterConn, clusterIf := clientset.NewClusterClientOrDie()
  3215  					defer utilio.Close(clusterConn)
  3216  					cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
  3217  					errors.CheckError(err)
  3218  
  3219  					proj := getProject(ctx, c, clientOpts, app.Spec.Project)
  3220  					//nolint:staticcheck
  3221  					unstructureds = getLocalObjects(context.Background(), app, proj.Project, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.TrackingMethod)
  3222  				case len(revisions) > 0 && len(sourcePositions) > 0:
  3223  					q := application.ApplicationManifestQuery{
  3224  						Name:            &appName,
  3225  						AppNamespace:    &appNs,
  3226  						Revision:        ptr.To(revision),
  3227  						Revisions:       revisions,
  3228  						SourcePositions: sourcePositions,
  3229  					}
  3230  					res, err := appIf.GetManifests(ctx, &q)
  3231  					errors.CheckError(err)
  3232  
  3233  					for _, mfst := range res.Manifests {
  3234  						obj, err := argoappv1.UnmarshalToUnstructured(mfst)
  3235  						errors.CheckError(err)
  3236  						unstructureds = append(unstructureds, obj)
  3237  					}
  3238  				case revision != "":
  3239  					q := application.ApplicationManifestQuery{
  3240  						Name:         &appName,
  3241  						AppNamespace: &appNs,
  3242  						Revision:     ptr.To(revision),
  3243  					}
  3244  					res, err := appIf.GetManifests(ctx, &q)
  3245  					errors.CheckError(err)
  3246  
  3247  					for _, mfst := range res.Manifests {
  3248  						obj, err := argoappv1.UnmarshalToUnstructured(mfst)
  3249  						errors.CheckError(err)
  3250  						unstructureds = append(unstructureds, obj)
  3251  					}
  3252  				default:
  3253  					targetObjs, err := targetObjects(resources.Items)
  3254  					errors.CheckError(err)
  3255  					unstructureds = targetObjs
  3256  				}
  3257  			case "live":
  3258  				liveObjs, err := cmdutil.LiveObjects(resources.Items)
  3259  				errors.CheckError(err)
  3260  				unstructureds = liveObjs
  3261  			default:
  3262  				log.Fatalf("Unknown source type '%s'", source)
  3263  			}
  3264  
  3265  			for _, obj := range unstructureds {
  3266  				fmt.Println("---")
  3267  				yamlBytes, err := yaml.Marshal(obj)
  3268  				errors.CheckError(err)
  3269  				fmt.Printf("%s\n", yamlBytes)
  3270  			}
  3271  		},
  3272  	}
  3273  	command.Flags().StringVar(&source, "source", "git", "Source of manifests. One of: live|git")
  3274  	command.Flags().StringVar(&revision, "revision", "", "Show manifests at a specific revision")
  3275  	command.Flags().StringArrayVar(&revisions, "revisions", []string{}, "Show manifests at specific revisions for the source at position in source-positions")
  3276  	command.Flags().Int64SliceVar(&sourcePositions, "source-positions", []int64{}, "List of source positions. Default is empty array. Counting start at 1.")
  3277  	command.Flags().StringArrayVar(&sourceNames, "source-names", []string{}, "List of source names. Default is an empty array.")
  3278  	command.Flags().StringVar(&local, "local", "", "If set, show locally-generated manifests. Value is the absolute path to app manifests within the manifest repo. Example: '/home/username/apps/env/app-1'.")
  3279  	command.Flags().StringVar(&localRepoRoot, "local-repo-root", ".", "Path to the local repository root. Used together with --local allows setting the repository root. Example: '/home/username/apps'.")
  3280  	return command
  3281  }
  3282  
  3283  // NewApplicationTerminateOpCommand returns a new instance of an `argocd app terminate-op` command
  3284  func NewApplicationTerminateOpCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  3285  	command := &cobra.Command{
  3286  		Use:   "terminate-op APPNAME",
  3287  		Short: "Terminate running operation of an application",
  3288  		Run: func(c *cobra.Command, args []string) {
  3289  			ctx := c.Context()
  3290  
  3291  			if len(args) != 1 {
  3292  				c.HelpFunc()(c, args)
  3293  				os.Exit(1)
  3294  			}
  3295  			appName, appNs := argo.ParseFromQualifiedName(args[0], "")
  3296  			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
  3297  			defer utilio.Close(conn)
  3298  			_, err := appIf.TerminateOperation(ctx, &application.OperationTerminateRequest{
  3299  				Name:         &appName,
  3300  				AppNamespace: &appNs,
  3301  			})
  3302  			errors.CheckError(err)
  3303  			fmt.Printf("Application '%s' operation terminating\n", appName)
  3304  		},
  3305  	}
  3306  	return command
  3307  }
  3308  
  3309  func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  3310  	var appNamespace string
  3311  	command := &cobra.Command{
  3312  		Use:   "edit APPNAME",
  3313  		Short: "Edit application",
  3314  		Run: func(c *cobra.Command, args []string) {
  3315  			ctx := c.Context()
  3316  
  3317  			if len(args) != 1 {
  3318  				c.HelpFunc()(c, args)
  3319  				os.Exit(1)
  3320  			}
  3321  
  3322  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
  3323  			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
  3324  			defer utilio.Close(conn)
  3325  			app, err := appIf.Get(ctx, &application.ApplicationQuery{
  3326  				Name:         &appName,
  3327  				AppNamespace: &appNs,
  3328  			})
  3329  			errors.CheckError(err)
  3330  
  3331  			appData, err := json.Marshal(app.Spec)
  3332  			errors.CheckError(err)
  3333  			appData, err = yaml.JSONToYAML(appData)
  3334  			errors.CheckError(err)
  3335  
  3336  			cli.InteractiveEdit(appName+"-*-edit.yaml", appData, func(input []byte) error {
  3337  				input, err = yaml.YAMLToJSON(input)
  3338  				if err != nil {
  3339  					return fmt.Errorf("error converting YAML to JSON: %w", err)
  3340  				}
  3341  				updatedSpec := argoappv1.ApplicationSpec{}
  3342  				err = json.Unmarshal(input, &updatedSpec)
  3343  				if err != nil {
  3344  					return fmt.Errorf("error unmarshaling input into application spec: %w", err)
  3345  				}
  3346  
  3347  				var appOpts cmdutil.AppOptions
  3348  
  3349  				// do not allow overrides for applications with multiple sources
  3350  				if !app.Spec.HasMultipleSources() {
  3351  					cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, 0)
  3352  				}
  3353  				_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
  3354  					Name:         &appName,
  3355  					Spec:         &updatedSpec,
  3356  					Validate:     &appOpts.Validate,
  3357  					AppNamespace: &appNs,
  3358  				})
  3359  				if err != nil {
  3360  					return fmt.Errorf("failed to update application spec: %w", err)
  3361  				}
  3362  				return nil
  3363  			})
  3364  		},
  3365  	}
  3366  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only edit application in namespace")
  3367  	return command
  3368  }
  3369  
  3370  func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  3371  	var (
  3372  		patch        string
  3373  		patchType    string
  3374  		appNamespace string
  3375  	)
  3376  
  3377  	command := cobra.Command{
  3378  		Use:   "patch APPNAME",
  3379  		Short: "Patch application",
  3380  		Example: `  # Update an application's source path using json patch
  3381    argocd app patch myapplication --patch='[{"op": "replace", "path": "/spec/source/path", "value": "newPath"}]' --type json
  3382  
  3383    # Update an application's repository target revision using merge patch
  3384    argocd app patch myapplication --patch '{"spec": { "source": { "targetRevision": "master" } }}' --type merge`,
  3385  		Run: func(c *cobra.Command, args []string) {
  3386  			ctx := c.Context()
  3387  
  3388  			if len(args) != 1 {
  3389  				c.HelpFunc()(c, args)
  3390  				os.Exit(1)
  3391  			}
  3392  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
  3393  			conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
  3394  			defer utilio.Close(conn)
  3395  
  3396  			patchedApp, err := appIf.Patch(ctx, &application.ApplicationPatchRequest{
  3397  				Name:         &appName,
  3398  				Patch:        &patch,
  3399  				PatchType:    &patchType,
  3400  				AppNamespace: &appNs,
  3401  			})
  3402  			errors.CheckError(err)
  3403  
  3404  			yamlBytes, err := yaml.Marshal(patchedApp)
  3405  			errors.CheckError(err)
  3406  
  3407  			fmt.Println(string(yamlBytes))
  3408  		},
  3409  	}
  3410  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only patch application in namespace")
  3411  	command.Flags().StringVar(&patch, "patch", "", "Patch body")
  3412  	command.Flags().StringVar(&patchType, "type", "json", "The type of patch being provided; one of [json merge]")
  3413  	return &command
  3414  }
  3415  
  3416  // NewApplicationAddSourceCommand returns a new instance of an `argocd app add-source` command
  3417  func NewApplicationAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  3418  	var (
  3419  		appOpts      cmdutil.AppOptions
  3420  		appNamespace string
  3421  	)
  3422  	command := &cobra.Command{
  3423  		Use:   "add-source APPNAME",
  3424  		Short: "Adds a source to the list of sources in the application",
  3425  		Example: `  # Append a source to the list of sources in the application
  3426    argocd app add-source guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --source-name guestbook`,
  3427  		Run: func(c *cobra.Command, args []string) {
  3428  			ctx := c.Context()
  3429  			if len(args) != 1 {
  3430  				c.HelpFunc()(c, args)
  3431  				os.Exit(1)
  3432  			}
  3433  
  3434  			argocdClient := headless.NewClientOrDie(clientOpts, c)
  3435  			conn, appIf := argocdClient.NewApplicationClientOrDie()
  3436  			defer utilio.Close(conn)
  3437  
  3438  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
  3439  
  3440  			app, err := appIf.Get(ctx, &application.ApplicationQuery{
  3441  				Name:         &appName,
  3442  				Refresh:      getRefreshType(false, false),
  3443  				AppNamespace: &appNs,
  3444  			})
  3445  
  3446  			errors.CheckError(err)
  3447  
  3448  			if c.Flags() == nil {
  3449  				errors.Fatal(errors.ErrorGeneric, "ApplicationSource needs atleast repoUrl, path or chart or ref field. No source to add.")
  3450  			}
  3451  
  3452  			if len(app.Spec.Sources) > 0 {
  3453  				appSource, _ := cmdutil.ConstructSource(&argoappv1.ApplicationSource{}, appOpts, c.Flags())
  3454  
  3455  				// sourcePosition is the index at which new source will be appended to spec.Sources
  3456  				sourcePosition := len(app.Spec.GetSources())
  3457  				app.Spec.Sources = append(app.Spec.Sources, *appSource)
  3458  
  3459  				setParameterOverrides(app, appOpts.Parameters, sourcePosition)
  3460  
  3461  				_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
  3462  					Name:         &app.Name,
  3463  					Spec:         &app.Spec,
  3464  					Validate:     &appOpts.Validate,
  3465  					AppNamespace: &appNs,
  3466  				})
  3467  				errors.CheckError(err)
  3468  
  3469  				fmt.Printf("Application '%s' updated successfully\n", app.Name)
  3470  			} else {
  3471  				errors.Fatal(errors.ErrorGeneric, fmt.Sprintf("Cannot add source: application %s does not have spec.sources defined", appName))
  3472  			}
  3473  		},
  3474  	}
  3475  	cmdutil.AddAppFlags(command, &appOpts)
  3476  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace of the target application where the source will be appended")
  3477  	return command
  3478  }
  3479  
  3480  // NewApplicationRemoveSourceCommand returns a new instance of an `argocd app remove-source` command
  3481  func NewApplicationRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  3482  	var (
  3483  		sourcePosition int
  3484  		sourceName     string
  3485  		appNamespace   string
  3486  	)
  3487  	command := &cobra.Command{
  3488  		Use:   "remove-source APPNAME",
  3489  		Short: "Remove a source from multiple sources application.",
  3490  		Example: `  # Remove the source at position 1 from application's sources. Counting starts at 1.
  3491    argocd app remove-source myapplication --source-position 1
  3492  
  3493    # Remove the source named "test" from application's sources.
  3494    argocd app remove-source myapplication --source-name test`,
  3495  		Run: func(c *cobra.Command, args []string) {
  3496  			ctx := c.Context()
  3497  
  3498  			if len(args) != 1 {
  3499  				c.HelpFunc()(c, args)
  3500  				os.Exit(1)
  3501  			}
  3502  
  3503  			if sourceName == "" && sourcePosition <= 0 {
  3504  				errors.Fatal(errors.ErrorGeneric, "Value of source-position must be greater than 0")
  3505  			}
  3506  
  3507  			argocdClient := headless.NewClientOrDie(clientOpts, c)
  3508  			conn, appIf := argocdClient.NewApplicationClientOrDie()
  3509  			defer utilio.Close(conn)
  3510  
  3511  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
  3512  
  3513  			app, err := appIf.Get(ctx, &application.ApplicationQuery{
  3514  				Name:         &appName,
  3515  				Refresh:      getRefreshType(false, false),
  3516  				AppNamespace: &appNs,
  3517  			})
  3518  			errors.CheckError(err)
  3519  
  3520  			if sourceName != "" && sourcePosition != -1 {
  3521  				errors.Fatal(errors.ErrorGeneric, "Only one of source-position and source-name can be specified.")
  3522  			}
  3523  
  3524  			if sourceName != "" {
  3525  				sourceNameToPosition := getSourceNameToPositionMap(app)
  3526  				pos, ok := sourceNameToPosition[sourceName]
  3527  				if !ok {
  3528  					log.Fatalf("Unknown source name '%s'", sourceName)
  3529  				}
  3530  				sourcePosition = int(pos)
  3531  			}
  3532  
  3533  			if !app.Spec.HasMultipleSources() {
  3534  				errors.Fatal(errors.ErrorGeneric, "Application does not have multiple sources configured")
  3535  			}
  3536  
  3537  			if len(app.Spec.GetSources()) == 1 {
  3538  				errors.Fatal(errors.ErrorGeneric, "Cannot remove the only source remaining in the app")
  3539  			}
  3540  
  3541  			if len(app.Spec.GetSources()) < sourcePosition {
  3542  				errors.Fatal(errors.ErrorGeneric, fmt.Sprintf("Application does not have source at %d\n", sourcePosition))
  3543  			}
  3544  
  3545  			app.Spec.Sources = append(app.Spec.Sources[:sourcePosition-1], app.Spec.Sources[sourcePosition:]...)
  3546  
  3547  			promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
  3548  			canDelete := promptUtil.Confirm("Are you sure you want to delete the source? [y/n]")
  3549  			if canDelete {
  3550  				_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
  3551  					Name:         &app.Name,
  3552  					Spec:         &app.Spec,
  3553  					AppNamespace: &appNs,
  3554  				})
  3555  				errors.CheckError(err)
  3556  
  3557  				fmt.Printf("Application '%s' updated successfully\n", app.Name)
  3558  			} else {
  3559  				fmt.Println("The command to delete the source was cancelled")
  3560  			}
  3561  		},
  3562  	}
  3563  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace of the target application where the source will be appended")
  3564  	command.Flags().IntVar(&sourcePosition, "source-position", -1, "Position of the source from the list of sources of the app. Counting starts at 1.")
  3565  	command.Flags().StringVar(&sourceName, "source-name", "", "Name of the source from the list of sources of the app.")
  3566  	return command
  3567  }
  3568  
  3569  func NewApplicationConfirmDeletionCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
  3570  	var appNamespace string
  3571  	command := &cobra.Command{
  3572  		Use:   "confirm-deletion APPNAME",
  3573  		Short: "Confirms deletion/pruning of an application resources",
  3574  		Run: func(c *cobra.Command, args []string) {
  3575  			ctx := c.Context()
  3576  
  3577  			if len(args) != 1 {
  3578  				c.HelpFunc()(c, args)
  3579  				os.Exit(1)
  3580  			}
  3581  
  3582  			argocdClient := headless.NewClientOrDie(clientOpts, c)
  3583  			conn, appIf := argocdClient.NewApplicationClientOrDie()
  3584  			defer utilio.Close(conn)
  3585  
  3586  			appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
  3587  
  3588  			app, err := appIf.Get(ctx, &application.ApplicationQuery{
  3589  				Name:         &appName,
  3590  				Refresh:      getRefreshType(false, false),
  3591  				AppNamespace: &appNs,
  3592  			})
  3593  			errors.CheckError(err)
  3594  
  3595  			annotations := app.Annotations
  3596  			if annotations == nil {
  3597  				annotations = map[string]string{}
  3598  				app.Annotations = annotations
  3599  			}
  3600  			annotations[common.AnnotationDeletionApproved] = metav1.Now().Format(time.RFC3339)
  3601  
  3602  			_, err = appIf.Update(ctx, &application.ApplicationUpdateRequest{
  3603  				Application: app,
  3604  				Validate:    ptr.To(false),
  3605  				Project:     &app.Spec.Project,
  3606  			})
  3607  			errors.CheckError(err)
  3608  
  3609  			fmt.Printf("Application '%s' updated successfully\n", app.Name)
  3610  		},
  3611  	}
  3612  	command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace of the target application where the source will be appended")
  3613  	return command
  3614  }
  3615  
  3616  // prepareObjectsForDiff prepares objects for diffing using the switch statement
  3617  // to handle different diff options and building the objKeyLiveTarget items
  3618  func prepareObjectsForDiff(ctx context.Context, app *argoappv1.Application, proj *argoappv1.AppProject, resources *application.ManagedResourcesResponse, argoSettings *settings.Settings, diffOptions *DifferenceOption) ([]objKeyLiveTarget, error) {
  3619  	liveObjs, err := cmdutil.LiveObjects(resources.Items)
  3620  	if err != nil {
  3621  		return nil, err
  3622  	}
  3623  	items := make([]objKeyLiveTarget, 0)
  3624  
  3625  	switch {
  3626  	case diffOptions.local != "":
  3627  		localObjs := groupObjsByKey(getLocalObjects(ctx, app, proj, diffOptions.local, diffOptions.localRepoRoot, argoSettings.AppLabelKey, diffOptions.cluster.Info.ServerVersion, diffOptions.cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.TrackingMethod), liveObjs, app.Spec.Destination.Namespace)
  3628  		items = groupObjsForDiff(resources, localObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace), app.Spec.Destination.Namespace)
  3629  	case diffOptions.revision != "" || len(diffOptions.revisions) > 0:
  3630  		var unstructureds []*unstructured.Unstructured
  3631  		for _, mfst := range diffOptions.res.Manifests {
  3632  			obj, err := argoappv1.UnmarshalToUnstructured(mfst)
  3633  			if err != nil {
  3634  				return nil, err
  3635  			}
  3636  			unstructureds = append(unstructureds, obj)
  3637  		}
  3638  		groupedObjs := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace)
  3639  		items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace), app.Spec.Destination.Namespace)
  3640  	case diffOptions.serversideRes != nil:
  3641  		var unstructureds []*unstructured.Unstructured
  3642  		for _, mfst := range diffOptions.serversideRes.Manifests {
  3643  			obj, err := argoappv1.UnmarshalToUnstructured(mfst)
  3644  			if err != nil {
  3645  				return nil, err
  3646  			}
  3647  			unstructureds = append(unstructureds, obj)
  3648  		}
  3649  		groupedObjs := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace)
  3650  		items = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.InstanceName(argoSettings.ControllerNamespace), app.Spec.Destination.Namespace)
  3651  	default:
  3652  		for i := range resources.Items {
  3653  			res := resources.Items[i]
  3654  			live := &unstructured.Unstructured{}
  3655  			err := json.Unmarshal([]byte(res.NormalizedLiveState), &live)
  3656  			if err != nil {
  3657  				return nil, err
  3658  			}
  3659  
  3660  			target := &unstructured.Unstructured{}
  3661  			err = json.Unmarshal([]byte(res.TargetState), &target)
  3662  			if err != nil {
  3663  				return nil, err
  3664  			}
  3665  
  3666  			items = append(items, objKeyLiveTarget{kube.NewResourceKey(res.Group, res.Kind, res.Namespace, res.Name), live, target})
  3667  		}
  3668  	}
  3669  
  3670  	return items, nil
  3671  }