From 17a2eb25ccb14adf36d7e76bd0893439a168d8fe Mon Sep 17 00:00:00 2001 From: schakrad <58915923+schakrad@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:45:57 -0700 Subject: [PATCH] feat(cli): tree option in output flag for app sync, app wait and app rollback for tree view (#15572) * app sync and app wait tree view changes Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> * documentation changes Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> * included the json,yaml and wide formats and removed the value assignment to the flag Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> * Reoved extra spaces Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> * removed extra spaces Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> * refactored the code Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> * better log statements Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> --------- Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> --- cmd/argocd/commands/app.go | 88 +++++++++++++------ cmd/argocd/commands/app_resources.go | 1 - cmd/argocd/commands/tree.go | 3 +- .../commands/argocd_app_rollback.md | 7 +- docs/user-guide/commands/argocd_app_sync.md | 1 + docs/user-guide/commands/argocd_app_wait.md | 1 + 6 files changed, 68 insertions(+), 33 deletions(-) diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index 2559c702b5ddc..20d80fb6b2835 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -269,7 +269,6 @@ func parentChildDetails(appIf application.ApplicationServiceClient, ctx context. errors.CheckError(err) for _, node := range resourceTree.Nodes { - mapUidToNode[node.UID] = node if len(node.ParentRefs) > 0 { @@ -282,7 +281,6 @@ func parentChildDetails(appIf application.ApplicationServiceClient, ctx context. } else { parentNode[node.UID] = struct{}{} } - } return mapUidToNode, mapParentToChild, parentNode } @@ -316,13 +314,11 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com showParams bool showOperation bool ) - var command = &cobra.Command{ Use: "get APPNAME", Short: "Get application details", Run: func(c *cobra.Command, args []string) { ctx := c.Context() - output, _ = c.Flags().GetString("output") if len(args) == 0 { c.HelpFunc()(c, args) os.Exit(1) @@ -362,22 +358,14 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com } case "tree": printHeader(acdClient, app, ctx, windows, showOperation, showParams) - mapUidToNode, mapParentToChild, parentNode := parentChildDetails(appIf, ctx, appName, appNs) - mapNodeNameToResourceState := make(map[string]*resourceState) - for _, res := range getResourceStates(app, nil) { - mapNodeNameToResourceState[res.Kind+"/"+res.Name] = res - } + mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState := resourceParentChild(acdClient, ctx, appName, appNs, app) if len(mapUidToNode) > 0 { fmt.Println() printTreeView(mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState) } case "tree=detailed": printHeader(acdClient, app, ctx, windows, showOperation, showParams) - mapUidToNode, mapParentToChild, parentNode := parentChildDetails(appIf, ctx, appName, appNs) - mapNodeNameToResourceState := make(map[string]*resourceState) - for _, res := range getResourceStates(app, nil) { - mapNodeNameToResourceState[res.Kind+"/"+res.Name] = res - } + mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState := resourceParentChild(acdClient, ctx, appName, appNs, app) if len(mapUidToNode) > 0 { fmt.Println() printTreeViewDetailed(mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState) @@ -1506,6 +1494,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co timeout uint selector string resources []string + output string ) var command = &cobra.Command{ Use: "wait [APPNAME.. | -l selector]", @@ -1554,7 +1543,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co } } for _, appName := range appNames { - _, _, err := waitOnApplicationStatus(ctx, acdClient, appName, timeout, watch, selectedResources) + _, _, err := waitOnApplicationStatus(ctx, acdClient, appName, timeout, watch, selectedResources, output) errors.CheckError(err) } }, @@ -1567,6 +1556,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co 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)) command.Flags().BoolVar(&watch.operation, "operation", false, "Wait for pending operations") command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds") + command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed") return command } @@ -1622,6 +1612,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co diffChanges bool diffChangesConfirm bool projects []string + output string ) var command = &cobra.Command{ Use: "sync [APPNAME... | -l selector | --project project-name]", @@ -1650,17 +1641,13 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co argocd app sync my-app --resource argoproj.io:Rollout:my-namespace/my-rollout`, Run: func(c *cobra.Command, args []string) { ctx := c.Context() - if len(args) == 0 && selector == "" && len(projects) == 0 { - c.HelpFunc()(c, args) os.Exit(1) } - if len(args) > 1 && selector != "" { log.Fatal("Cannot use selector option when application name(s) passed as argument(s)") } - acdClient := headless.NewClientOrDie(clientOpts, c) conn, appIf := acdClient.NewApplicationClientOrDie() defer argoio.Close(conn) @@ -1705,6 +1692,8 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co log.Fatal(err) } + fmt.Println("The name of the app is ", appName) + for _, mfst := range res.Manifests { obj, err := argoappv1.UnmarshalToUnstructured(mfst) errors.CheckError(err) @@ -1864,7 +1853,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co errors.CheckError(err) if !async { - app, opState, err := waitOnApplicationStatus(ctx, acdClient, appQualifiedName, timeout, watchOpts{operation: true}, selectedResources) + app, opState, err := waitOnApplicationStatus(ctx, acdClient, appQualifiedName, timeout, watchOpts{operation: true}, selectedResources, output) errors.CheckError(err) if !dryRun { @@ -1905,6 +1894,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co command.Flags().BoolVar(&diffChangesConfirm, "assumeYes", false, "Assume yes as answer for all user queries or prompts") command.Flags().BoolVar(&diffChanges, "preview-changes", false, "Preview difference against the target and live state before syncing app and wait for user confirmation") command.Flags().StringArrayVar(&projects, "project", []string{}, "Sync apps that belong to the specified projects. This option may be specified repeatedly.") + command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed") return command } @@ -2084,13 +2074,22 @@ func checkResourceStatus(watch watchOpts, healthStatus string, syncStatus string operational := !watch.operation || operationStatus == nil return synced && healthCheckPassed && operational } +func resourceParentChild(acdClient argocdclient.Client, ctx context.Context, appName string, appNs string, app *argoappv1.Application) (map[string]argoappv1.ResourceNode, map[string][]string, map[string]struct{}, map[string]*resourceState) { + _, appIf := acdClient.NewApplicationClientOrDie() + mapUidToNode, mapParentToChild, parentNode := parentChildDetails(appIf, ctx, appName, appNs) + mapNodeNameToResourceState := make(map[string]*resourceState) + for _, res := range getResourceStates(app, nil) { + mapNodeNameToResourceState[res.Kind+"/"+res.Name] = res + } + return mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState +} const waitFormatString = "%s\t%5s\t%10s\t%10s\t%20s\t%8s\t%7s\t%10s\t%s\n" // waitOnApplicationStatus watches an application and blocks until either the desired watch conditions // are fulfiled or we reach the timeout. Returns the app once desired conditions have been filled. // Additionally return the operationState at time of fulfilment (which may be different than returned app). -func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client, appName string, timeout uint, watch watchOpts, selectedResources []*argoappv1.SyncOperationResource) (*argoappv1.Application, *argoappv1.OperationState, error) { +func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client, appName string, timeout uint, watch watchOpts, selectedResources []*argoappv1.SyncOperationResource, output string) (*argoappv1.Application, *argoappv1.OperationState, error) { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -2122,19 +2121,51 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client, printOperationResult(app.Status.OperationState) } - if len(app.Status.Resources) > 0 { - fmt.Println() - w := tabwriter.NewWriter(os.Stdout, 5, 0, 2, ' ', 0) - printAppResources(w, app) - _ = w.Flush() + switch output { + case "yaml", "json": + err := PrintResource(app, output) + errors.CheckError(err) + case "wide", "": + if len(app.Status.Resources) > 0 { + fmt.Println() + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + printAppResources(w, app) + _ = w.Flush() + } + case "tree": + mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState := resourceParentChild(acdClient, ctx, appName, appNs, app) + if len(mapUidToNode) > 0 { + fmt.Println() + printTreeView(mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState) + } + case "tree=detailed": + mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState := resourceParentChild(acdClient, ctx, appName, appNs, app) + if len(mapUidToNode) > 0 { + fmt.Println() + printTreeViewDetailed(mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState) + } + default: + errors.CheckError(fmt.Errorf("unknown output format: %s", output)) } return app } if timeout != 0 { time.AfterFunc(time.Duration(timeout)*time.Second, func() { + _, appClient := acdClient.NewApplicationClientOrDie() + app, err := appClient.Get(ctx, &application.ApplicationQuery{ + Name: &appRealName, + AppNamespace: &appNs, + }) + errors.CheckError(err) + fmt.Println() + fmt.Println("This is the state of the app after `wait` timed out:") + printFinalStatus(app) cancel() + fmt.Println() + fmt.Println("The command timed out waiting for the conditions to be met") }) + } w := tabwriter.NewWriter(os.Stdout, 5, 0, 2, ' ', 0) @@ -2341,13 +2372,13 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr var ( prune bool timeout uint + output string ) var command = &cobra.Command{ Use: "rollback APPNAME [ID]", Short: "Rollback application to a previous deployed version by History ID, omitted will Rollback to the previous version", Run: func(c *cobra.Command, args []string) { ctx := c.Context() - if len(args) == 0 { c.HelpFunc()(c, args) os.Exit(1) @@ -2381,12 +2412,13 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr _, _, err = waitOnApplicationStatus(ctx, acdClient, app.QualifiedName(), timeout, watchOpts{ operation: true, - }, nil) + }, nil, output) errors.CheckError(err) }, } command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources") command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds") + command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed") return command } diff --git a/cmd/argocd/commands/app_resources.go b/cmd/argocd/commands/app_resources.go index f7c8ecdbe12bf..e48465c7e4693 100644 --- a/cmd/argocd/commands/app_resources.go +++ b/cmd/argocd/commands/app_resources.go @@ -255,7 +255,6 @@ func NewApplicationListResourcesCommand(clientOpts *argocdclient.ClientOptions) Short: "List resource of application", Run: func(c *cobra.Command, args []string) { ctx := c.Context() - output, _ = c.Flags().GetString("output") if len(args) != 1 { c.HelpFunc()(c, args) os.Exit(1) diff --git a/cmd/argocd/commands/tree.go b/cmd/argocd/commands/tree.go index d46e5375df130..5261adb5b7f4a 100644 --- a/cmd/argocd/commands/tree.go +++ b/cmd/argocd/commands/tree.go @@ -27,11 +27,12 @@ func extractHealthStatusAndReason(node v1alpha1.ResourceNode) (healthStatus heal } func treeViewAppGet(prefix string, uidToNodeMap map[string]v1alpha1.ResourceNode, parentToChildMap map[string][]string, parent v1alpha1.ResourceNode, mapNodeNameToResourceState map[string]*resourceState, w *tabwriter.Writer) { + healthStatus, _ := extractHealthStatusAndReason(parent) if mapNodeNameToResourceState[parent.Kind+"/"+parent.Name] != nil { value := mapNodeNameToResourceState[parent.Kind+"/"+parent.Name] _, _ = fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\n", printPrefix(prefix), parent.Kind+"/"+value.Name, value.Status, value.Health, value.Message) } else { - _, _ = fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\n", printPrefix(prefix), parent.Kind+"/"+parent.Name, "", "", "") + _, _ = fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\n", printPrefix(prefix), parent.Kind+"/"+parent.Name, "", healthStatus, "") } chs := parentToChildMap[parent.UID] for i, childUid := range chs { diff --git a/docs/user-guide/commands/argocd_app_rollback.md b/docs/user-guide/commands/argocd_app_rollback.md index 5f9e643af10e1..bfcbf89631854 100644 --- a/docs/user-guide/commands/argocd_app_rollback.md +++ b/docs/user-guide/commands/argocd_app_rollback.md @@ -11,9 +11,10 @@ argocd app rollback APPNAME [ID] [flags] ### Options ``` - -h, --help help for rollback - --prune Allow deleting unexpected resources - --timeout uint Time out after this many seconds + -h, --help help for rollback + -o, --output string Output format. One of: json|yaml|wide|tree|tree=detailed (default "wide") + --prune Allow deleting unexpected resources + --timeout uint Time out after this many seconds ``` ### Options inherited from parent commands diff --git a/docs/user-guide/commands/argocd_app_sync.md b/docs/user-guide/commands/argocd_app_sync.md index d6c06e6b05921..81ce3fd024c5c 100644 --- a/docs/user-guide/commands/argocd_app_sync.md +++ b/docs/user-guide/commands/argocd_app_sync.md @@ -48,6 +48,7 @@ argocd app sync [APPNAME... | -l selector | --project project-name] [flags] --label stringArray Sync only specific resources with a label. This option may be specified repeatedly. --local string Path to a local directory. When this flag is present no git queries will be made --local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/") + -o, --output string Output format. One of: json|yaml|wide|tree|tree=detailed (default "wide") --preview-changes Preview difference against the target and live state before syncing app and wait for user confirmation --project stringArray Sync apps that belong to the specified projects. This option may be specified repeatedly. --prune Allow deleting unexpected resources diff --git a/docs/user-guide/commands/argocd_app_wait.md b/docs/user-guide/commands/argocd_app_wait.md index 5287960fe3006..99e422167b76f 100644 --- a/docs/user-guide/commands/argocd_app_wait.md +++ b/docs/user-guide/commands/argocd_app_wait.md @@ -42,6 +42,7 @@ argocd app wait [APPNAME.. | -l selector] [flags] --health Wait for health -h, --help help for wait --operation Wait for pending operations + -o, --output string Output format. One of: json|yaml|wide|tree|tree=detailed (default "wide") --resource stringArray Sync only specific resources as GROUP:KIND:NAME or !GROUP:KIND:NAME. Fields may be blank and '*' can be used. This option may be specified repeatedly -l, --selector string Wait for apps by label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints. --suspended Wait for suspended