Skip to content

Commit f6f20b4

Browse files
committed
Added support to export svg and json file formats
1 parent a853acf commit f6f20b4

10 files changed

Lines changed: 106 additions & 26 deletions

File tree

.DS_Store

-6 KB
Binary file not shown.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.DS_Store

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@ Flags:
1717
-p, --password string password to connect to the host
1818
-u, --username string username to connect to the host
1919
roopesh:~/ $ netmap create -n ok270 -u admin -p password
20-
```
20+
```
21+
22+
<figure>
23+
<img src="./graph.png" alt="Graph created by netmap">
24+
<figcaption>Graph created by Netmap</figcaption>
25+
</figure>

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
)
1010

1111
require (
12+
aqwari.net/xml v0.0.0-20210331023308-d9421b293817 // indirect
1213
github.com/disintegration/imaging v1.6.2 // indirect
1314
github.com/flopp/go-findfont v0.1.0 // indirect
1415
github.com/fogleman/gg v1.3.0 // indirect
@@ -19,5 +20,6 @@ require (
1920
github.com/tetratelabs/wazero v1.8.1 // indirect
2021
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec // indirect
2122
golang.org/x/image v0.21.0 // indirect
23+
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc // indirect
2224
golang.org/x/text v0.19.0 // indirect
2325
)

go.sum

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
aqwari.net/xml v0.0.0-20210331023308-d9421b293817 h1:+3Rh5EaTzNLnzWx3/uy/mAaH/dGI7svJ6e0oOIDcPuE=
2+
aqwari.net/xml v0.0.0-20210331023308-d9421b293817/go.mod h1:c7kkWzc7HS/t8Q2DcVY8P2d1dyWNEhEVT5pL0ZHO11c=
13
github.com/aristanetworks/goeapi v1.0.0 h1:FjckkjOY32SkmKrqDyBqYu6hN7DaIJuxcii9LLdZqtQ=
24
github.com/aristanetworks/goeapi v1.0.0/go.mod h1:DcgIvssM+qcRRVICDky/ecT/Gqpx40UQDTYY8Lu/iJ0=
35
github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
@@ -28,11 +30,31 @@ github.com/tetratelabs/wazero v1.8.1 h1:NrcgVbWfkWvVc4UtT4LRLDf91PsOzDzefMdwhLfA
2830
github.com/tetratelabs/wazero v1.8.1/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
2931
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
3032
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
33+
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
34+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
35+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
36+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
3137
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
3238
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
3339
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
40+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
41+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
42+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
43+
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
44+
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
45+
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
46+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
47+
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
48+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
49+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
50+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
3451
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
3552
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
3653
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
54+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
55+
golang.org/x/tools v0.0.0-20200821192610-3366bbee4705/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
56+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
57+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
58+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
3759
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3860
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

graph.png

558 KB
Loading

internal/arista/eapi.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package arista
22

33
import (
4-
"opennetworktools/netmap/internal/utils"
5-
64
"github.com/aristanetworks/goeapi"
75
"github.com/aristanetworks/goeapi/module"
86
)
@@ -26,10 +24,5 @@ func GetNeighbors(hostname, username, password, enablePasswd string, port int) (
2624
return lldp, err
2725
}
2826

29-
err = utils.SaveStructAsJson(hostname, lldp)
30-
if err != nil {
31-
return lldp, err
32-
}
33-
3427
return lldp, nil
3528
}

internal/traverse.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import (
66
"opennetworktools/netmap/internal/arista"
77
"opennetworktools/netmap/internal/utils"
88
"opennetworktools/netmap/internal/visualizer"
9+
"strings"
10+
"time"
911
)
1012

1113
func Traverse(hostname, username, password string) {
1214
ctx := context.Background()
1315

16+
hostname = normalizeHostname(hostname)
17+
1418
// hostname := "roi460"
1519
// username := "admin"
1620
// password := ""
@@ -53,7 +57,7 @@ func Traverse(hostname, username, password string) {
5357

5458
// Process each neighbor
5559
for _, obj := range neighbors.LLDPNeighbors {
56-
neighborDevice := obj.NeighborDevice
60+
neighborDevice := normalizeHostname(obj.NeighborDevice)
5761
// fmt.Printf("\n - Found neighbor: %s", neighborDevice)
5862

5963
// Store the connection in the network map
@@ -70,17 +74,28 @@ func Traverse(hostname, username, password string) {
7074
}
7175
}
7276

77+
// Generate timestamp for filename (.json, .svg, .png)
78+
timestamp := time.Now().Format("2006-01-02_15-04-05")
79+
7380
// Exporting networkMap to a JSON file
74-
err := utils.SaveStructAsJson("networkMap", networkMap)
81+
err := utils.SaveStructAsJson("graph", networkMap, timestamp)
7582
if err != nil {
7683
fmt.Println("Error writing the file: ", err)
7784
return
7885
}
7986

8087
// Creating a graph and exporting it to a PNG file using go-graphviz
81-
err = visualizer.SaveTopologyWithGraphviz(ctx, networkMap)
88+
err = visualizer.SaveTopologyWithGraphviz(ctx, networkMap, timestamp)
8289
if err != nil {
8390
fmt.Println("Error rendering the topology: ", err)
8491
return
8592
}
8693
}
94+
95+
// Sometimes the neighbor or peer device sees the FQDN instead of hostname, which is false.
96+
// For eg. Say the hostname is "ok270" and the neigbor device sees this as "ok270.aristanetworks.com"
97+
// then an extra API call will be triggered as the hostname and FQDN didn't match.
98+
func normalizeHostname(hostname string) string {
99+
parts := strings.Split(hostname, ".")
100+
return parts[0]
101+
}

internal/utils/utils.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"path/filepath"
88
)
99

10-
func SaveStructAsJson(filename string, structData any) error {
10+
func SaveStructAsJson(filename string, structData any, timestamp string) error {
1111
jsonRes, err := json.Marshal(structData)
1212
if err != nil {
1313
return err
@@ -18,7 +18,9 @@ func SaveStructAsJson(filename string, structData any) error {
1818
return err
1919
}
2020

21-
err = os.WriteFile(appDir+filename+".json", jsonRes, 0644)
21+
jsonFilename := fmt.Sprintf("%s_%s.json", "graph", timestamp)
22+
jsonPilePath := filepath.Join(appDir, jsonFilename)
23+
err = os.WriteFile(jsonPilePath, jsonRes, 0644)
2224
if err != nil {
2325
fmt.Println("Error writing file:", err)
2426
return err

internal/visualizer/graphviz.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path/filepath"
1111

12+
"aqwari.net/xml/xmltree"
1213
"github.com/goccy/go-graphviz"
1314
"github.com/goccy/go-graphviz/cgraph"
1415
)
@@ -23,7 +24,7 @@ type Edge struct {
2324
NeighborPort string
2425
}
2526

26-
func SaveTopologyWithGraphviz(ctx context.Context, networkMap *NetworkMap) error {
27+
func SaveTopologyWithGraphviz(ctx context.Context, networkMap *NetworkMap, timestamp string) error {
2728
g, err := graphviz.New(ctx)
2829
if err != nil {
2930
return err
@@ -80,42 +81,63 @@ func SaveTopologyWithGraphviz(ctx context.Context, networkMap *NetworkMap) error
8081
// if err := g.Render(ctx, graph, "dot", &buf); err != nil {
8182
// log.Fatal(err)
8283
// }
83-
// fmt.Println(buf.String())
84+
// fmt.Printf("\n%v", buf.String())
8485

8586
// image.Image format
8687
outputImg, err := g.RenderImage(ctx, graph)
8788
if err != nil {
8889
return err
8990
}
9091

91-
// encode
92-
out := make([]byte, 0)
93-
writer := bytes.NewBuffer(out)
92+
// encode to svg
93+
outputBuf := &bytes.Buffer{}
94+
err = g.Render(ctx, graph, graphviz.SVG, outputBuf)
95+
if err != nil {
96+
return err
97+
}
9498

95-
err = png.Encode(writer, outputImg)
99+
doc, err := xmltree.Parse(outputBuf.Bytes())
96100
if err != nil {
97101
return err
98102
}
99103

104+
outputSvg := xmltree.Marshal(doc)
105+
106+
// MacOS Path: /Users/<USERNAME>/Library/Application Support/netmap
100107
appDir, err := utils.CreateDirectoryToSaveOutput()
101108
if err != nil {
102109
return err
103110
}
104111

105-
// creating a file
106-
filePath := filepath.Join(appDir, "network-topology.png")
107-
outputFile, err := os.Create(filePath)
112+
// create filenames for svg and png
113+
baseFilename := "graph"
114+
svgFilename := fmt.Sprintf("%s_%s.svg", baseFilename, timestamp)
115+
svgFilenamePath := filepath.Join(appDir, svgFilename)
116+
pngFilename := fmt.Sprintf("%s_%s.png", baseFilename, timestamp)
117+
118+
err = renderOutputToFilename(svgFilenamePath, outputSvg)
108119
if err != nil {
109120
return err
110121
}
111-
defer outputFile.Close()
112122

113-
// writing to a file
114-
_, err = outputFile.Write(writer.Bytes())
123+
// encode to png
124+
outputImgBytes := make([]byte, 0)
125+
writer := bytes.NewBuffer(outputImgBytes)
126+
127+
err = png.Encode(writer, outputImg)
128+
if err != nil {
129+
return err
130+
}
131+
132+
err = renderOutputToFilename(filepath.Join(appDir, pngFilename), writer.Bytes())
115133
if err != nil {
116134
return err
117135
}
118-
fmt.Printf("\nOutput path: %s", appDir)
136+
137+
fmt.Printf("\nExported graph to .json, .svg, and .png file formats: %s", appDir)
138+
fmt.Printf("\nTo view the SVG file, copy and paste the path in your favorite browser: %s", svgFilenamePath)
139+
140+
// TODO: Create a ZIP file with .svg, .png, .jpg, dot files.
119141

120142
// Save as PNG
121143
// filePath := "/Users/roopesh/Desktop/projects/network-mapper/network-topology.png"
@@ -127,3 +149,21 @@ func SaveTopologyWithGraphviz(ctx context.Context, networkMap *NetworkMap) error
127149

128150
return nil
129151
}
152+
153+
func renderOutputToFilename(filePath string, outData []byte) error {
154+
// creating a file
155+
// filePath := filepath.Join(appDir, "network-topology.png")
156+
outputFile, err := os.Create(filePath)
157+
if err != nil {
158+
return err
159+
}
160+
defer outputFile.Close()
161+
162+
// writing to a file
163+
_, err = outputFile.Write(outData)
164+
if err != nil {
165+
return err
166+
}
167+
168+
return nil
169+
}

0 commit comments

Comments
 (0)