Skip to content

Commit

Permalink
feat(cli): tree option in output flag for app sync, app wait and app …
Browse files Browse the repository at this point in the history
…rollback for tree view (argoproj#15572)

* app sync and app wait tree view changes

Signed-off-by: schakrad <[email protected]>

* documentation changes

Signed-off-by: schakrad <[email protected]>

* included the json,yaml and wide formats and removed the value assignment to the flag

Signed-off-by: schakrad <[email protected]>

* Reoved extra spaces

Signed-off-by: schakrad <[email protected]>

* removed extra spaces

Signed-off-by: schakrad <[email protected]>

* refactored the code

Signed-off-by: schakrad <[email protected]>

* better log statements

Signed-off-by: schakrad <[email protected]>

---------

Signed-off-by: schakrad <[email protected]>
  • Loading branch information
schakrad authored and lukaszgyg committed Jan 12, 2024
1 parent 1a5017a commit 17a2eb2
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 33 deletions.
88 changes: 60 additions & 28 deletions cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -282,7 +281,6 @@ func parentChildDetails(appIf application.ApplicationServiceClient, ctx context.
} else {
parentNode[node.UID] = struct{}{}
}

}
return mapUidToNode, mapParentToChild, parentNode
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]",
Expand Down Expand Up @@ -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)
}
},
Expand All @@ -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
}

Expand Down Expand Up @@ -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]",
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
1 change: 0 additions & 1 deletion cmd/argocd/commands/app_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion cmd/argocd/commands/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 4 additions & 3 deletions docs/user-guide/commands/argocd_app_rollback.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/user-guide/commands/argocd_app_sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/user-guide/commands/argocd_app_wait.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 17a2eb2

Please sign in to comment.