Skip to content

Commit fb02dfd

Browse files
authored
Improve spacing network report output (#567)
* Refactor to use print package
1 parent dd18907 commit fb02dfd

File tree

2 files changed

+126
-90
lines changed

2 files changed

+126
-90
lines changed

cli/cmd/network_report.go

Lines changed: 11 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package cmd
22

33
import (
4-
"encoding/json"
5-
"fmt"
4+
"os"
5+
"text/tabwriter"
66
"time"
77

88
"github.com/pkg/errors"
9+
"github.com/replicatedhq/replicated/cli/print"
910
"github.com/replicatedhq/replicated/pkg/platformclient"
1011
"github.com/replicatedhq/replicated/pkg/types"
1112
"github.com/spf13/cobra"
@@ -62,12 +63,12 @@ func (r *runners) getNetworkReport(_ *cobra.Command, args []string) error {
6263

6364
// Handle watch mode
6465
if r.args.networkReportWatch {
66+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
67+
6568
// Print initial events
6669
if len(report.Events) > 0 {
67-
if r.outputFormat == "json" {
68-
printEventsJSONL(report.Events)
69-
} else {
70-
printEventsTable(report.Events, true)
70+
if err := print.NetworkEvents(r.outputFormat, w, report.Events, true); err != nil {
71+
return errors.Wrap(err, "print initial network events")
7172
}
7273
}
7374

@@ -92,10 +93,8 @@ func (r *runners) getNetworkReport(_ *cobra.Command, args []string) error {
9293

9394
// Print new events
9495
if len(newReport.Events) > 0 {
95-
if r.outputFormat == "json" {
96-
printEventsJSONL(newReport.Events)
97-
} else {
98-
printEventsTable(newReport.Events, false)
96+
if err := print.NetworkEvents(r.outputFormat, w, newReport.Events, false); err != nil {
97+
return errors.Wrap(err, "print new network events")
9998
}
10099
// Update last seen time
101100
lastEventTime = &newReport.Events[len(newReport.Events)-1].CreatedAt
@@ -105,84 +104,6 @@ func (r *runners) getNetworkReport(_ *cobra.Command, args []string) error {
105104
}
106105

107106
// Output the report (non-watch mode)
108-
return outputReport(report, r.outputFormat)
109-
}
110-
111-
func outputReport(report *types.NetworkReport, outputFormat string) error {
112-
switch outputFormat {
113-
case "json":
114-
output, err := json.MarshalIndent(report, "", " ")
115-
if err != nil {
116-
return errors.Wrap(err, "marshal report to json")
117-
}
118-
fmt.Println(string(output))
119-
case "table":
120-
if len(report.Events) == 0 {
121-
fmt.Println("No network events found.")
122-
return nil
123-
}
124-
printEventsTable(report.Events, true)
125-
default:
126-
return errors.Errorf("unsupported output format: %s", outputFormat)
127-
}
128-
return nil
129-
}
130-
131-
func printEventsTable(events []*types.NetworkEvent, includeHeader bool) {
132-
if includeHeader {
133-
fmt.Printf("%-20s %-15s %-15s %-8s %-8s %-8s %-12s %-8s %-15s %s\n",
134-
"CREATED AT", "SRC IP", "DST IP", "SRC PORT", "DST PORT", "PROTOCOL", "COMMAND", "PID", "DNS QUERY", "SERVICE")
135-
fmt.Println("---")
136-
}
137-
138-
for _, event := range events {
139-
// Parse the event data if it's JSON
140-
var eventData map[string]interface{}
141-
if err := json.Unmarshal([]byte(event.EventData), &eventData); err == nil {
142-
fmt.Printf("%-20s %-15s %-15s %-8.0f %-8.0f %-8s %-12s %-8.0f %-15s %s\n",
143-
event.CreatedAt.Format("2006-01-02 15:04:05"),
144-
getStringValue(eventData, "srcIp"),
145-
getStringValue(eventData, "dstIp"),
146-
getFloatValue(eventData, "srcPort"),
147-
getFloatValue(eventData, "dstPort"),
148-
getStringValue(eventData, "proto"),
149-
getStringValue(eventData, "comm"),
150-
getFloatValue(eventData, "pid"),
151-
getStringValue(eventData, "dnsQueryName"),
152-
getStringValue(eventData, "likelyService"))
153-
} else {
154-
// Fallback if event data is not valid JSON
155-
fmt.Printf("%-20s %s\n",
156-
event.CreatedAt.Format("2006-01-02 15:04:05"),
157-
event.EventData)
158-
}
159-
}
160-
}
161-
162-
func getStringValue(data map[string]interface{}, key string) string {
163-
if val, ok := data[key]; ok {
164-
if str, ok := val.(string); ok {
165-
return str
166-
}
167-
}
168-
return ""
169-
}
170-
171-
func getFloatValue(data map[string]interface{}, key string) float64 {
172-
if val, ok := data[key]; ok {
173-
if f, ok := val.(float64); ok {
174-
return f
175-
}
176-
}
177-
return 0
178-
}
179-
180-
func printEventsJSONL(events []*types.NetworkEvent) {
181-
for _, event := range events {
182-
output, err := json.Marshal(event)
183-
if err != nil {
184-
continue // Skip events that can't be marshaled
185-
}
186-
fmt.Println(string(output))
187-
}
107+
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
108+
return print.NetworkReport(r.outputFormat, w, report)
188109
}

cli/print/network_reports.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package print
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"text/tabwriter"
7+
"text/template"
8+
"time"
9+
10+
"github.com/replicatedhq/replicated/pkg/types"
11+
)
12+
13+
// Table formatting for network reports
14+
var networkReportTmplTableHeaderSrc = `CREATED AT SRC IP DST IP SRC PORT DST PORT PROTOCOL COMMAND PID DNS QUERY SERVICE`
15+
var networkReportTmplTableRowSrc = `{{ range . -}}
16+
{{ padding (printf "%s" (.CreatedAt | localeTime)) 20 }} {{ padding .EventData.SrcIP 15 }} {{ padding .EventData.DstIP 15 }} {{ if .EventData.SrcPort }}{{ padding (printf "%d" .EventData.SrcPort) 8 }}{{ else }}{{ padding "-" 8 }}{{ end }} {{ if .EventData.DstPort }}{{ padding (printf "%d" .EventData.DstPort) 8 }}{{ else }}{{ padding "-" 8 }}{{ end }} {{ padding .EventData.Protocol 8 }} {{ padding .EventData.Command 15 }} {{ if .EventData.PID }}{{ padding (printf "%d" .EventData.PID) 8 }}{{ else }}{{ padding "-" 8 }}{{ end }} {{ padding .EventData.DNSQueryName 20 }} {{ padding .EventData.LikelyService 15 }}
17+
{{ end }}`
18+
19+
var networkReportTmplTableSrc = fmt.Sprintln(networkReportTmplTableHeaderSrc) + networkReportTmplTableRowSrc
20+
var networkReportTmplTable = template.Must(template.New("networkReport").Funcs(funcs).Parse(networkReportTmplTableSrc))
21+
var networkReportTmplTableNoHeader = template.Must(template.New("networkReport").Funcs(funcs).Parse(networkReportTmplTableRowSrc))
22+
23+
// NetworkEventsWithData represents network events with parsed event data
24+
type NetworkEventsWithData struct {
25+
CreatedAt time.Time
26+
EventData *types.NetworkEventData
27+
}
28+
29+
// NetworkReport prints network report in various formats
30+
func NetworkReport(outputFormat string, w *tabwriter.Writer, report *types.NetworkReport) error {
31+
switch outputFormat {
32+
case "table":
33+
if len(report.Events) == 0 {
34+
_, err := fmt.Fprintln(w, "No network events found.")
35+
return err
36+
}
37+
eventsWithData, err := parseNetworkEventsData(report.Events)
38+
if err != nil {
39+
return fmt.Errorf("failed to parse network events: %v", err)
40+
}
41+
if err := networkReportTmplTable.Execute(w, eventsWithData); err != nil {
42+
return err
43+
}
44+
case "json":
45+
reportBytes, err := json.MarshalIndent(report, "", " ")
46+
if err != nil {
47+
return fmt.Errorf("failed to marshal report to json: %v", err)
48+
}
49+
if _, err := fmt.Fprintln(w, string(reportBytes)); err != nil {
50+
return err
51+
}
52+
default:
53+
return fmt.Errorf("unsupported output format: %s", outputFormat)
54+
}
55+
return w.Flush()
56+
}
57+
58+
// NetworkEvents prints network events in table format (for watch mode)
59+
func NetworkEvents(outputFormat string, w *tabwriter.Writer, events []*types.NetworkEvent, includeHeader bool) error {
60+
switch outputFormat {
61+
case "table":
62+
if len(events) == 0 {
63+
return nil
64+
}
65+
eventsWithData, err := parseNetworkEventsData(events)
66+
if err != nil {
67+
return fmt.Errorf("failed to parse network events: %v", err)
68+
}
69+
if includeHeader {
70+
if err := networkReportTmplTable.Execute(w, eventsWithData); err != nil {
71+
return err
72+
}
73+
} else {
74+
if err := networkReportTmplTableNoHeader.Execute(w, eventsWithData); err != nil {
75+
return err
76+
}
77+
}
78+
case "json":
79+
for _, event := range events {
80+
eventBytes, err := json.Marshal(event)
81+
if err != nil {
82+
continue // Skip events that can't be marshaled
83+
}
84+
if _, err := fmt.Fprintln(w, string(eventBytes)); err != nil {
85+
return err
86+
}
87+
}
88+
default:
89+
return fmt.Errorf("unsupported output format: %s", outputFormat)
90+
}
91+
return w.Flush()
92+
}
93+
94+
// parseNetworkEventsData parses the JSON event data for template consumption
95+
func parseNetworkEventsData(events []*types.NetworkEvent) ([]*NetworkEventsWithData, error) {
96+
var eventsWithData []*NetworkEventsWithData
97+
98+
for _, event := range events {
99+
var eventData types.NetworkEventData
100+
if err := json.Unmarshal([]byte(event.EventData), &eventData); err != nil {
101+
// For events that can't be parsed, create a minimal entry
102+
eventsWithData = append(eventsWithData, &NetworkEventsWithData{
103+
CreatedAt: event.CreatedAt,
104+
EventData: &types.NetworkEventData{},
105+
})
106+
} else {
107+
eventsWithData = append(eventsWithData, &NetworkEventsWithData{
108+
CreatedAt: event.CreatedAt,
109+
EventData: &eventData,
110+
})
111+
}
112+
}
113+
114+
return eventsWithData, nil
115+
}

0 commit comments

Comments
 (0)