diff --git a/pkg/netpol/connlist/conns_formatter.go b/pkg/netpol/connlist/conns_formatter.go index 4dc11c1d..6ac721ee 100644 --- a/pkg/netpol/connlist/conns_formatter.go +++ b/pkg/netpol/connlist/conns_formatter.go @@ -1,14 +1,9 @@ package connlist import ( - "bytes" - "encoding/csv" - "encoding/json" "fmt" "sort" - "strings" - "github.com/np-guard/netpol-analyzer/pkg/netpol/eval" "github.com/np-guard/netpol-analyzer/pkg/netpol/internal/common" ) @@ -52,133 +47,3 @@ func formSingleConn(conn Peer2PeerConnection) singleConnFields { connStr := common.ConnStrFromConnProperties(conn.AllProtocolsAndPorts(), conn.ProtocolsAndPorts()) return singleConnFields{Src: conn.Src().String(), Dst: conn.Dst().String(), ConnString: connStr} } - -// formatText: implements the connsFormatter interface for txt output format -type formatText struct { -} - -// returns a textual string format of connections from list of Peer2PeerConnection objects -func (t formatText) writeOutput(conns []Peer2PeerConnection) (string, error) { - connLines := make([]string, len(conns)) - for i := range conns { - connLines[i] = formSingleConn(conns[i]).string() - } - sort.Strings(connLines) - return strings.Join(connLines, newLineChar), nil -} - -// formatJSON: implements the connsFormatter interface for JSON output format -type formatJSON struct { -} - -// returns a json string form of connections from list of Peer2PeerConnection objects -func (j formatJSON) writeOutput(conns []Peer2PeerConnection) (string, error) { - // get an array of sorted conns items ([]singleConnFields) - sortedConnItems := sortConnections(conns) - jsonConns, err := json.MarshalIndent(sortedConnItems, "", " ") - if err != nil { - return "", err - } - return string(jsonConns), nil -} - -// formatDOT: implements the connsFormatter interface for dot output format -type formatDOT struct { -} - -// formats an edge line from a singleConnFields struct , to be used for dot graph -func getEdgeLine(c singleConnFields) string { - return fmt.Sprintf("\t%q -> %q [label=%q color=\"gold2\" fontcolor=\"darkgreen\"]", c.Src, c.Dst, c.ConnString) -} - -// formats a peer line for dot graph -func getPeerLine(peer eval.Peer) string { - var peerColor string - if peer.IsPeerIPType() { - peerColor = "red2" - } else { - peerColor = "blue" - } - peerName := peer.String() - return fmt.Sprintf("\t%q [label=%q color=%q fontcolor=%q]", peerName, peerName, peerColor, peerColor) -} - -// returns a dot string form of connections from list of Peer2PeerConnection objects -func (d formatDOT) writeOutput(conns []Peer2PeerConnection) (string, error) { - edgeLines := make([]string, len(conns)) // list of edges lines - peersVisited := make(map[string]struct{}, 0) // acts as a set - peerLines := make([]string, 0) // list of peers lines - for index := range conns { - connLine := formSingleConn(conns[index]) - edgeLines[index] = getEdgeLine(connLine) - if _, ok := peersVisited[connLine.Src]; !ok { - peersVisited[connLine.Src] = struct{}{} - peerLines = append(peerLines, getPeerLine(conns[index].Src())) - } - if _, ok := peersVisited[connLine.Dst]; !ok { - peersVisited[connLine.Dst] = struct{}{} - peerLines = append(peerLines, getPeerLine(conns[index].Dst())) - } - } - // sort graph lines - sort.Strings(peerLines) - sort.Strings(edgeLines) - // collect all lines by order - allLines := []string{common.DotHeader} - allLines = append(allLines, peerLines...) - allLines = append(allLines, edgeLines...) - allLines = append(allLines, common.DotClosing) - return strings.Join(allLines, newLineChar), nil -} - -// formatCSV: implements the connsFormatter interface for csv output format -type formatCSV struct { -} - -// returns a CSV string form of connections from list of Peer2PeerConnection objects -func (cs formatCSV) writeOutput(conns []Peer2PeerConnection) (string, error) { - // get an array of sorted conns items ([]singleConnFields) - sortedConnItems := sortConnections(conns) - var headerCSV = []string{"src", "dst", "conn"} - - // writing csv rows into a buffer - buf := new(bytes.Buffer) - writer := csv.NewWriter(buf) - if err := writer.Write(headerCSV); err != nil { - return "", err - } - for _, conn := range sortedConnItems { - row := []string{conn.Src, conn.Dst, conn.ConnString} - if err := writer.Write(row); err != nil { - return "", err - } - } - writer.Flush() - return buf.String(), nil -} - -// formatMD: implements the connsFormatter interface for md output format -type formatMD struct { -} - -// formats the md output header -func getMDHeader() string { - return "| src | dst | conn |\n|-----|-----|------|" -} - -// formats a connection line for md output -func getMDLine(c singleConnFields) string { - return fmt.Sprintf("| %s | %s | %s |", c.Src, c.Dst, c.ConnString) -} - -// returns a md string form of connections from list of Peer2PeerConnection objects -func (md formatMD) writeOutput(conns []Peer2PeerConnection) (string, error) { - mdLines := make([]string, len(conns)) - for index := range conns { - mdLines[index] = getMDLine(formSingleConn(conns[index])) - } - sort.Strings(mdLines) - allLines := []string{getMDHeader()} - allLines = append(allLines, mdLines...) - return strings.Join(allLines, newLineChar), nil -} diff --git a/pkg/netpol/connlist/conns_formatter_csv.go b/pkg/netpol/connlist/conns_formatter_csv.go new file mode 100644 index 00000000..df82c1bf --- /dev/null +++ b/pkg/netpol/connlist/conns_formatter_csv.go @@ -0,0 +1,32 @@ +package connlist + +import ( + "bytes" + "encoding/csv" +) + +// formatCSV: implements the connsFormatter interface for csv output format +type formatCSV struct { +} + +// returns a CSV string form of connections from list of Peer2PeerConnection objects +func (cs formatCSV) writeOutput(conns []Peer2PeerConnection) (string, error) { + // get an array of sorted conns items ([]singleConnFields) + sortedConnItems := sortConnections(conns) + var headerCSV = []string{"src", "dst", "conn"} + + // writing csv rows into a buffer + buf := new(bytes.Buffer) + writer := csv.NewWriter(buf) + if err := writer.Write(headerCSV); err != nil { + return "", err + } + for _, conn := range sortedConnItems { + row := []string{conn.Src, conn.Dst, conn.ConnString} + if err := writer.Write(row); err != nil { + return "", err + } + } + writer.Flush() + return buf.String(), nil +} diff --git a/pkg/netpol/connlist/conns_formatter_dot.go b/pkg/netpol/connlist/conns_formatter_dot.go new file mode 100644 index 00000000..e1164bff --- /dev/null +++ b/pkg/netpol/connlist/conns_formatter_dot.go @@ -0,0 +1,103 @@ +package connlist + +import ( + "fmt" + "sort" + "strings" + + "github.com/np-guard/netpol-analyzer/pkg/netpol/internal/common" +) + +const ( + ipColor = "red2" + nsPeerColor = "blue" +) + +// formatDOT: implements the connsFormatter interface for dot output format +type formatDOT struct { +} + +// formats an edge line from a singleConnFields struct , to be used for dot graph +func getEdgeLine(c Peer2PeerConnection) string { + connStr := common.ConnStrFromConnProperties(c.AllProtocolsAndPorts(), c.ProtocolsAndPorts()) + srcName, _ := peerNameAndColorByType(c.Src()) + dstName, _ := peerNameAndColorByType(c.Dst()) + return fmt.Sprintf("\t%q -> %q [label=%q color=\"gold2\" fontcolor=\"darkgreen\"]", srcName, dstName, connStr) +} + +func peerNameAndColorByType(peer Peer) (name, color string) { + if peer.IsPeerIPType() { + return peer.String(), ipColor + } else if peer.Name() == common.IngressPodName { + return peer.String(), nsPeerColor + } + return peer.Name(), nsPeerColor +} + +// formats a peer line for dot graph +func getPeerLine(peer Peer) string { + peerName, peerColor := peerNameAndColorByType(peer) + return fmt.Sprintf("\t\t%q [label=%q color=%q fontcolor=%q]", peerName, peerName, peerColor, peerColor) +} + +// returns a dot string form of connections from list of Peer2PeerConnection objects +func (d formatDOT) writeOutput(conns []Peer2PeerConnection) (string, error) { + nsPeers := make(map[string][]string) // map from namespace to its peers (grouping peers by namespaces) + edgeLines := make([]string, len(conns)) // list of edges lines + peersVisited := make(map[string]struct{}, 0) // acts as a set + for index := range conns { + srcStr, dstStr := conns[index].Src().String(), conns[index].Dst().String() + edgeLines[index] = getEdgeLine(conns[index]) + if _, ok := peersVisited[srcStr]; !ok { + peersVisited[srcStr] = struct{}{} + checkAndAddPeerToNsGroup(nsPeers, conns[index].Src()) + } + if _, ok := peersVisited[dstStr]; !ok { + peersVisited[dstStr] = struct{}{} + checkAndAddPeerToNsGroup(nsPeers, conns[index].Dst()) + } + } + // sort graph lines + sort.Strings(edgeLines) + // collect all lines by order + allLines := []string{common.DotHeader} + allLines = append(allLines, addNsGroups(nsPeers)...) + allLines = append(allLines, edgeLines...) + allLines = append(allLines, common.DotClosing) + return strings.Join(allLines, newLineChar), nil +} + +func checkAndAddPeerToNsGroup(mapNsToPeers map[string][]string, peer Peer) { + if _, ok := mapNsToPeers[peer.Namespace()]; !ok { + mapNsToPeers[peer.Namespace()] = []string{} + } + mapNsToPeers[peer.Namespace()] = append(mapNsToPeers[peer.Namespace()], getPeerLine(peer)) +} + +func addNsGroups(nsPeersMap map[string][]string) []string { + res := []string{} + // sort namespaces (map's keys) to ensure same output always + nsKeys := sortMapKeys(nsPeersMap) + // write ns groups + for _, ns := range nsKeys { + peersLines := nsPeersMap[ns] + sort.Strings(peersLines) + // create ns subgraph cluster + nsLabel := strings.ReplaceAll(ns, "-", "_") + nsLines := []string{"\tsubgraph cluster_" + nsLabel + " {"} // subgraph header + nsLines = append(nsLines, peersLines...) + nsLines = append(nsLines, "\t\tlabel=\""+ns+"\"", "\t}") + // add ns section to the res + res = append(res, nsLines...) + } + return res +} + +func sortMapKeys(nsPeersMap map[string][]string) []string { + keys := make([]string, 0, len(nsPeersMap)) + for k := range nsPeersMap { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} diff --git a/pkg/netpol/connlist/conns_formatter_json.go b/pkg/netpol/connlist/conns_formatter_json.go new file mode 100644 index 00000000..d952b9e6 --- /dev/null +++ b/pkg/netpol/connlist/conns_formatter_json.go @@ -0,0 +1,18 @@ +package connlist + +import "encoding/json" + +// formatJSON: implements the connsFormatter interface for JSON output format +type formatJSON struct { +} + +// returns a json string form of connections from list of Peer2PeerConnection objects +func (j formatJSON) writeOutput(conns []Peer2PeerConnection) (string, error) { + // get an array of sorted conns items ([]singleConnFields) + sortedConnItems := sortConnections(conns) + jsonConns, err := json.MarshalIndent(sortedConnItems, "", " ") + if err != nil { + return "", err + } + return string(jsonConns), nil +} diff --git a/pkg/netpol/connlist/conns_formatter_md.go b/pkg/netpol/connlist/conns_formatter_md.go new file mode 100644 index 00000000..a7097a74 --- /dev/null +++ b/pkg/netpol/connlist/conns_formatter_md.go @@ -0,0 +1,33 @@ +package connlist + +import ( + "fmt" + "sort" + "strings" +) + +// formatMD: implements the connsFormatter interface for md output format +type formatMD struct { +} + +// formats the md output header +func getMDHeader() string { + return "| src | dst | conn |\n|-----|-----|------|" +} + +// formats a connection line for md output +func getMDLine(c singleConnFields) string { + return fmt.Sprintf("| %s | %s | %s |", c.Src, c.Dst, c.ConnString) +} + +// returns a md string form of connections from list of Peer2PeerConnection objects +func (md formatMD) writeOutput(conns []Peer2PeerConnection) (string, error) { + mdLines := make([]string, len(conns)) + for index := range conns { + mdLines[index] = getMDLine(formSingleConn(conns[index])) + } + sort.Strings(mdLines) + allLines := []string{getMDHeader()} + allLines = append(allLines, mdLines...) + return strings.Join(allLines, newLineChar), nil +} diff --git a/pkg/netpol/connlist/conns_formatter_txt.go b/pkg/netpol/connlist/conns_formatter_txt.go new file mode 100644 index 00000000..e4519b1e --- /dev/null +++ b/pkg/netpol/connlist/conns_formatter_txt.go @@ -0,0 +1,20 @@ +package connlist + +import ( + "sort" + "strings" +) + +// formatText: implements the connsFormatter interface for txt output format +type formatText struct { +} + +// returns a textual string format of connections from list of Peer2PeerConnection objects +func (t formatText) writeOutput(conns []Peer2PeerConnection) (string, error) { + connLines := make([]string, len(conns)) + for i := range conns { + connLines[i] = formSingleConn(conns[i]).string() + } + sort.Strings(connLines) + return strings.Join(connLines, newLineChar), nil +}