Skip to content

Commit 6e41a72

Browse files
feat: add deployment flag for printing out the graph of resources
This allows for loading the graph data into visualization tools and to better reason about the ordering of a deployment Signed-off-by: Erik Miller <erik.miller@gusto.com>
1 parent be214f1 commit 6e41a72

File tree

5 files changed

+133
-0
lines changed

5 files changed

+133
-0
lines changed

pkg/kapp/cmd/app/deploy.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ func (o *DeployOptions) Run() error {
183183
return err
184184
}
185185

186+
if o.DeployFlags.ChangeGraphFile != "" {
187+
err = clusterChangesGraph.WriteToFile(o.DeployFlags.ChangeGraphFile)
188+
if err != nil {
189+
return err
190+
}
191+
}
192+
186193
if o.DiffFlags.UI {
187194
return o.presentDiffUI(clusterChangesGraph)
188195
}

pkg/kapp/cmd/app/deploy_flags.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type DeployFlags struct {
2929
AppMetadataFile string
3030

3131
DisableGKScoping bool
32+
33+
ChangeGraphFile string
3234
}
3335

3436
func (s *DeployFlags) Set(cmd *cobra.Command) {
@@ -63,4 +65,6 @@ func (s *DeployFlags) Set(cmd *cobra.Command) {
6365

6466
cmd.Flags().BoolVar(&s.DisableGKScoping, "dangerous-disable-gk-scoping",
6567
false, "Disable scoping of resource searching to used GroupKinds")
68+
69+
cmd.Flags().StringVar(&s.ChangeGraphFile, "change-graph-file-output", "", "Render the deployment graph to a file")
6670
}

pkg/kapp/diffgraph/change_graph.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
package diffgraph
55

66
import (
7+
"encoding/json"
78
"fmt"
9+
"os"
810
"sort"
911
"strings"
1012

1113
ctlconf "carvel.dev/kapp/pkg/kapp/config"
1214
"carvel.dev/kapp/pkg/kapp/logger"
15+
ctlres "carvel.dev/kapp/pkg/kapp/resources"
1316
)
1417

1518
type ChangeGraph struct {
@@ -314,3 +317,50 @@ func (g *ChangeGraph) checkCyclesVisit(nodeN *Change, markedTemp, markedPerm map
314317
markedPerm[nodeN] = struct{}{}
315318
return nil
316319
}
320+
321+
func (g *ChangeGraph) WriteToFile(outputFile string) error {
322+
sb := &strings.Builder{}
323+
encoder := json.NewEncoder(sb)
324+
nodes := []RenderedNode{}
325+
edges := []RenderedEdge{}
326+
327+
for _, change := range g.All() {
328+
change.Change.Resource().UnstructuredObject()
329+
changeGroups, _ := change.Groups()
330+
331+
groups := []string{}
332+
for _, changeGroup := range changeGroups {
333+
groups = append(groups, changeGroup.Name)
334+
}
335+
changeID := ctlres.NewAssociationLabel(change.Change.Resource()).Value()
336+
var namespaceRef *string
337+
namespace := change.Change.Resource().Namespace()
338+
if namespace != "" {
339+
namespaceRef = &namespace
340+
}
341+
node := RenderedNode{
342+
ID: changeID,
343+
Data: RenderedNodeData{
344+
Name: change.Change.Resource().Name(),
345+
Namespace: namespaceRef,
346+
ChangeGroups: groups,
347+
GroupKind: RenderedGroupKind(change.Change.Resource().GroupKind()),
348+
Op: change.Change.Op(),
349+
},
350+
}
351+
nodes = append(nodes, node)
352+
changeDependencies := change.WaitingFor
353+
for _, dependency := range changeDependencies {
354+
depID := ctlres.NewAssociationLabel(dependency.Change.Resource()).Value()
355+
edges = append(edges, RenderedEdge{
356+
Source: changeID,
357+
Target: depID,
358+
})
359+
}
360+
}
361+
encoder.Encode(RenderedGraph{
362+
Nodes: nodes,
363+
Edges: edges,
364+
})
365+
return os.WriteFile(outputFile, []byte(sb.String()), os.ModePerm)
366+
}

pkg/kapp/diffgraph/change_graph_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package diffgraph_test
55

66
import (
7+
"os"
78
"strings"
89
"testing"
910

@@ -840,6 +841,48 @@ func buildChangeGraph(resourcesBs string, op ctldgraph.ActualChangeOp, t *testin
840841
return buildChangeGraphWithOpts(buildGraphOpts{resourcesBs: resourcesBs, op: op}, t)
841842
}
842843

844+
func TestRenderGraph(t *testing.T) {
845+
configYAML := `
846+
kind: Job
847+
metadata:
848+
name: import-etcd-into-db
849+
annotations:
850+
kapp.k14s.io/change-group: "apps.big.co/import-etcd-into-db"
851+
---
852+
kind: Job
853+
metadata:
854+
name: after-migrations
855+
annotations:
856+
kapp.k14s.io/change-group.group1: "apps.big.co/after-migrations-1"
857+
kapp.k14s.io/change-group.group2: "apps.big.co/after-migrations-2"
858+
---
859+
kind: Job
860+
metadata:
861+
name: migrations
862+
annotations:
863+
kapp.k14s.io/change-rule.rule1: "upsert after upserting apps.big.co/import-etcd-into-db"
864+
kapp.k14s.io/change-rule.rule2: "upsert before upserting apps.big.co/after-migrations-1"
865+
kapp.k14s.io/change-rule.rule3: "upsert before upserting apps.big.co/after-migrations-2"
866+
`
867+
868+
graph, err := buildChangeGraph(configYAML, ctldgraph.ActualChangeOpUpsert, t)
869+
require.NoErrorf(t, err, "Expected graph to build")
870+
tmpFile, err := os.CreateTemp("", "*")
871+
defer func() {
872+
os.Remove(tmpFile.Name())
873+
}()
874+
require.NoErrorf(t, err, "Failed to create temp file")
875+
err = graph.WriteToFile(tmpFile.Name())
876+
require.NoErrorf(t, err, "Failed to write to file")
877+
contents, err := os.ReadFile(tmpFile.Name())
878+
require.NoErrorf(t, err, "Failed to read file")
879+
880+
expectedOutput := strings.TrimSpace(`
881+
{"nodes":[{"id":"v1.88b09231fb1239b5798a9fc230ef23f3","data":{"name":"import-etcd-into-db","namespace":null,"changeGroups":["apps.big.co/import-etcd-into-db"],"groupKind":{"group":"","kind":"Job"},"op":"upsert"}},{"id":"v1.3258dfc11ef4e6bfbfe728d7f6ed8193","data":{"name":"after-migrations","namespace":null,"changeGroups":["apps.big.co/after-migrations-1","apps.big.co/after-migrations-2"],"groupKind":{"group":"","kind":"Job"},"op":"upsert"}},{"id":"v1.cb954b4a1b9d4fbaaf4ff3ce5a9df1ba","data":{"name":"migrations","namespace":null,"changeGroups":[],"groupKind":{"group":"","kind":"Job"},"op":"upsert"}}],"edges":[{"source":"v1.3258dfc11ef4e6bfbfe728d7f6ed8193","target":"v1.cb954b4a1b9d4fbaaf4ff3ce5a9df1ba"},{"source":"v1.cb954b4a1b9d4fbaaf4ff3ce5a9df1ba","target":"v1.88b09231fb1239b5798a9fc230ef23f3"}]}
882+
`)
883+
require.Equal(t, expectedOutput, strings.TrimSpace(string(contents)))
884+
}
885+
843886
type buildGraphOpts struct {
844887
resources []ctlres.Resource
845888
resourcesBs string

pkg/kapp/diffgraph/graph_meta.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package diffgraph
2+
3+
type RenderedNodeData struct {
4+
Name string `json:"name"`
5+
Namespace *string `json:"namespace"`
6+
ChangeGroups []string `json:"changeGroups"`
7+
GroupKind RenderedGroupKind `json:"groupKind"`
8+
Op ActualChangeOp `json:"op"`
9+
}
10+
11+
type RenderedNode struct {
12+
ID string `json:"id"`
13+
Data RenderedNodeData `json:"data"`
14+
}
15+
16+
type RenderedGroupKind struct {
17+
Group string `json:"group"`
18+
Kind string `json:"kind"`
19+
}
20+
21+
type RenderedEdge struct {
22+
Source string `json:"source"`
23+
Target string `json:"target"`
24+
}
25+
26+
type RenderedGraph struct {
27+
Nodes []RenderedNode `json:"nodes"`
28+
Edges []RenderedEdge `json:"edges"`
29+
}

0 commit comments

Comments
 (0)