Skip to content

Commit a7628e0

Browse files
committed
feat: diagram just test target
adds output name option adds just targets for different vlab diagrams adds just target for lab-ci env Signed-off-by: Pau Capdevila <pau@githedgehog.com>
1 parent b4b3c12 commit a7628e0

File tree

7 files changed

+227
-26
lines changed

7 files changed

+227
-26
lines changed

cmd/hhfab/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,11 @@ func Run(ctx context.Context) error {
605605
func(item diagram.StyleType, _ int) string { return string(item) }), ", "),
606606
Value: string(diagram.StyleTypeDefault),
607607
},
608+
&cli.StringFlag{
609+
Name: "output",
610+
Aliases: []string{"o"},
611+
Usage: "output file path for the generated diagram (default: result/diagram.{format})",
612+
},
608613
&cli.BoolFlag{
609614
Name: "live",
610615
Usage: "load resources from actually running API instead of the config file (fab.yaml and include/*)",
@@ -614,7 +619,7 @@ func Run(ctx context.Context) error {
614619
Action: func(c *cli.Context) error {
615620
format := diagram.Format(strings.ToLower(c.String("format")))
616621
styleType := diagram.StyleType(c.String("style"))
617-
if err := hhfab.Diagram(ctx, workDir, cacheDir, c.Bool("live"), format, styleType); err != nil {
622+
if err := hhfab.Diagram(ctx, workDir, cacheDir, c.Bool("live"), format, styleType, c.String("output")); err != nil {
618623
return fmt.Errorf("failed to generate %s diagram: %w", format, err)
619624
}
620625

justfile

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,122 @@ zot := localbin / "zot" + "-" + zot_os + "-" + zot_arch + "-" + zot_version
165165
_localreg: _zot
166166
./hack/localreg.sh
167167
{{zot}} serve .zot/config.json 2>&1 | tee .zot/log
168+
169+
#
170+
# Generate diagrams for multiple environments in different formats and styles
171+
#
172+
test-diagram:
173+
@mkdir -p test-diagram
174+
@echo "==============================================="
175+
@echo "Diagram generation test - various topologies, formats, and styles"
176+
@echo "==============================================="
177+
178+
# Check if a VLAB is actually running
179+
@echo "=== Checking for running VLAB ==="
180+
@VLAB_PIDS=$(pgrep -f "[h]hfab vlab up" 2>/dev/null || echo ""); \
181+
if [ -n "$VLAB_PIDS" ] && [ -f "vlab/kubeconfig" ]; then \
182+
echo "=== Detected running VLAB, generating live diagrams ==="; \
183+
bin/hhfab diagram --format drawio --style default --live --output test-diagram/live-drawio-default.drawio || echo "Failed to generate live DrawIO diagram"; \
184+
bin/hhfab diagram --format drawio --style cisco --live --output test-diagram/live-drawio-cisco.drawio || echo "Failed to generate live DrawIO cisco diagram"; \
185+
bin/hhfab diagram --format drawio --style hedgehog --live --output test-diagram/live-drawio-hedgehog.drawio || echo "Failed to generate live DrawIO hedgehog diagram"; \
186+
bin/hhfab diagram --format dot --live --output test-diagram/live-dot.dot || echo "Failed to generate live DOT diagram"; \
187+
if command -v dot >/dev/null 2>&1 && [ -f "test-diagram/live-dot.dot" ]; then \
188+
dot -Tpng test-diagram/live-dot.dot -o test-diagram/live-dot.png; \
189+
fi; \
190+
bin/hhfab diagram --format mermaid --live --output test-diagram/live-mermaid.mermaid || echo "Failed to generate live Mermaid diagram"; \
191+
if [ -f "test-diagram/live-mermaid.mermaid" ]; then \
192+
echo '# Live Network Diagram' > test-diagram/live-mermaid.md; \
193+
echo '```mermaid' >> test-diagram/live-mermaid.md; \
194+
cat test-diagram/live-mermaid.mermaid >> test-diagram/live-mermaid.md; \
195+
echo '```' >> test-diagram/live-mermaid.md; \
196+
fi; \
197+
else \
198+
echo "No running VLAB detected, skipping live diagrams"; \
199+
fi
200+
201+
@echo -n "This will generate diagrams from multiple environments. Continue? [y/N] " && read ans && [ "$ans" = "y" -o "$ans" = "Y" ]
202+
203+
@echo "=== Generating diagrams for default VLAB topology ==="
204+
bin/hhfab init -f --dev --gw
205+
bin/hhfab vlab gen
206+
207+
# Generate all formats and styles for default topology
208+
bin/hhfab diagram --format drawio --style default --output test-diagram/default-drawio-default.drawio
209+
bin/hhfab diagram --format drawio --style cisco --output test-diagram/default-drawio-cisco.drawio
210+
bin/hhfab diagram --format drawio --style hedgehog --output test-diagram/default-drawio-hedgehog.drawio
211+
bin/hhfab diagram --format dot --output test-diagram/default-dot.dot
212+
bin/hhfab diagram --format mermaid --output test-diagram/default-mermaid.mermaid
213+
214+
@echo "=== Generating diagrams for variant 3-spine topology ==="
215+
bin/hhfab vlab gen --spines-count 3 --mclag-leafs-count 2 --orphan-leafs-count 1 --eslag-leaf-groups 2
216+
217+
# Generate all formats and styles for 3-spine topology
218+
bin/hhfab diagram --format drawio --style default --output test-diagram/3spine-drawio-default.drawio
219+
bin/hhfab diagram --format drawio --style cisco --output test-diagram/3spine-drawio-cisco.drawio
220+
bin/hhfab diagram --format drawio --style hedgehog --output test-diagram/3spine-drawio-hedgehog.drawio
221+
bin/hhfab diagram --format dot --output test-diagram/3spine-dot.dot
222+
bin/hhfab diagram --format mermaid --output test-diagram/3spine-mermaid.mermaid
223+
224+
# Convert DOT files to PNG if GraphViz is installed
225+
@echo "=== Converting DOT files to PNG if GraphViz is installed ==="
226+
@if command -v dot >/dev/null 2>&1; then \
227+
for DOT_FILE in test-diagram/*-dot.dot; do \
228+
PNG_FILE="${DOT_FILE%.dot}.png"; \
229+
echo "Converting $DOT_FILE to $PNG_FILE"; \
230+
dot -Tpng "$DOT_FILE" -o "$PNG_FILE"; \
231+
done; \
232+
else \
233+
echo "GraphViz dot not installed, skipping PNG conversion"; \
234+
fi
235+
236+
# Create markdown files with embedded mermaid diagrams
237+
@echo "=== Creating Markdown files with embedded Mermaid diagrams ==="
238+
@for MERMAID_FILE in test-diagram/*-mermaid.mermaid; do \
239+
MD_FILE="${MERMAID_FILE%.mermaid}.md"; \
240+
BASE_NAME=$(basename "$MERMAID_FILE" -mermaid.mermaid); \
241+
echo "Creating $MD_FILE"; \
242+
echo "# $BASE_NAME Network Diagram" > "$MD_FILE"; \
243+
echo '```mermaid' >> "$MD_FILE"; \
244+
cat "$MERMAID_FILE" >> "$MD_FILE"; \
245+
echo '```' >> "$MD_FILE"; \
246+
done
247+
248+
@echo ""
249+
@echo "All diagrams generated in test-diagram/ directory"
250+
@ls -la test-diagram/
251+
@echo ""
252+
@echo "Summary of generated files:"
253+
@echo "- Default VLAB topology: default-*"
254+
@echo "- 3-spine VLAB topology: 3spine-*"
255+
@echo "- Live diagrams (if VLAB running): live-*"
256+
@echo ""
257+
@echo "For each topology, these formats are available:"
258+
@echo "- DrawIO: *-drawio-{default,cisco,hedgehog}.drawio"
259+
@echo "- DOT: *-dot.dot (and PNG if GraphViz was installed)"
260+
@echo "- Mermaid: *-mermaid.mermaid (and embedded in markdown *.md)"
261+
262+
# Test lab-ci diagram
263+
test-diagram-labci:
264+
@mkdir -p test-diagram
265+
@echo "==============================================="
266+
@echo "Diagram generation test - lab-ci"
267+
@echo "==============================================="
268+
269+
# Check if the lab-ci repo is available
270+
@if [ ! -d "lab-ci" ]; then \
271+
echo "lab-ci repository not found. Please clone it:"; \
272+
echo "git clone https://github.com/githedgehog/lab-ci.git"; \
273+
exit 1; \
274+
fi
275+
276+
@echo "=== Generating diagrams for lab-ci environment 1 ==="
277+
bin/hhfab init -f -c lab-ci/envs/env-ci-1.l/hhfab.yaml -w lab-ci/envs/env-ci-1.l/wiring-0-base.yaml -w lab-ci/envs/env-ci-1.l/wiring-1-spine-leaf.yaml
278+
bin/hhfab diagram --format drawio --style default --output test-diagram/labci1-drawio-default.drawio
279+
bin/hhfab diagram --format drawio --style cisco --output test-diagram/labci1-drawio-cisco.drawio
280+
bin/hhfab diagram --format drawio --style hedgehog --output test-diagram/labci1-drawio-hedgehog.drawio
281+
bin/hhfab diagram --format dot --output test-diagram/labci1-dot.dot
282+
bin/hhfab diagram --format mermaid --output test-diagram/labci1-mermaid.mermaid
283+
284+
@echo ""
285+
@echo "All diagrams generated in test-diagram/ directory"
286+
@ls -la test-diagram/

pkg/hhfab/cmddiagram.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
kclient "sigs.k8s.io/controller-runtime/pkg/client"
1717
)
1818

19-
func Diagram(ctx context.Context, workDir, cacheDir string, live bool, format diagram.Format, style diagram.StyleType) error {
19+
func Diagram(ctx context.Context, workDir, cacheDir string, live bool, format diagram.Format, style diagram.StyleType, outputPath string) error {
2020
resultDir := filepath.Join(workDir, ResultDir)
2121
if err := os.MkdirAll(resultDir, 0o755); err != nil {
2222
return fmt.Errorf("creating result directory: %w", err)
@@ -44,7 +44,7 @@ func Diagram(ctx context.Context, workDir, cacheDir string, live bool, format di
4444
client = kube
4545
}
4646

47-
if err := diagram.Generate(ctx, resultDir, client, format, style); err != nil {
47+
if err := diagram.Generate(ctx, resultDir, client, format, style, outputPath); err != nil {
4848
return fmt.Errorf("generating diagram: %w", err)
4949
}
5050

pkg/hhfab/diagram/diagram.go

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ var Formats = []Format{
2727
FormatMermaid,
2828
}
2929

30-
func Generate(ctx context.Context, resultDir string, client kclient.Reader, format Format, style StyleType) error {
30+
func Generate(ctx context.Context, resultDir string, client kclient.Reader, format Format, style StyleType, outputPath string) error {
3131
if !slices.Contains(Formats, format) {
3232
return fmt.Errorf("unsupported diagram format: %s", format) //nolint:goerr113
3333
}
@@ -40,39 +40,84 @@ func Generate(ctx context.Context, resultDir string, client kclient.Reader, form
4040
return fmt.Errorf("getting topology: %w", err)
4141
}
4242

43+
var filePath string
44+
var displayPath string
45+
4346
switch format {
4447
case FormatDrawio:
4548
slog.Debug("Generating draw.io diagram", "style", style)
46-
if err := GenerateDrawio(resultDir, topo, style); err != nil {
49+
if err := GenerateDrawio(resultDir, topo, style, outputPath); err != nil {
4750
return fmt.Errorf("generating draw.io diagram: %w", err)
4851
}
49-
filePath := filepath.Join("result", DrawioFilename)
50-
slog.Info("Generated draw.io diagram", "file", filePath, "style", style)
52+
if outputPath != "" {
53+
filePath = outputPath
54+
} else {
55+
filePath = filepath.Join(resultDir, DrawioFilename)
56+
}
57+
58+
displayPath = filePath
59+
workDir, err := filepath.Abs(".")
60+
if err == nil {
61+
rel, err := filepath.Rel(workDir, filePath)
62+
if err == nil {
63+
displayPath = rel
64+
}
65+
}
66+
67+
slog.Info("Generated draw.io diagram", "file", displayPath, "style", style)
5168
fmt.Printf("To use this diagram:\n")
5269
fmt.Printf("1. Open with https://app.diagrams.net/ or the desktop Draw.io application\n")
5370
fmt.Printf("2. You can edit the diagram and export to PNG, SVG, PDF or other formats\n")
5471
case FormatDot:
5572
slog.Debug("Generating DOT diagram")
56-
if err := GenerateDOT(resultDir, topo); err != nil {
73+
if err := GenerateDOT(resultDir, topo, outputPath); err != nil {
5774
return fmt.Errorf("generating DOT diagram: %w", err)
5875
}
59-
filePath := filepath.Join("result", DotFilename)
60-
slog.Info("Generated graphviz diagram", "file", filePath)
76+
if outputPath != "" {
77+
filePath = outputPath
78+
} else {
79+
filePath = filepath.Join(resultDir, DotFilename)
80+
}
81+
82+
displayPath = filePath
83+
workDir, err := filepath.Abs(".")
84+
if err == nil {
85+
rel, err := filepath.Rel(workDir, filePath)
86+
if err == nil {
87+
displayPath = rel
88+
}
89+
}
90+
91+
slog.Info("Generated graphviz diagram", "file", displayPath)
6192
fmt.Printf("To render this diagram with Graphviz:\n")
6293
fmt.Printf("1. Install Graphviz: https://graphviz.org/download/\n")
63-
fmt.Printf("2. Convert to PNG: dot -Tpng %s -o diagram.png\n", filePath)
64-
fmt.Printf("3. Convert to SVG: dot -Tsvg %s -o diagram.svg\n", filePath)
65-
fmt.Printf("4. Convert to PDF: dot -Tpdf %s -o diagram.pdf\n", filePath)
94+
fmt.Printf("2. Convert to PNG: dot -Tpng %s -o diagram.png\n", displayPath)
95+
fmt.Printf("3. Convert to SVG: dot -Tsvg %s -o diagram.svg\n", displayPath)
96+
fmt.Printf("4. Convert to PDF: dot -Tpdf %s -o diagram.pdf\n", displayPath)
6697
case FormatMermaid:
6798
slog.Debug("Generating Mermaid diagram")
68-
if err := GenerateMermaid(resultDir, topo); err != nil {
99+
if err := GenerateMermaid(resultDir, topo, outputPath); err != nil {
69100
return fmt.Errorf("generating Mermaid diagram: %w", err)
70101
}
71-
filePath := filepath.Join("result", MermaidFilename)
72-
slog.Info("Generated Mermaid diagram", "file", filePath)
102+
if outputPath != "" {
103+
filePath = outputPath
104+
} else {
105+
filePath = filepath.Join(resultDir, MermaidFilename)
106+
}
107+
108+
displayPath = filePath
109+
workDir, err := filepath.Abs(".")
110+
if err == nil {
111+
rel, err := filepath.Rel(workDir, filePath)
112+
if err == nil {
113+
displayPath = rel
114+
}
115+
}
116+
117+
slog.Info("Generated Mermaid diagram", "file", displayPath)
73118
fmt.Printf("To render this diagram with Mermaid:\n")
74119
fmt.Printf("1. Visit https://mermaid.live/ or use a Markdown editor with Mermaid support\n")
75-
fmt.Printf("2. Copy the contents of %s into the editor\n", filePath)
120+
fmt.Printf("2. Copy the contents of %s into the editor\n", displayPath)
76121
default:
77122
return fmt.Errorf("unsupported diagram format: %s", format) //nolint:goerr113
78123
}

pkg/hhfab/diagram/drawio.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,15 @@ var nodeConnectionsMap map[string][]float64
7272

7373
var nodes []Node
7474

75-
func GenerateDrawio(workDir string, topo Topology, styleType StyleType) error {
75+
func GenerateDrawio(workDir string, topo Topology, styleType StyleType, outputPath string) error {
7676
slog.Debug("Initializing new diagram generation")
77-
outputFile := filepath.Join(workDir, DrawioFilename)
77+
78+
var finalOutputPath string
79+
if outputPath != "" {
80+
finalOutputPath = outputPath
81+
} else {
82+
finalOutputPath = filepath.Join(workDir, DrawioFilename)
83+
}
7884

7985
style := GetStyle(styleType)
8086

@@ -86,7 +92,12 @@ func GenerateDrawio(workDir string, topo Topology, styleType StyleType) error {
8692
return fmt.Errorf("marshaling XML: %w", err)
8793
}
8894
xmlContent := []byte(xml.Header + string(outputXML))
89-
if err := os.WriteFile(outputFile, xmlContent, 0o600); err != nil {
95+
96+
if err := os.MkdirAll(filepath.Dir(finalOutputPath), 0o755); err != nil {
97+
return fmt.Errorf("creating output directory: %w", err)
98+
}
99+
100+
if err := os.WriteFile(finalOutputPath, xmlContent, 0o600); err != nil {
90101
return fmt.Errorf("writing draw.io file: %w", err)
91102
}
92103

pkg/hhfab/diagram/graphviz.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,21 @@ const (
2222
ColorDefault = "black"
2323
)
2424

25-
func GenerateDOT(workDir string, topo Topology) error {
26-
outputFile := filepath.Join(workDir, DotFilename)
25+
func GenerateDOT(workDir string, topo Topology, outputPath string) error {
26+
var finalOutputPath string
27+
if outputPath != "" {
28+
finalOutputPath = outputPath
29+
} else {
30+
finalOutputPath = filepath.Join(workDir, DotFilename)
31+
}
2732

2833
dot := generateDOT(topo)
29-
if err := os.WriteFile(outputFile, []byte(dot), 0o600); err != nil {
34+
35+
if err := os.MkdirAll(filepath.Dir(finalOutputPath), 0o755); err != nil {
36+
return fmt.Errorf("creating output directory: %w", err)
37+
}
38+
39+
if err := os.WriteFile(finalOutputPath, []byte(dot), 0o600); err != nil {
3040
return fmt.Errorf("writing DOT file: %w", err)
3141
}
3242

pkg/hhfab/diagram/mermaid.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,21 @@ import (
1212
"strings"
1313
)
1414

15-
func GenerateMermaid(workDir string, topo Topology) error {
16-
outputFile := filepath.Join(workDir, MermaidFilename)
15+
func GenerateMermaid(workDir string, topo Topology, outputPath string) error {
16+
var finalOutputPath string
17+
if outputPath != "" {
18+
finalOutputPath = outputPath
19+
} else {
20+
finalOutputPath = filepath.Join(workDir, MermaidFilename)
21+
}
22+
1723
mermaid := generateMermaid(topo)
18-
if err := os.WriteFile(outputFile, []byte(mermaid), 0o600); err != nil {
24+
25+
if err := os.MkdirAll(filepath.Dir(finalOutputPath), 0o755); err != nil {
26+
return fmt.Errorf("creating output directory: %w", err)
27+
}
28+
29+
if err := os.WriteFile(finalOutputPath, []byte(mermaid), 0o600); err != nil {
1930
return fmt.Errorf("writing Mermaid file: %w", err)
2031
}
2132

0 commit comments

Comments
 (0)