Skip to content

Commit

Permalink
feat: add option in output flag for app get and app resources cli com…
Browse files Browse the repository at this point in the history
…mand for tree view(#13370) (#15386)

* tree view feature for app get cli comand

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

* including application details

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

* testcase included for printTreeView

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

* removed the unnecessary variables

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

* Adding changes to the documentation

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

* change in the argocd doc

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

* included tee_test.go

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

* tree view changes for app get and app resources

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

* go.mod and go.sum by running go mod tidy

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

* changes after review

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

* changes after review

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

---------

Signed-off-by: schakrad <[email protected]>
  • Loading branch information
schakrad authored Sep 15, 2023
1 parent 56e8987 commit 0d0a295
Show file tree
Hide file tree
Showing 8 changed files with 735 additions and 36 deletions.
107 changes: 91 additions & 16 deletions cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,54 @@ func hasAppChanged(appReq, appRes *argoappv1.Application, upsert bool) bool {
return true
}

func parentChildDetails(appIf application.ApplicationServiceClient, ctx context.Context, appName string, appNs string) (map[string]argoappv1.ResourceNode, map[string][]string, map[string]struct{}) {

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 mapUidToNode, mapParentToChild, parentNode
}

func printHeader(acdClient argocdclient.Client, app *argoappv1.Application, ctx context.Context, windows *argoappv1.SyncWindows, showOperation bool, showParams bool) {
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)
}
}

// NewApplicationGetCommand returns a new instance of an `argocd app get` command
func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
Expand All @@ -268,12 +316,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 +332,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 All @@ -302,35 +353,41 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
err := PrintResource(app, output)
errors.CheckError(err)
case "wide", "":
aURL := appURL(ctx, acdClient, app.Name)
printAppSummaryTable(app, aURL, windows)

if len(app.Status.Conditions) > 0 {
printHeader(acdClient, app, ctx, windows, showOperation, showParams)
if len(app.Status.Resources) > 0 {
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
printAppConditions(w, app)
printAppResources(w, app)
_ = w.Flush()
fmt.Println()
}
if showOperation && app.Status.OperationState != nil {
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
}
if len(mapUidToNode) > 0 {
fmt.Println()
printOperationResult(app.Status.OperationState)
printTreeView(mapUidToNode, mapParentToChild, parentNode, mapNodeNameToResourceState)
}
if showParams {
printParams(app)
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
}
if len(app.Status.Resources) > 0 {
if len(mapUidToNode) > 0 {
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
printAppResources(w, app)
_ = w.Flush()
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 @@ -1521,6 +1578,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

0 comments on commit 0d0a295

Please sign in to comment.