Skip to content

Commit ea6885a

Browse files
kro cli generate diagram (#589)
* kro-cli generate diagram command
1 parent a0dbcc4 commit ea6885a

File tree

3 files changed

+138
-2
lines changed

3 files changed

+138
-2
lines changed

cmd/kro/commands/generate.go

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1+
// Copyright 2025 The Kube Resource Orchestrator Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
package commands
216

317
import (
418
"encoding/json"
519
"fmt"
620
"os"
721

22+
"github.com/go-echarts/go-echarts/v2/charts"
23+
"github.com/go-echarts/go-echarts/v2/opts"
824
"github.com/kro-run/kro/api/v1alpha1"
925
kroclient "github.com/kro-run/kro/pkg/client"
1026
"github.com/kro-run/kro/pkg/graph"
@@ -64,7 +80,7 @@ var generateCRDCmd = &cobra.Command{
6480

6581
var generateInstanceCmd = &cobra.Command{
6682
Use: "instance",
67-
Short: "Generate a ResourceGraphDefinition (RGD) instance",
83+
Short: "Generate an instance of the ResourceGraphDefinition",
6884
Long: "Generate a ResourceGraphDefinition (RGD) instance from a " +
6985
"ResourceGraphDefinition file. This command reads the " +
7086
"ResourceGraphDefinition and outputs the corresponding RGD instance",
@@ -84,7 +100,36 @@ var generateInstanceCmd = &cobra.Command{
84100
}
85101

86102
if err = generateInstance(&rgd); err != nil {
87-
return fmt.Errorf("failed to generate RGD instance: %w", err)
103+
return fmt.Errorf("failed to generate instance: %w", err)
104+
}
105+
106+
return nil
107+
},
108+
}
109+
110+
var generateDiagramCmd = &cobra.Command{
111+
Use: "diagram",
112+
Short: "Generate a diagram from a ResourceGraphDefinition",
113+
Long: "Generate a diagram from a ResourceGraphDefinition file. This command reads the " +
114+
"ResourceGraphDefinition and outputs the corresponding diagram " +
115+
"in the specified format.",
116+
RunE: func(cmd *cobra.Command, args []string) error {
117+
if config.resourceGraphDefinitionFile == "" {
118+
return fmt.Errorf("ResourceGraphDefinition file is required")
119+
}
120+
121+
data, err := os.ReadFile(config.resourceGraphDefinitionFile)
122+
if err != nil {
123+
return fmt.Errorf("failed to read ResourceGraphDefinition file: %w", err)
124+
}
125+
126+
var rgd v1alpha1.ResourceGraphDefinition
127+
if err = yaml.Unmarshal(data, &rgd); err != nil {
128+
return fmt.Errorf("failed to unmarshal ResourceGraphDefinition: %w", err)
129+
}
130+
131+
if err = generateDiagram(&rgd); err != nil {
132+
return fmt.Errorf("failed to generate diagram: %w", err)
88133
}
89134

90135
return nil
@@ -110,6 +155,93 @@ func generateCRD(rgd *v1alpha1.ResourceGraphDefinition) error {
110155
return nil
111156
}
112157

158+
func generateDiagram(rgd *v1alpha1.ResourceGraphDefinition) error {
159+
rgdGraph, err := createGraphBuilder(rgd)
160+
if err != nil {
161+
return fmt.Errorf("failed to setup rgd graph: %w", err)
162+
}
163+
164+
graph := charts.NewGraph()
165+
166+
// Graph layout
167+
graph.SetGlobalOptions(
168+
charts.WithInitializationOpts(opts.Initialization{
169+
Width: "70%",
170+
Height: "90vh",
171+
}),
172+
charts.WithTitleOpts(opts.Title{
173+
Title: fmt.Sprintf("Resource Graph: %s", rgd.Name),
174+
Subtitle: fmt.Sprintf("Topological Order: %v", rgdGraph.TopologicalOrder),
175+
TitleStyle: &opts.TextStyle{
176+
FontSize: 18,
177+
FontWeight: "bold",
178+
},
179+
SubtitleStyle: &opts.TextStyle{
180+
FontSize: 14,
181+
},
182+
}),
183+
charts.WithLegendOpts(opts.Legend{
184+
Show: opts.Bool(false),
185+
}),
186+
)
187+
188+
resourceOrder := make(map[string]int)
189+
for i, resourceName := range rgdGraph.TopologicalOrder {
190+
resourceOrder[resourceName] = i + 1
191+
}
192+
193+
// Graph nodes
194+
nodes := make([]opts.GraphNode, 0, len(rgdGraph.Resources))
195+
for resourceName := range rgdGraph.Resources {
196+
order := resourceOrder[resourceName]
197+
198+
node := opts.GraphNode{
199+
Name: fmt.Sprintf("%s\n(%d)", resourceName, order),
200+
SymbolSize: 40.0,
201+
ItemStyle: &opts.ItemStyle{
202+
Color: "#269103",
203+
BorderColor: "#333",
204+
BorderWidth: 1,
205+
},
206+
}
207+
nodes = append(nodes, node)
208+
}
209+
210+
// Graph links
211+
links := make([]opts.GraphLink, 0)
212+
for resourceName, resource := range rgdGraph.Resources {
213+
targetOrder := resourceOrder[resourceName]
214+
215+
for _, dependency := range resource.GetDependencies() {
216+
sourceOrder := resourceOrder[dependency]
217+
218+
link := opts.GraphLink{
219+
Source: fmt.Sprintf("%s\n(%d)", dependency, sourceOrder),
220+
Target: fmt.Sprintf("%s\n(%d)", resourceName, targetOrder),
221+
LineStyle: &opts.LineStyle{
222+
Color: "#000",
223+
},
224+
}
225+
links = append(links, link)
226+
}
227+
}
228+
229+
graph.AddSeries("resource", nodes, links).SetSeriesOptions(
230+
charts.WithGraphChartOpts(
231+
opts.GraphChart{
232+
Force: &opts.GraphForce{
233+
Repulsion: 5000,
234+
},
235+
Roam: opts.Bool(true),
236+
EdgeSymbol: "arrow",
237+
EdgeSymbolSize: []int{0, 7},
238+
},
239+
),
240+
)
241+
242+
return graph.Render(os.Stdout)
243+
}
244+
113245
func generateInstance(rgd *v1alpha1.ResourceGraphDefinition) error {
114246
rgdGraph, err := createGraphBuilder(rgd)
115247
if err != nil {
@@ -180,6 +312,7 @@ func marshalObject(obj interface{}, outputFormat string) ([]byte, error) {
180312

181313
func AddGenerateCommands(rootCmd *cobra.Command) {
182314
generateCmd.AddCommand(generateCRDCmd)
315+
generateCmd.AddCommand(generateDiagramCmd)
183316
generateCmd.AddCommand(generateInstanceCmd)
184317
rootCmd.AddCommand(generateCmd)
185318
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/kro-run/kro
33
go 1.24.0
44

55
require (
6+
github.com/go-echarts/go-echarts/v2 v2.6.1
67
github.com/go-logr/logr v1.4.2
78
github.com/gobuffalo/flect v1.0.2
89
github.com/google/cel-go v0.24.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
8585
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
8686
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
8787
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
88+
github.com/go-echarts/go-echarts/v2 v2.6.1 h1:UjyovbU7sbALakMYaoFsSKimT1Sm3kHCJcJSu6U5JoU=
89+
github.com/go-echarts/go-echarts/v2 v2.6.1/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
8890
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
8991
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
9092
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=

0 commit comments

Comments
 (0)