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 }