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: tree option in output flag for app get and app resources cli command for tree view(#13370) #15386

Merged
merged 15 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 107 additions & 4 deletions cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,34 @@ func hasAppChanged(appReq, appRes *argoappv1.Application, upsert bool) bool {
return true
}

func parentChildDetails(appIf application.ApplicationServiceClient, ctx context.Context, appName string, appNs string) (mapUidToNode map[string]argoappv1.ResourceNode, mapParentToChild map[string][]string, parentNode map[string]struct{}) {
schakrad marked this conversation as resolved.
Show resolved Hide resolved

mapUidToNode = make(map[string]argoappv1.ResourceNode)
mapParentToChild = make(map[string][]string)
parentNode = make(map[string]struct{})

resourceTree, err := appIf.ResourceTree(ctx, &application.ResourcesQuery{Name: &appName, AppNamespace: &appNs, ApplicationName: &appName})
errors.CheckError(err)

for _, node := range resourceTree.Nodes {

mapUidToNode[node.UID] = node

if len(node.ParentRefs) > 0 {
_, ok := mapParentToChild[node.ParentRefs[0].UID]
if !ok {
var temp []string
mapParentToChild[node.ParentRefs[0].UID] = temp
}
mapParentToChild[node.ParentRefs[0].UID] = append(mapParentToChild[node.ParentRefs[0].UID], node.UID)
} else {
parentNode[node.UID] = struct{}{}
}

}
return
}

// NewApplicationGetCommand returns a new instance of an `argocd app get` command
func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
Expand All @@ -268,12 +296,13 @@ 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 All @@ -283,11 +312,13 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
defer argoio.Close(conn)

appName, appNs := argo.ParseFromQualifiedName(args[0], "")

app, err := appIf.Get(ctx, &application.ApplicationQuery{
Name: &appName,
Refresh: getRefreshType(refresh, hardRefresh),
AppNamespace: &appNs,
})

errors.CheckError(err)

pConn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
Expand Down Expand Up @@ -319,18 +350,72 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
if showParams {
printParams(app)
}

if len(app.Status.Resources) > 0 {
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
printAppResources(w, app)
_ = w.Flush()
}
case "tree":
schakrad marked this conversation as resolved.
Show resolved Hide resolved
aURL := appURL(ctx, acdClient, app.Name)
printAppSummaryTable(app, aURL, windows)

if len(app.Status.Conditions) > 0 {
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
printAppConditions(w, app)
_ = w.Flush()
fmt.Println()
}
if showOperation && app.Status.OperationState != nil {
fmt.Println()
printOperationResult(app.Status.OperationState)
}
if showParams {
printParams(app)
}
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
}
if len(mapUidToNode) > 0 {
printTreeView(mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState)
}
case "tree=detailed":
aURL := appURL(ctx, acdClient, app.Name)
printAppSummaryTable(app, aURL, windows)

if len(app.Status.Conditions) > 0 {
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
printAppConditions(w, app)
_ = w.Flush()
fmt.Println()
}
if showOperation && app.Status.OperationState != nil {
fmt.Println()
printOperationResult(app.Status.OperationState)
}
if showParams {
printParams(app)
}
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
}
if len(mapUidToNode) > 0 {
fmt.Println()
printTreeViewDetailed(mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState)
}
default:
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
}
},
}
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide")
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree")
command.Flags().BoolVar(&showOperation, "show-operation", false, "Show application operation")
command.Flags().BoolVar(&showParams, "show-params", false, "Show application parameters and overrides")
command.Flags().BoolVar(&refresh, "refresh", false, "Refresh application data when retrieving")
Expand Down Expand Up @@ -420,12 +505,12 @@ func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().StringVar(&kind, "kind", "", "Resource kind")
command.Flags().StringVar(&namespace, "namespace", "", "Resource namespace")
command.Flags().StringVar(&resourceName, "name", "", "Resource name")
command.Flags().BoolVarP(&follow, "follow", "f", false, "Specify if the logs should be streamed")
command.Flags().BoolVar(&follow, "follow", false, "Specify if the logs should be streamed")
schakrad marked this conversation as resolved.
Show resolved Hide resolved
command.Flags().Int64Var(&tail, "tail", 0, "The number of lines from the end of the logs to show")
command.Flags().Int64Var(&sinceSeconds, "since-seconds", 0, "A relative time in seconds before the current time from which to show logs")
command.Flags().StringVar(&untilTime, "until-time", "", "Show logs until this time")
command.Flags().StringVar(&filter, "filter", "", "Show logs contain this string")
command.Flags().StringVarP(&container, "container", "c", "", "Optional container name")
command.Flags().StringVar(&container, "container", "", "Optional container name")
schakrad marked this conversation as resolved.
Show resolved Hide resolved
command.Flags().BoolVarP(&previous, "previous", "p", false, "Specify if the previously terminated container logs should be returned")

return command
Expand Down Expand Up @@ -1521,6 +1606,24 @@ func printAppResources(w io.Writer, app *argoappv1.Application) {
}
}

func printTreeView(nodeMapping map[string]argoappv1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, mapNodeNameToResourceState map[string]*resourceState) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintf(w, "KIND/NAME\tSTATUS\tHEALTH\tMESSAGE\n")
for uid := range parentNodes {
treeViewAppGet("", nodeMapping, parentChildMapping, nodeMapping[uid], mapNodeNameToResourceState, w)
}
_ = w.Flush()
}

func printTreeViewDetailed(nodeMapping map[string]argoappv1.ResourceNode, parentChildMapping map[string][]string, parentNodes map[string]struct{}, mapNodeNameToResourceState map[string]*resourceState) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "KIND/NAME\tSTATUS\tHEALTH\tAGE\tMESSAGE\tREASON\n")
for uid := range parentNodes {
detailedTreeViewAppGet("", nodeMapping, parentChildMapping, nodeMapping[uid], mapNodeNameToResourceState, w)
}
_ = w.Flush()
}

// NewApplicationSyncCommand returns a new instance of an `argocd app sync` command
func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
Expand Down
82 changes: 81 additions & 1 deletion cmd/argocd/commands/app_resource_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,93 @@
package commands

import (
"bytes"
"testing"
"text/tabwriter"

"github.com/stretchr/testify/assert"

"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)

func TestPrintTreeViewAppResources(t *testing.T) {
var nodes [3]v1alpha1.ResourceNode
nodes[0].ResourceRef = v1alpha1.ResourceRef{Group: "", Version: "v1", Kind: "Pod", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5-6trpt", UID: "92c3a5fe-d13e-4ae2-b8ec-c10dd3543b28"}
nodes[0].ParentRefs = []v1alpha1.ResourceRef{{Group: "apps", Version: "v1", Kind: "ReplicaSet", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5", UID: "75c30dce-1b66-414f-a86c-573a74be0f40"}}
nodes[1].ResourceRef = v1alpha1.ResourceRef{Group: "apps", Version: "v1", Kind: "ReplicaSet", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5", UID: "75c30dce-1b66-414f-a86c-573a74be0f40"}
nodes[1].ParentRefs = []v1alpha1.ResourceRef{{Group: "argoproj.io", Version: "", Kind: "Rollout", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo", UID: "87f3aab0-f634-4b2c-959a-7ddd30675ed0"}}
nodes[2].ResourceRef = v1alpha1.ResourceRef{Group: "argoproj.io", Version: "", Kind: "Rollout", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo", UID: "87f3aab0-f634-4b2c-959a-7ddd30675ed0"}
var nodeMapping = make(map[string]v1alpha1.ResourceNode)
var mapParentToChild = make(map[string][]string)
var parentNode = make(map[string]struct{})
for _, node := range nodes {
nodeMapping[node.UID] = node
if len(node.ParentRefs) > 0 {
_, ok := mapParentToChild[node.ParentRefs[0].UID]
if !ok {
var temp []string
mapParentToChild[node.ParentRefs[0].UID] = temp
}
mapParentToChild[node.ParentRefs[0].UID] = append(mapParentToChild[node.ParentRefs[0].UID], node.UID)
} else {
parentNode[node.UID] = struct{}{}
}
}
buf := &bytes.Buffer{}
w := tabwriter.NewWriter(buf, 0, 0, 2, ' ', 0)

printTreeViewAppResourcesNotOrphaned(nodeMapping, mapParentToChild, parentNode, false, false, w)
if err := w.Flush(); err != nil {
t.Fatal(err)
}
output := buf.String()

assert.Contains(t, output, "Rollout")
assert.Contains(t, output, "argoproj.io")
}

func TestPrintTreeViewDetailedAppResources(t *testing.T) {
var nodes [3]v1alpha1.ResourceNode
nodes[0].ResourceRef = v1alpha1.ResourceRef{Group: "", Version: "v1", Kind: "Pod", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5-6trpt", UID: "92c3a5fe-d13e-4ae2-b8ec-c10dd3543b28"}
nodes[0].ParentRefs = []v1alpha1.ResourceRef{{Group: "apps", Version: "v1", Kind: "ReplicaSet", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5", UID: "75c30dce-1b66-414f-a86c-573a74be0f40"}}
nodes[1].ResourceRef = v1alpha1.ResourceRef{Group: "apps", Version: "v1", Kind: "ReplicaSet", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5", UID: "75c30dce-1b66-414f-a86c-573a74be0f40"}
nodes[1].ParentRefs = []v1alpha1.ResourceRef{{Group: "argoproj.io", Version: "", Kind: "Rollout", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo", UID: "87f3aab0-f634-4b2c-959a-7ddd30675ed0"}}
nodes[2].ResourceRef = v1alpha1.ResourceRef{Group: "argoproj.io", Version: "", Kind: "Rollout", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo", UID: "87f3aab0-f634-4b2c-959a-7ddd30675ed0"}
nodes[2].Health = &v1alpha1.HealthStatus{
Status: "Degraded",
Message: "Readiness Gate failed",
}

var nodeMapping = make(map[string]v1alpha1.ResourceNode)
var mapParentToChild = make(map[string][]string)
var parentNode = make(map[string]struct{})
for _, node := range nodes {
nodeMapping[node.UID] = node
if len(node.ParentRefs) > 0 {
_, ok := mapParentToChild[node.ParentRefs[0].UID]
if !ok {
var temp []string
mapParentToChild[node.ParentRefs[0].UID] = temp
}
mapParentToChild[node.ParentRefs[0].UID] = append(mapParentToChild[node.ParentRefs[0].UID], node.UID)
} else {
parentNode[node.UID] = struct{}{}
}
}
buf := &bytes.Buffer{}
w := tabwriter.NewWriter(buf, 0, 0, 2, ' ', 0)

printDetailedTreeViewAppResourcesNotOrphaned(nodeMapping, mapParentToChild, parentNode, false, false, w)
if err := w.Flush(); err != nil {
t.Fatal(err)
}
output := buf.String()

assert.Contains(t, output, "Rollout")
assert.Contains(t, output, "Degraded")
assert.Contains(t, output, "Readiness Gate failed")
}

func TestPrintResourcesTree(t *testing.T) {
tree := v1alpha1.ApplicationTree{
Nodes: []v1alpha1.ResourceNode{
Expand All @@ -32,7 +112,7 @@ func TestPrintResourcesTree(t *testing.T) {
},
}
output, _ := captureOutput(func() error {
printResources(true, false, &tree)
printResources(true, false, &tree, "")
return nil
})

Expand Down
Loading
Loading