Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions pkg/kapp/cmd/app/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,13 @@ func (o *DeployOptions) Run() error {
return err
}

if o.DeployFlags.ChangeGraphFile != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason we do this check in this line and in line 528?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having it here makes it clear what the cli logic actually does. The check in the writeChangeGraphToFile was purely defensive/copying the pattern of writeAppMetadataToFile. Happy to remove that to remove any confusion

err = clusterChangesGraph.WriteToFile(o.DeployFlags.ChangeGraphFile)
if err != nil {
return err
}
}

if o.DiffFlags.UI {
return o.presentDiffUI(clusterChangesGraph)
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/kapp/cmd/app/deploy_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type DeployFlags struct {
AppMetadataFile string

DisableGKScoping bool

ChangeGraphFile string
}

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

cmd.Flags().BoolVar(&s.DisableGKScoping, "dangerous-disable-gk-scoping",
false, "Disable scoping of resource searching to used GroupKinds")

cmd.Flags().StringVar(&s.ChangeGraphFile, "change-graph-file-output", "", "Render the deployment graph to a file")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense that this option would only print the graph and not apply it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it as option to print out the graph that will be used to apply. This flag can then be used either with the apply or with the --diff-run. The hope is to be able to use the information in the graph to display the plan for review ahead of time and use it to show progress of the deployment when it's being applied

}
50 changes: 50 additions & 0 deletions pkg/kapp/diffgraph/change_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
package diffgraph

import (
"encoding/json"
"fmt"
"os"
"sort"
"strings"

ctlconf "carvel.dev/kapp/pkg/kapp/config"
"carvel.dev/kapp/pkg/kapp/logger"
ctlres "carvel.dev/kapp/pkg/kapp/resources"
)

type ChangeGraph struct {
Expand Down Expand Up @@ -314,3 +317,50 @@ func (g *ChangeGraph) checkCyclesVisit(nodeN *Change, markedTemp, markedPerm map
markedPerm[nodeN] = struct{}{}
return nil
}

func (g *ChangeGraph) WriteToFile(outputFile string) error {
sb := &strings.Builder{}
encoder := json.NewEncoder(sb)
nodes := []RenderedNode{}
edges := []RenderedEdge{}

for _, change := range g.All() {
change.Change.Resource().UnstructuredObject()
changeGroups, _ := change.Groups()

groups := []string{}
for _, changeGroup := range changeGroups {
groups = append(groups, changeGroup.Name)
}
changeID := ctlres.NewAssociationLabel(change.Change.Resource()).Value()
var namespaceRef *string
namespace := change.Change.Resource().Namespace()
if namespace != "" {
namespaceRef = &namespace
}
node := RenderedNode{
ID: changeID,
Data: RenderedNodeData{
Name: change.Change.Resource().Name(),
Namespace: namespaceRef,
ChangeGroups: groups,
GroupKind: RenderedGroupKind(change.Change.Resource().GroupKind()),
Op: change.Change.Op(),
},
}
nodes = append(nodes, node)
changeDependencies := change.WaitingFor
for _, dependency := range changeDependencies {
depID := ctlres.NewAssociationLabel(dependency.Change.Resource()).Value()
edges = append(edges, RenderedEdge{
Source: changeID,
Target: depID,
})
}
}
encoder.Encode(RenderedGraph{
Nodes: nodes,
Edges: edges,
})
return os.WriteFile(outputFile, []byte(sb.String()), os.ModePerm)
}
43 changes: 43 additions & 0 deletions pkg/kapp/diffgraph/change_graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package diffgraph_test

import (
"os"
"strings"
"testing"

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

func TestRenderGraph(t *testing.T) {
configYAML := `
kind: Job
metadata:
name: import-etcd-into-db
annotations:
kapp.k14s.io/change-group: "apps.big.co/import-etcd-into-db"
---
kind: Job
metadata:
name: after-migrations
annotations:
kapp.k14s.io/change-group.group1: "apps.big.co/after-migrations-1"
kapp.k14s.io/change-group.group2: "apps.big.co/after-migrations-2"
---
kind: Job
metadata:
name: migrations
annotations:
kapp.k14s.io/change-rule.rule1: "upsert after upserting apps.big.co/import-etcd-into-db"
kapp.k14s.io/change-rule.rule2: "upsert before upserting apps.big.co/after-migrations-1"
kapp.k14s.io/change-rule.rule3: "upsert before upserting apps.big.co/after-migrations-2"
`

graph, err := buildChangeGraph(configYAML, ctldgraph.ActualChangeOpUpsert, t)
require.NoErrorf(t, err, "Expected graph to build")
tmpFile, err := os.CreateTemp("", "*")
defer func() {
os.Remove(tmpFile.Name())
}()
require.NoErrorf(t, err, "Failed to create temp file")
err = graph.WriteToFile(tmpFile.Name())
require.NoErrorf(t, err, "Failed to write to file")
contents, err := os.ReadFile(tmpFile.Name())
require.NoErrorf(t, err, "Failed to read file")

expectedOutput := strings.TrimSpace(`
{"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"}]}
`)
require.Equal(t, expectedOutput, strings.TrimSpace(string(contents)))
}

type buildGraphOpts struct {
resources []ctlres.Resource
resourcesBs string
Expand Down
32 changes: 32 additions & 0 deletions pkg/kapp/diffgraph/graph_meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2024 The Carvel Authors.
// SPDX-License-Identifier: Apache-2.0

package diffgraph

type RenderedNodeData struct {
Name string `json:"name"`
Namespace *string `json:"namespace"`
ChangeGroups []string `json:"changeGroups"`
GroupKind RenderedGroupKind `json:"groupKind"`
Op ActualChangeOp `json:"op"`
}

type RenderedNode struct {
ID string `json:"id"`
Data RenderedNodeData `json:"data"`
}

type RenderedGroupKind struct {
Group string `json:"group"`
Kind string `json:"kind"`
}

type RenderedEdge struct {
Source string `json:"source"`
Target string `json:"target"`
}

type RenderedGraph struct {
Nodes []RenderedNode `json:"nodes"`
Edges []RenderedEdge `json:"edges"`
}