Skip to content

Commit

Permalink
csv, md, json are consistent with txt - two sections
Browse files Browse the repository at this point in the history
  • Loading branch information
shireenf-ibm committed Jun 9, 2024
1 parent 8c7c0d0 commit d22e48a
Show file tree
Hide file tree
Showing 107 changed files with 1,365 additions and 978 deletions.
74 changes: 55 additions & 19 deletions pkg/netpol/connlist/conns_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package connlist
import (
"fmt"
"sort"
"strings"

"k8s.io/apimachinery/pkg/labels"

Expand Down Expand Up @@ -79,6 +80,8 @@ func formSingleP2PConn(conn Peer2PeerConnection) singleConnFields {
const (
entireCluster = "entire-cluster"
exposureAnalysisHeader = "Exposure Analysis Result:"
egressExposureHeader = "Egress Exposure:"
ingressExposureHeader = "Ingress Exposure:"
)

// formSingleExposureConn returns a representation of single exposure connection fields as singleConnFields object
Expand Down Expand Up @@ -146,7 +149,7 @@ func getRepresentativePodString(podLabels map[string]string, txtOutFlag bool) st
return res
}

// following code is common for md, csv and json:
// following code is common for txt, md, csv and json:

// getConnlistAsSortedSingleConnFieldsArray returns a sorted singleConnFields list from Peer2PeerConnection list.
// creates ipMaps object if the format requires it (to be used for exposure results later)
Expand All @@ -158,38 +161,60 @@ func getConnlistAsSortedSingleConnFieldsArray(conns []Peer2PeerConnection, ipMap
}
connItems[i] = formSingleP2PConn(conns[i])
}
return sortConnFields(connItems)
return sortConnFields(connItems, true)
}

// sortConnFields returns sorted list from the given singleConnFields list
func sortConnFields(conns []singleConnFields) []singleConnFields {
// sortConnFields returns sorted list from the given singleConnFields list;
// list may be sorted by src or by dst field as required
func sortConnFields(conns []singleConnFields, sortBySrc bool) []singleConnFields {
sort.Slice(conns, func(i, j int) bool {
if conns[i].Src != conns[j].Src {
return conns[i].Src < conns[j].Src
if sortBySrc {
if conns[i].Src != conns[j].Src {
return conns[i].Src < conns[j].Src
}
return conns[i].Dst < conns[j].Dst
} // else sort by dst
if conns[i].Dst != conns[j].Dst {
return conns[i].Dst < conns[j].Dst
}
return conns[i].Dst < conns[j].Dst
return conns[i].Src < conns[j].Src
})
return conns
}

// getExposureConnsAsSortedSingleConnFieldsArray returns a sorted singleConnFields list from ExposedPeer list and ipMaps records.
func getExposureConnsAsSortedSingleConnFieldsArray(exposureConns []ExposedPeer, ipMaps ipMaps) []singleConnFields {
exposureRecords := make([]singleConnFields, 0)
// getExposureConnsAsSortedSingleConnFieldsArray returns two sorted singleConnFields of ingress exposure and ingress exposure lists from
// ExposedPeer list and ipMaps records.
// and for txt output use only, returns unprotected peers' lines
func getExposureConnsAsSortedSingleConnFieldsArray(exposureConns []ExposedPeer, ipMaps ipMaps) (ingExposure,
egExposure []singleConnFields, unprotectedLines []string) {
for _, ep := range exposureConns {
exposureRecords = append(exposureRecords, getXgressExposureConnsAsSingleConnFieldsArray(ep.ExposedPeer().String(),
true, ep.IsProtectedByIngressNetpols(), ep.IngressExposure(), ipMaps)...)
exposureRecords = append(exposureRecords, getXgressExposureConnsAsSingleConnFieldsArray(ep.ExposedPeer().String(),
false, ep.IsProtectedByEgressNetpols(), ep.EgressExposure(), ipMaps)...)
pIngExposure, ingUnprotected := getXgressExposureConnsAsSingleConnFieldsArray(ep.ExposedPeer().String(),
true, ep.IsProtectedByIngressNetpols(), ep.IngressExposure(), ipMaps)
ingExposure = append(ingExposure, pIngExposure...)
unprotectedLines = append(unprotectedLines, ingUnprotected...)
pEgExposure, egUnprotected := getXgressExposureConnsAsSingleConnFieldsArray(ep.ExposedPeer().String(),
false, ep.IsProtectedByEgressNetpols(), ep.EgressExposure(), ipMaps)
egExposure = append(egExposure, pEgExposure...)
unprotectedLines = append(unprotectedLines, egUnprotected...)
}
return sortConnFields(exposureRecords)
return sortConnFields(ingExposure, false), sortConnFields(egExposure, true), unprotectedLines
}

// getXgressExposureConnsAsSingleConnFieldsArray returns xgress data of an exposed peer as singleConnFields list
// getXgressExposureConnsAsSingleConnFieldsArray returns xgress data of an exposed peer as singleConnFields list.
// and for txt output use only, returns also the unprotected line of the peer
// if a peer is not protected, two lines are to be added to exposure analysis result:
// 1. all conns with entire cluster (added here)
// 2. all conns with ip-blocks (all destinations); for sure found in the ip conns map so will be added automatically
// also unprotected line will be added to textual output
func getXgressExposureConnsAsSingleConnFieldsArray(peerStr string, isIngress, isProtected bool,
xgressExp []XgressExposureData, ipMaps ipMaps) []singleConnFields {
xgressLines := make([]singleConnFields, 0)
xgressExp []XgressExposureData, ipMaps ipMaps) (xgressLines []singleConnFields, xgressUnprotectedLine []string) {
direction := "Ingress"
if !isIngress {
direction = "Egress"
}
if !isProtected {
xgressLines = append(xgressLines, formSingleExposureConn(peerStr, entireCluster, common.MakeConnectionSet(true), isIngress))
xgressUnprotectedLine = append(xgressUnprotectedLine, peerStr+" is not protected on "+direction)
} else { // protected
for _, data := range xgressExp {
xgressLines = append(xgressLines, formExposureItemAsSingleConnFiled(peerStr, data, isIngress))
Expand All @@ -203,5 +228,16 @@ func getXgressExposureConnsAsSingleConnFieldsArray(peerStr string, isIngress, is
if ipConns, ok := ipMap[peerStr]; ok {
xgressLines = append(xgressLines, ipConns...)
}
return xgressLines
return xgressLines, xgressUnprotectedLine
}

// writeExposureSubSection if the list is not empty returns it as string lines with the matching sub section given header
func writeExposureSubSection(lines []string, header string) string {
res := ""
if len(lines) > 0 {
res += header
res += strings.Join(lines, newLineChar)
res += newLineChar
}
return res
}
42 changes: 34 additions & 8 deletions pkg/netpol/connlist/conns_formatter_csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,21 @@ func (cs *formatCSV) writeOutput(conns []Peer2PeerConnection, exposureConns []Ex
}

// writeCsvColumnsHeader writes columns header row
func writeCsvColumnsHeader(writer *csv.Writer) error {
var headerCSV = []string{"src", "dst", "conn"}
func writeCsvColumnsHeader(writer *csv.Writer, srcFirst bool) error {
headerCSV := []string{src, dst, conn}
if !srcFirst {
headerCSV = []string{dst, src, conn}
}
return writer.Write(headerCSV)
}

// writeTableRows writes the given connections list as csv table
func writeTableRows(conns []singleConnFields, writer *csv.Writer) error {
func writeTableRows(conns []singleConnFields, writer *csv.Writer, srcFirst bool) error {
for _, conn := range conns {
row := []string{conn.Src, conn.Dst, conn.ConnString}
if !srcFirst {
row = []string{conn.Dst, conn.Src, conn.ConnString}
}
if err := writer.Write(row); err != nil {
return err
}
Expand All @@ -56,27 +62,47 @@ func writeTableRows(conns []singleConnFields, writer *csv.Writer) error {

// writeCsvConnlistTable writes csv table for the Peer2PeerConnection list
func (cs *formatCSV) writeCsvConnlistTable(conns []Peer2PeerConnection, writer *csv.Writer, saveIPConns bool) error {
err := writeCsvColumnsHeader(writer)
err := writeCsvColumnsHeader(writer, true)
if err != nil {
return err
}
cs.ipMaps = createIPMaps(saveIPConns)
// get an array of sorted conns items ([]singleConnFields), if required also save the relevant conns to ipMaps
sortedConnItems := getConnlistAsSortedSingleConnFieldsArray(conns, cs.ipMaps, saveIPConns)
return writeTableRows(sortedConnItems, writer)
return writeTableRows(sortedConnItems, writer, true)
}

// writeCsvExposureTable writes csv table for ExposedPeer list
func (cs *formatCSV) writeCsvExposureTable(exposureConns []ExposedPeer, writer *csv.Writer) error {
exposureRecords := getExposureConnsAsSortedSingleConnFieldsArray(exposureConns, cs.ipMaps)
ingressExposure, egressExposure, _ := getExposureConnsAsSortedSingleConnFieldsArray(exposureConns, cs.ipMaps)
// start new section for exposure analysis
err := writer.Write([]string{exposureAnalysisHeader, "", ""})
if err != nil {
return err
}
err = writeCsvColumnsHeader(writer)
err = writeCsvSubSection(egressExposure, false, writer)
if err != nil {
return err
}
return writeCsvSubSection(ingressExposure, true, writer)
}

// writeCsvSubSection writes new csv table with its headers for the given xgress section
func writeCsvSubSection(expData []singleConnFields, isIngress bool, writer *csv.Writer) error {
if len(expData) == 0 {
return nil
}
subHeader := egressExposureHeader
if isIngress {
subHeader = ingressExposureHeader
}
err := writer.Write([]string{subHeader, "", ""})
if err != nil {
return err
}
err = writeCsvColumnsHeader(writer, !isIngress)
if err != nil {
return err
}
return writeTableRows(exposureRecords, writer)
return writeTableRows(expData, writer, !isIngress)
}
17 changes: 14 additions & 3 deletions pkg/netpol/connlist/conns_formatter_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ const indent = " "

type jsonFields struct {
ConnlistResults []singleConnFields `json:"connlist_results"`
ExposureResults []singleConnFields `json:"exposure_results"`
ExposureResults exposureFields `json:"exposure_results"`
}

type exposureFields struct {
EgressExposure []singleConnFields `json:"egress_exposure"`
IngressExposure []singleConnFields `json:"ingress_exposure"`
}

// writeOutput returns a json string form of connections from list of Peer2PeerConnection objects
Expand All @@ -33,8 +38,14 @@ func (j *formatJSON) writeOutput(conns []Peer2PeerConnection, exposureConns []Ex
sortedConnItems := getConnlistAsSortedSingleConnFieldsArray(conns, j.ipMaps, exposureFlag)
if exposureFlag {
// get an array of sorted exposure items
exposureConnItems := getExposureConnsAsSortedSingleConnFieldsArray(exposureConns, j.ipMaps)
jsonOut := jsonFields{ConnlistResults: sortedConnItems, ExposureResults: exposureConnItems}
ingressExposureItems, egressExposureItems, _ := getExposureConnsAsSortedSingleConnFieldsArray(exposureConns, j.ipMaps)
jsonOut := jsonFields{
ConnlistResults: sortedConnItems,
ExposureResults: exposureFields{
EgressExposure: egressExposureItems,
IngressExposure: ingressExposureItems,
},
}
jsonConns, err = json.MarshalIndent(jsonOut, "", indent)
} else { // no exposure
jsonConns, err = json.MarshalIndent(sortedConnItems, "", indent)
Expand Down
63 changes: 46 additions & 17 deletions pkg/netpol/connlist/conns_formatter_md.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,49 @@ type formatMD struct {
ipMaps ipMaps
}

// getMDHeader formats the md output header
func getMDHeader() string {
return "| src | dst | conn |\n|-----|-----|------|"
const (
src = "src"
dst = "dst"
conn = "conn"
mdUnderLine = "|-----|-----|------|"
headerPrefix = "## "
subHeaderPrefix = "### "
mdRowFormat = "| %s | %s | %s |"
)

// getMDHeader formats the md output table header
func getMDHeader(srcFirst bool) string {
tableHeaderForm := mdRowFormat + newLineChar + mdUnderLine
if srcFirst {
return fmt.Sprintf(tableHeaderForm, src, dst, conn)
} // else dst first
return fmt.Sprintf(tableHeaderForm, dst, src, conn)
}

// getMDLine formats a connection line for md output
func getMDLine(c singleConnFields) string {
return fmt.Sprintf("| %s | %s | %s |", c.Src, c.Dst, c.ConnString)
func getMDLine(c singleConnFields, srcFirst bool) string {
if srcFirst {
return fmt.Sprintf(mdRowFormat, c.Src, c.Dst, c.ConnString)
} // else dst first
return fmt.Sprintf(mdRowFormat, c.Dst, c.Src, c.ConnString)
}

// writeOutput returns a md string form of connections from list of Peer2PeerConnection objects,
// and exposure analysis results from list ExposedPeer if exists
func (md *formatMD) writeOutput(conns []Peer2PeerConnection, exposureConns []ExposedPeer, exposureFlag bool) (string, error) {
connlistMdLines := md.writeMdConnlistLines(conns, exposureFlag)
allLines := []string{getMDHeader()}
allLines = append(allLines, connlistMdLines...)

// first write connlist lines
allLines := md.writeMdConnlistLines(conns, exposureFlag)
if exposureFlag {
allLines = append(allLines, "## "+exposureAnalysisHeader, getMDHeader())
exposureMdLines := md.writeMdExposureLines(exposureConns)
allLines = append(allLines, exposureMdLines...)
allLines = append(allLines, md.writeMdExposureLines(exposureConns)...)
}
return strings.Join(allLines, newLineChar), nil
}

// writeMdLines returns sorted md lines from the sorted singleConnFields list
func writeMdLines(conns []singleConnFields) []string {
func writeMdLines(conns []singleConnFields, srcFirst bool) []string {
res := make([]string, len(conns))
for i := range conns {
res[i] = getMDLine(conns[i])
res[i] = getMDLine(conns[i], srcFirst)
}
return res
}
Expand All @@ -54,11 +67,27 @@ func writeMdLines(conns []singleConnFields) []string {
func (md *formatMD) writeMdConnlistLines(conns []Peer2PeerConnection, saveIPConns bool) []string {
md.ipMaps = createIPMaps(saveIPConns)
sortedConns := getConnlistAsSortedSingleConnFieldsArray(conns, md.ipMaps, saveIPConns)
return writeMdLines(sortedConns)
connlistLines := []string{getMDHeader(true)} // connlist results are formatted: src | dst | conn
connlistLines = append(connlistLines, writeMdLines(sortedConns, true)...)
return connlistLines
}

// writeMdExposureLines returns md lines from exposure conns list
func (md *formatMD) writeMdExposureLines(exposureConns []ExposedPeer) []string {
sortedExposureConns := getExposureConnsAsSortedSingleConnFieldsArray(exposureConns, md.ipMaps)
return writeMdLines(sortedExposureConns)
exposureMdLines := []string{headerPrefix + exposureAnalysisHeader}
sortedIngExpConns, sortedEgExpConns, _ := getExposureConnsAsSortedSingleConnFieldsArray(exposureConns, md.ipMaps)
// egress exposure formatted src | dst | conn
// ingress exposure formatted: dst | src | conn
exposureMdLines = append(exposureMdLines,
writeExposureSubSection(writeMdLines(sortedEgExpConns, true), getMdSubSectionHeader(false)),
writeExposureSubSection(writeMdLines(sortedIngExpConns, false), getMdSubSectionHeader(true)))
return exposureMdLines
}

// getMdSubSectionHeader returns the headers of a new section in md result and its table's header
func getMdSubSectionHeader(isIngress bool) string {
if isIngress {
return subHeaderPrefix + ingressExposureHeader + newLineChar + getMDHeader(false) + newLineChar
}
return subHeaderPrefix + egressExposureHeader + newLineChar + getMDHeader(true) + newLineChar
}
Loading

0 comments on commit d22e48a

Please sign in to comment.