fix: get app resources tree view (#26166)

Signed-off-by: Patroklos Papapetrou <ppapapetrou76@gmail.com>
This commit is contained in:
Papapetrou Patroklos
2026-02-03 22:54:10 +02:00
committed by GitHub
parent ae34305d18
commit bf1f836ece
5 changed files with 93 additions and 22 deletions

View File

@@ -43,9 +43,49 @@ func TestPrintTreeViewAppResources(t *testing.T) {
printTreeViewAppResourcesNotOrphaned(nodeMapping, mapParentToChild, parentNode, w)
require.NoError(t, w.Flush())
output := buf.String()
t.Logf("Output:\n%s", output)
assert.Contains(t, output, "Rollout")
assert.Contains(t, output, "argoproj.io")
assert.Contains(t, output, "└─apps ReplicaSet sandbox-rollout-numalogic-demo numalogic-rollout-demo-5dcd5457d5 No")
assert.Contains(t, output, " └─ Pod sandbox-rollout-numalogic-demo numalogic-rollout-demo-5dcd5457d5-6trpt No")
}
func TestPrintTreeViewAppResourcesWithMultipleChildren(t *testing.T) {
var nodes [4]v1alpha1.ResourceNode
// Parent
nodes[0].ResourceRef = v1alpha1.ResourceRef{Group: "argoproj.io", Kind: "Rollout", Namespace: "ns", Name: "rollout", UID: "root"}
// Child 1
nodes[1].ResourceRef = v1alpha1.ResourceRef{Group: "apps", Kind: "ReplicaSet", Namespace: "ns", Name: "rs1", UID: "rs1"}
nodes[1].ParentRefs = []v1alpha1.ResourceRef{{UID: "root"}}
// Child 2
nodes[2].ResourceRef = v1alpha1.ResourceRef{Group: "apps", Kind: "ReplicaSet", Namespace: "ns", Name: "rs2", UID: "rs2"}
nodes[2].ParentRefs = []v1alpha1.ResourceRef{{UID: "root"}}
// Grandchild
nodes[3].ResourceRef = v1alpha1.ResourceRef{Group: "", Kind: "Pod", Namespace: "ns", Name: "pod1", UID: "pod1"}
nodes[3].ParentRefs = []v1alpha1.ResourceRef{{UID: "rs1"}}
nodeMapping := make(map[string]v1alpha1.ResourceNode)
mapParentToChild := make(map[string][]string)
parentNode := make(map[string]struct{})
for _, node := range nodes {
nodeMapping[node.UID] = node
if len(node.ParentRefs) > 0 {
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, w)
require.NoError(t, w.Flush())
output := buf.String()
t.Logf("Output:\n%s", output)
assert.Contains(t, output, "├─apps ReplicaSet ns rs1")
assert.Contains(t, output, "│ └─ Pod ns pod1")
assert.Contains(t, output, "└─apps ReplicaSet ns rs2")
}
func TestPrintTreeViewDetailedAppResources(t *testing.T) {
@@ -82,10 +122,11 @@ func TestPrintTreeViewDetailedAppResources(t *testing.T) {
printDetailedTreeViewAppResourcesNotOrphaned(nodeMapping, mapParentToChild, parentNode, w)
require.NoError(t, w.Flush())
output := buf.String()
t.Logf("Output:\n%s", output)
assert.Contains(t, output, "Rollout")
assert.Contains(t, output, "Degraded")
assert.Contains(t, output, "Readiness Gate failed")
assert.Contains(t, output, "argoproj.io Rollout sandbox-rollout-numalogic-demo numalogic-rollout-demo No <unknown> Degraded Readiness Gate failed")
assert.Contains(t, output, "└─apps ReplicaSet sandbox-rollout-numalogic-demo numalogic-rollout-demo-5dcd5457d5 No")
assert.Contains(t, output, " └─ Pod sandbox-rollout-numalogic-demo numalogic-rollout-demo-5dcd5457d5-6trpt No")
}
func TestPrintResourcesTree(t *testing.T) {

View File

@@ -10,6 +10,8 @@ import (
"gopkg.in/yaml.v3"
"github.com/argoproj/argo-cd/v3/util/templates"
"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils"
"github.com/argoproj/argo-cd/v3/cmd/util"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
@@ -533,7 +535,20 @@ func NewApplicationListResourcesCommand(clientOpts *argocdclient.ClientOptions)
)
command := &cobra.Command{
Use: "resources APPNAME",
Short: "List resource of application",
Short: "List resources of application",
Example: templates.Examples(`
# List first-level resources of application
argocd app resources my-app --refresh
# List only the orphaned resources of application
argocd app resources my-app --orphaned
# Shows resource hierarchy with parent-child relationships
argocd app resources my-app --output tree
# Shows resource hierarchy with parent-child relationships including information about age, health and reason
argocd app resources my-app --output tree=detailed
`),
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()
if len(args) != 1 {
@@ -554,7 +569,9 @@ func NewApplicationListResourcesCommand(clientOpts *argocdclient.ClientOptions)
},
}
command.Flags().BoolVar(&orphaned, "orphaned", false, "Lists only orphaned resources")
command.Flags().StringVar(&output, "output", "", "Provides the tree view of the resources")
command.Flags().StringVar(&output, "output", "", `Output format. One of: tree|tree=detailed.
tree: Shows resource hierarchy with parent-child relationships
tree=detailed: Same as tree, but includes AGE, HEALTH, and REASON columns`)
command.Flags().StringVar(&project, "project", "", `The name of the application's project - specifying this allows the command to report "not found" instead of "permission denied" if the app does not exist`)
return command
}

View File

@@ -15,7 +15,6 @@ import (
const (
firstElemPrefix = `├─`
lastElemPrefix = `└─`
indent = " "
pipe = ``
)
@@ -75,9 +74,7 @@ func detailedTreeViewAppGet(prefix string, uidToNodeMap map[string]v1alpha1.Reso
}
func treeViewAppResourcesNotOrphaned(prefix string, uidToNodeMap map[string]v1alpha1.ResourceNode, parentChildMap map[string][]string, parent v1alpha1.ResourceNode, w *tabwriter.Writer) {
if len(parent.ParentRefs) == 0 {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", parent.Group, parent.Kind, parent.Namespace, parent.Name, "No")
}
_, _ = fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\t%s\n", printPrefix(prefix), parent.Group, parent.Kind, parent.Namespace, parent.Name, "No")
chs := parentChildMap[parent.UID]
for i, child := range chs {
var p string
@@ -92,7 +89,7 @@ func treeViewAppResourcesNotOrphaned(prefix string, uidToNodeMap map[string]v1al
}
func treeViewAppResourcesOrphaned(prefix string, uidToNodeMap map[string]v1alpha1.ResourceNode, parentChildMap map[string][]string, parent v1alpha1.ResourceNode, w *tabwriter.Writer) {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", parent.Group, parent.Kind, parent.Namespace, parent.Name, "Yes")
_, _ = fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\t%s\n", printPrefix(prefix), parent.Group, parent.Kind, parent.Namespace, parent.Name, "Yes")
chs := parentChildMap[parent.UID]
for i, child := range chs {
var p string
@@ -107,14 +104,12 @@ func treeViewAppResourcesOrphaned(prefix string, uidToNodeMap map[string]v1alpha
}
func detailedTreeViewAppResourcesNotOrphaned(prefix string, uidToNodeMap map[string]v1alpha1.ResourceNode, parentChildMap map[string][]string, parent v1alpha1.ResourceNode, w *tabwriter.Writer) {
if len(parent.ParentRefs) == 0 {
healthStatus, reason := extractHealthStatusAndReason(parent)
age := "<unknown>"
if parent.CreatedAt != nil {
age = duration.HumanDuration(time.Since(parent.CreatedAt.Time))
}
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", parent.Group, parent.Kind, parent.Namespace, parent.Name, "No", age, healthStatus, reason)
healthStatus, reason := extractHealthStatusAndReason(parent)
age := "<unknown>"
if parent.CreatedAt != nil {
age = duration.HumanDuration(time.Since(parent.CreatedAt.Time))
}
_, _ = fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", printPrefix(prefix), parent.Group, parent.Kind, parent.Namespace, parent.Name, "No", age, healthStatus, reason)
chs := parentChildMap[parent.UID]
for i, child := range chs {
var p string
@@ -134,7 +129,7 @@ func detailedTreeViewAppResourcesOrphaned(prefix string, uidToNodeMap map[string
if parent.CreatedAt != nil {
age = duration.HumanDuration(time.Since(parent.CreatedAt.Time))
}
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", parent.Group, parent.Kind, parent.Namespace, parent.Name, "Yes", age, healthStatus, reason)
_, _ = fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", printPrefix(prefix), parent.Group, parent.Kind, parent.Namespace, parent.Name, "Yes", age, healthStatus, reason)
chs := parentChildMap[parent.UID]
for i, child := range chs {

View File

@@ -97,7 +97,7 @@ argocd app [flags]
* [argocd app patch](argocd_app_patch.md) - Patch application
* [argocd app patch-resource](argocd_app_patch-resource.md) - Patch resource in an application
* [argocd app remove-source](argocd_app_remove-source.md) - Remove a source from multiple sources application.
* [argocd app resources](argocd_app_resources.md) - List resource of application
* [argocd app resources](argocd_app_resources.md) - List resources of application
* [argocd app rollback](argocd_app_rollback.md) - Rollback application to a previous deployed version by History ID, omitted will Rollback to the previous version
* [argocd app set](argocd_app_set.md) - Set application parameters
* [argocd app sync](argocd_app_sync.md) - Sync an application to its target state

View File

@@ -2,18 +2,36 @@
## argocd app resources
List resource of application
List resources of application
```
argocd app resources APPNAME [flags]
```
### Examples
```
# List first-level resources of application
argocd app resources my-app --refresh
# List only the orphaned resources of application
argocd app resources my-app --orphaned
# Shows resource hierarchy with parent-child relationships
argocd app resources my-app --output tree
# Shows resource hierarchy with parent-child relationships including information about age, health and reason
argocd app resources my-app --output tree=detailed
```
### Options
```
-h, --help help for resources
--orphaned Lists only orphaned resources
--output string Provides the tree view of the resources
--output string Output format. One of: tree|tree=detailed.
tree: Shows resource hierarchy with parent-child relationships
tree=detailed: Same as tree, but includes AGE, HEALTH, and REASON columns
--project string The name of the application's project - specifying this allows the command to report "not found" instead of "permission denied" if the app does not exist
```