From 24cf0c1fdc88af1c6ce7a403932148c7e879d92f Mon Sep 17 00:00:00 2001 From: Katrina Verey Date: Mon, 23 Dec 2019 16:36:25 -0500 Subject: [PATCH] config tree defaults to graph structure when ownerRefs available --- cmd/config/docs/commands/tree.md | 8 +- cmd/config/internal/commands/tree.go | 2 +- .../internal/generateddocs/commands/docs.go | 8 +- kyaml/kio/tree.go | 10 +- kyaml/kio/tree_test.go | 221 +++++++++++++++++- 5 files changed, 231 insertions(+), 18 deletions(-) diff --git a/cmd/config/docs/commands/tree.md b/cmd/config/docs/commands/tree.md index 07cf250e7..3b9742df0 100644 --- a/cmd/config/docs/commands/tree.md +++ b/cmd/config/docs/commands/tree.md @@ -20,8 +20,9 @@ container names, etc. kustomize config tree supports printing arbitrary fields using the '--field' flag. -By default, kustomize config tree uses the directory structure for the tree structure, however when printing -from the cluster, the Resource graph structure may be used instead. +By default, kustomize config tree uses Resource graph structure if any relationships between resources (ownerReferences) +are detected, as is typically the case when printing from a cluster. Otherwise, directory graph structure is used. The +graph structure can also be selected explicitly using the '--graph-structure' flag. ### Examples @@ -42,8 +43,7 @@ from the cluster, the Resource graph structure may be used instead. --field="status.conditions[type=Completed].status" # print live Resources from a cluster using owners for graph structure - kubectl get all -o yaml | kustomize config tree --replicas --name --image \ - --graph-structure=owners + kubectl get all -o yaml | kustomize config tree --replicas --name --image # print live Resources with status condition fields kubectl get all -o yaml | kustomize config tree \ diff --git a/cmd/config/internal/commands/tree.go b/cmd/config/internal/commands/tree.go index 91bb64858..ab8c38804 100644 --- a/cmd/config/internal/commands/tree.go +++ b/cmd/config/internal/commands/tree.go @@ -45,7 +45,7 @@ func GetTreeRunner(name string) *TreeRunner { "if true, include local-config in the output.") c.Flags().BoolVar(&r.excludeNonLocal, "exclude-non-local", false, "if true, exclude non-local-config in the output.") - c.Flags().StringVar(&r.structure, "graph-structure", "directory", + c.Flags().StringVar(&r.structure, "graph-structure", "", "Graph structure to use for printing the tree. may be any of: "+ strings.Join(kio.GraphStructures, ",")) diff --git a/cmd/config/internal/generateddocs/commands/docs.go b/cmd/config/internal/generateddocs/commands/docs.go index ef0013e83..4ee4d845e 100644 --- a/cmd/config/internal/generateddocs/commands/docs.go +++ b/cmd/config/internal/generateddocs/commands/docs.go @@ -460,8 +460,9 @@ container names, etc. kustomize config tree supports printing arbitrary fields using the '--field' flag. -By default, kustomize config tree uses the directory structure for the tree structure, however when printing -from the cluster, the Resource graph structure may be used instead. +By default, kustomize config tree uses Resource graph structure if any relationships between resources (ownerReferences) +are detected, as is typically the case when printing from a cluster. Otherwise, directory graph structure is used. The +graph structure can also be selected explicitly using the '--graph-structure' flag. ` var TreeExamples = ` # print Resources using directory structure @@ -481,8 +482,7 @@ var TreeExamples = ` --field="status.conditions[type=Completed].status" # print live Resources from a cluster using owners for graph structure - kubectl get all -o yaml | kustomize config tree --replicas --name --image \ - --graph-structure=owners + kubectl get all -o yaml | kustomize config tree --replicas --name --image # print live Resources with status condition fields kubectl get all -o yaml | kustomize config tree \ diff --git a/kyaml/kio/tree.go b/kyaml/kio/tree.go index b96d99d62..0ea8f0ae6 100644 --- a/kyaml/kio/tree.go +++ b/kyaml/kio/tree.go @@ -98,9 +98,15 @@ func (p TreeWriter) Write(nodes []*yaml.RNode) error { return p.packageStructure(nodes) case TreeStructureGraph: return p.graphStructure(nodes) - default: - return p.packageStructure(nodes) } + + // If any resource has an owner reference, default to the graph structure. Otherwise, use package structure. + for _, node := range nodes { + if owners, _ := node.Pipe(yaml.Lookup("metadata", "ownerReferences")); owners != nil { + return p.graphStructure(nodes) + } + } + return p.packageStructure(nodes) } // node wraps a tree node, and any children nodes diff --git a/kyaml/kio/tree_test.go b/kyaml/kio/tree_test.go index e5779face..fb9041c16 100644 --- a/kyaml/kio/tree_test.go +++ b/kyaml/kio/tree_test.go @@ -12,7 +12,7 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" ) -func TestPrinter_Write(t *testing.T) { +func TestPrinter_Write_Package_Structure(t *testing.T) { in := `kind: Deployment metadata: labels: @@ -66,7 +66,7 @@ spec: out := &bytes.Buffer{} err := Pipeline{ Inputs: []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}}, - Outputs: []Writer{TreeWriter{Writer: out}}, + Outputs: []Writer{TreeWriter{Writer: out, Structure: TreeStructurePackage}}, }.Execute() if !assert.NoError(t, err) { t.FailNow() @@ -85,7 +85,7 @@ spec: } } -func TestPrinter_Write_base(t *testing.T) { +func TestPrinter_Write_Package_Structure_base(t *testing.T) { in := `kind: Deployment metadata: labels: @@ -139,7 +139,7 @@ spec: out := &bytes.Buffer{} err := Pipeline{ Inputs: []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}}, - Outputs: []Writer{TreeWriter{Writer: out}}, + Outputs: []Writer{TreeWriter{Writer: out, Structure: TreeStructurePackage}}, }.Execute() if !assert.NoError(t, err) { t.FailNow() @@ -157,7 +157,7 @@ spec: } } -func TestPrinter_Write_sort(t *testing.T) { +func TestPrinter_Write_Package_Structure_sort(t *testing.T) { in := `apiVersion: extensions/v1 kind: Deployment metadata: @@ -255,7 +255,7 @@ spec: out := &bytes.Buffer{} err := Pipeline{ Inputs: []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}}, - Outputs: []Writer{TreeWriter{Writer: out}}, + Outputs: []Writer{TreeWriter{Writer: out, Structure: TreeStructurePackage}}, }.Execute() if !assert.NoError(t, err) { t.FailNow() @@ -287,7 +287,7 @@ func TestPrinter_metaError(t *testing.T) { } } -func TestPrinter_Write_owners(t *testing.T) { +func TestPrinter_Write_Graph_Structure(t *testing.T) { in := ` apiVersion: v1 kind: Pod @@ -384,3 +384,210 @@ metadata: t.FailNow() } } + +func TestPrinter_Write_Structure_Defaulting_when_ownerRefs_present(t *testing.T) { + in := ` +apiVersion: v1 +kind: Pod +metadata: + name: cockroachdb-0 + namespace: myapp-staging + ownerReferences: + - apiVersion: apps/v1 + kind: StatefulSet + name: cockroachdb +spec: + containers: + - name: cockroachdb + image: cockraochdb:1.1.1 +--- +apiVersion: v1 +kind: Pod +metadata: + name: cockroachdb-1 + namespace: myapp-staging + ownerReferences: + - apiVersion: apps/v1 + kind: StatefulSet + name: cockroachdb +spec: + containers: + - name: cockroachdb + image: cockraochdb:1.1.1 +--- +apiVersion: v1 +kind: Pod +metadata: + name: cockroachdb-2 + namespace: myapp-staging + ownerReferences: + - apiVersion: apps/v1 + kind: StatefulSet + name: cockroachdb +spec: + containers: + - name: cockroachdb + image: cockraochdb:1.1.0 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: cockroachdb + namespace: myapp-staging + ownerReferences: + - apiVersion: app.k8s.io/v1beta1 + kind: Application + name: myapp +spec: + replicas: 3 + containers: + - name: cockroachdb + image: cockraochdb:1.1.1 +--- +apiVersion: v1 +kind: Service +metadata: + name: cockroachdb + namespace: myapp-staging + ownerReferences: + - apiVersion: app.k8s.io/v1beta1 + kind: Application + name: myapp +--- +apiVersion: app.k8s.io/v1beta1 +kind: Application +metadata: + labels: + app.kubernetes.io/name: myapp + name: myapp + namespace: myapp-staging +` + out := &bytes.Buffer{} + err := Pipeline{ + Inputs: []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}}, + Outputs: []Writer{TreeWriter{Writer: out}}, // Structure unspecified + }.Execute() + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, `. +└── [Resource] Application myapp-staging/myapp + ├── [Resource] Service myapp-staging/cockroachdb + └── [Resource] StatefulSet myapp-staging/cockroachdb + ├── [Resource] Pod myapp-staging/cockroachdb-0 + ├── [Resource] Pod myapp-staging/cockroachdb-1 + └── [Resource] Pod myapp-staging/cockroachdb-2 +`, out.String()) { + t.FailNow() + } +} + +func TestPrinter_Write_Structure_Defaulting_when_ownerRefs_absent(t *testing.T) { + in := ` +apiVersion: v1 +kind: Pod +metadata: + name: cockroachdb-0 + namespace: myapp-staging +spec: + containers: + - name: cockroachdb + image: cockraochdb:1.1.1 +--- +apiVersion: v1 +kind: Pod +metadata: + name: cockroachdb-1 + namespace: myapp-staging +spec: + containers: + - name: cockroachdb + image: cockraochdb:1.1.1 +--- +apiVersion: v1 +kind: Pod +metadata: + name: cockroachdb-2 + namespace: myapp-staging +spec: + containers: + - name: cockroachdb + image: cockraochdb:1.1.0 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: cockroachdb + namespace: myapp-staging +spec: + replicas: 3 + containers: + - name: cockroachdb + image: cockraochdb:1.1.1 +--- +apiVersion: v1 +kind: Service +metadata: + name: cockroachdb + namespace: myapp-staging +--- +apiVersion: app.k8s.io/v1beta1 +kind: Application +metadata: + labels: + app.kubernetes.io/name: myapp + name: myapp + namespace: myapp-staging +` + out := &bytes.Buffer{} + err := Pipeline{ + Inputs: []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}}, + Outputs: []Writer{TreeWriter{Writer: out}}, // Structure unspecified + }.Execute() + if !assert.NoError(t, err) { + t.FailNow() + } + + if !assert.Equal(t, ` +└── + ├── [.] Service myapp-staging/cockroachdb + ├── [.] StatefulSet myapp-staging/cockroachdb + ├── [.] Pod myapp-staging/cockroachdb-0 + ├── [.] Pod myapp-staging/cockroachdb-1 + ├── [.] Pod myapp-staging/cockroachdb-2 + └── [.] Application myapp-staging/myapp +`, out.String()) { + t.FailNow() + } +} + +func TestPrinter_Write_error_when_owner_missing(t *testing.T) { + in := ` +--- +apiVersion: v1 +kind: Service +metadata: + name: cockroachdb + namespace: myapp-staging + ownerReferences: + - apiVersion: app.k8s.io/v1beta1 + kind: Application + name: nginx +--- +apiVersion: app.k8s.io/v1beta1 +kind: Application +metadata: + labels: + app.kubernetes.io/name: myapp + name: myapp + namespace: myapp-staging +` + out := &bytes.Buffer{} + err := Pipeline{ + Inputs: []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}}, + Outputs: []Writer{TreeWriter{Writer: out}}, + }.Execute() + assert.Error(t, err) + assert.Equal(t, "owner 'Application myapp-staging/nginx' not found in input, but found as an owner of input objects", err.Error()) +}