Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): tree option in output flag for app sync, app wait and app rollback for tree view #15572

Merged
merged 7 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 {
schakrad marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -9,9 +9,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 @@ -46,6 +46,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 @@ -40,6 +40,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