diff --git a/core/core/list.go b/core/core/list.go index 6973cece..72c664c4 100644 --- a/core/core/list.go +++ b/core/core/list.go @@ -7,14 +7,13 @@ import ( "sort" "strings" - "github.com/jwalton/gchalk" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/kubescape/kubescape/v3/core/cautils" metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer" - v2 "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/maruel/natural" - "github.com/olekukonko/tablewriter" ) var listFunc = map[string]func(context.Context, *metav1.ListPolicies) ([]string, error){ @@ -100,30 +99,19 @@ func prettyPrintListFormat(ctx context.Context, targetPolicy string, policies [] return } - policyTable := tablewriter.NewWriter(printer.GetWriter(ctx, "")) + policyTable := table.NewWriter() + policyTable.SetOutputMirror(printer.GetWriter(ctx, "")) - policyTable.SetAutoWrapText(true) header := fmt.Sprintf("Supported %s", targetPolicy) - policyTable.SetHeader([]string{header}) - policyTable.SetHeaderLine(true) - policyTable.SetRowLine(true) - policyTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - policyTable.SetAutoFormatHeaders(false) - policyTable.SetAlignment(tablewriter.ALIGN_CENTER) - policyTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) - data := v2.Matrix{} + policyTable.AppendHeader(table.Row{header}) + policyTable.Style().Options.SeparateHeader = true + policyTable.Style().Options.SeparateRows = true + policyTable.Style().Format.HeaderAlign = text.AlignLeft + policyTable.Style().Format.Header = text.FormatDefault + policyTable.Style().Format.RowAlign = text.AlignCenter + policyTable.Style().Box = table.StyleBoxRounded - controlRows := generatePolicyRows(policies) - - var headerColors []tablewriter.Colors - for range controlRows[0] { - headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor}) - } - policyTable.SetHeaderColor(headerColors...) - - data = append(data, controlRows...) - - policyTable.AppendBulk(data) + policyTable.AppendRows(generatePolicyRows(policies)) policyTable.Render() } @@ -134,40 +122,32 @@ func jsonListFormat(_ context.Context, _ string, policies []string) { } func prettyPrintControls(ctx context.Context, policies []string) { - controlsTable := tablewriter.NewWriter(printer.GetWriter(ctx, "")) + controlsTable := table.NewWriter() + controlsTable.SetOutputMirror(printer.GetWriter(ctx, "")) - controlsTable.SetAutoWrapText(false) - controlsTable.SetHeaderLine(true) - controlsTable.SetRowLine(true) - controlsTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - controlsTable.SetAutoFormatHeaders(false) - controlsTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) + controlsTable.Style().Options.SeparateHeader = true + controlsTable.Style().Options.SeparateRows = true + controlsTable.Style().Format.HeaderAlign = text.AlignLeft + controlsTable.Style().Format.Header = text.FormatDefault + controlsTable.Style().Box = table.StyleBoxRounded + controlsTable.SetColumnConfigs([]table.ColumnConfig{{Number: 1, Align: text.AlignRight}}) controlRows := generateControlRows(policies) - short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control name", "Docs", "Frameworks"}) + short := utils.CheckShortTerminalWidth(controlRows, table.Row{"Control ID", "Control name", "Docs", "Frameworks"}) if short { - controlsTable.SetAutoWrapText(false) - controlsTable.SetHeader([]string{"Controls"}) + controlsTable.AppendHeader(table.Row{"Controls"}) controlRows = shortFormatControlRows(controlRows) } else { - controlsTable.SetHeader([]string{"Control ID", "Control name", "Docs", "Frameworks"}) + controlsTable.AppendHeader(table.Row{"Control ID", "Control name", "Docs", "Frameworks"}) } - var headerColors []tablewriter.Colors - for range controlRows[0] { - headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor}) - } - controlsTable.SetHeaderColor(headerColors...) - data := v2.Matrix{} - data = append(data, controlRows...) - - controlsTable.AppendBulk(data) + controlsTable.AppendRows(controlRows) controlsTable.Render() } -func generateControlRows(policies []string) [][]string { - rows := [][]string{} +func generateControlRows(policies []string) []table.Row { + rows := make([]table.Row, 0, len(policies)) for _, control := range policies { @@ -188,7 +168,7 @@ func generateControlRows(policies []string) [][]string { docs := cautils.GetControlLink(id) - currentRow := []string{id, control, docs, strings.Replace(framework, " ", "\n", -1)} + currentRow := table.Row{id, control, docs, strings.Replace(framework, " ", "\n", -1)} rows = append(rows, currentRow) } @@ -196,20 +176,19 @@ func generateControlRows(policies []string) [][]string { return rows } -func generatePolicyRows(policies []string) [][]string { - rows := [][]string{} +func generatePolicyRows(policies []string) []table.Row { + rows := make([]table.Row, 0, len(policies)) for _, policy := range policies { - currentRow := []string{policy} - rows = append(rows, currentRow) + rows = append(rows, table.Row{policy}) } return rows } -func shortFormatControlRows(controlRows [][]string) [][]string { - rows := [][]string{} +func shortFormatControlRows(controlRows []table.Row) []table.Row { + rows := make([]table.Row, 0, len(controlRows)) for _, controlRow := range controlRows { - rows = append(rows, []string{fmt.Sprintf("Control ID"+strings.Repeat(" ", 3)+": %+v\nControl Name"+strings.Repeat(" ", 1)+": %+v\nDocs"+strings.Repeat(" ", 9)+": %+v\nFrameworks"+strings.Repeat(" ", 3)+": %+v", controlRow[0], controlRow[1], controlRow[2], strings.Replace(controlRow[3], "\n", " ", -1))}) + rows = append(rows, table.Row{fmt.Sprintf("Control ID"+strings.Repeat(" ", 3)+": %+v\nControl Name"+strings.Repeat(" ", 1)+": %+v\nDocs"+strings.Repeat(" ", 9)+": %+v\nFrameworks"+strings.Repeat(" ", 3)+": %+v", controlRow[0], controlRow[1], controlRow[2], strings.Replace(controlRow[3].(string), "\n", " ", -1))}) } return rows } diff --git a/core/core/list_test.go b/core/core/list_test.go index cd24d475..31e60c9d 100644 --- a/core/core/list_test.go +++ b/core/core/list_test.go @@ -9,6 +9,7 @@ import ( "sort" "testing" + "github.com/jedib0t/go-pretty/v6/table" metav1 "github.com/kubescape/kubescape/v3/core/meta/datastructures/v1" "github.com/stretchr/testify/assert" ) @@ -105,7 +106,7 @@ func TestGeneratePolicyRows_NonEmptyPolicyList(t *testing.T) { result := generatePolicyRows(policies) // Assert - assert.Equal(t, [][]string{{"policy1"}, {"policy2"}, {"policy3"}}, result) + assert.Equal(t, []table.Row{{"policy1"}, {"policy2"}, {"policy3"}}, result) } // Returns an empty 2D slice for an empty list of policies. @@ -122,12 +123,12 @@ func TestGeneratePolicyRows_EmptyPolicyList(t *testing.T) { // The function returns a list of rows, each containing a formatted string with control ID, control name, docs, and frameworks. func TestShortFormatControlRows_ReturnsListOfRowsWithFormattedString(t *testing.T) { - controlRows := [][]string{ + controlRows := []table.Row{ {"ID1", "Control 1", "Docs 1", "Framework 1"}, {"ID2", "Control 2", "Docs 2", "Framework 2"}, } - want := [][]string{ + want := []table.Row{ {"Control ID : ID1\nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"}, {"Control ID : ID2\nControl Name : Control 2\nDocs : Docs 2\nFrameworks : Framework 2"}, } @@ -139,12 +140,12 @@ func TestShortFormatControlRows_ReturnsListOfRowsWithFormattedString(t *testing. // The function formats the control rows correctly, replacing newlines in the frameworks column with line breaks. func TestShortFormatControlRows_FormatsControlRowsCorrectly(t *testing.T) { - controlRows := [][]string{ + controlRows := []table.Row{ {"ID1", "Control 1", "Docs 1", "Framework\n1"}, {"ID2", "Control 2", "Docs 2", "Framework\n2"}, } - want := [][]string{ + want := []table.Row{ {"Control ID : ID1\nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"}, {"Control ID : ID2\nControl Name : Control 2\nDocs : Docs 2\nFrameworks : Framework 2"}, } @@ -156,11 +157,11 @@ func TestShortFormatControlRows_FormatsControlRowsCorrectly(t *testing.T) { // The function handles a control row with an empty control ID. func TestShortFormatControlRows_HandlesControlRowWithEmptyControlID(t *testing.T) { - controlRows := [][]string{ + controlRows := []table.Row{ {"", "Control 1", "Docs 1", "Framework 1"}, } - want := [][]string{ + want := []table.Row{ {"Control ID : \nControl Name : Control 1\nDocs : Docs 1\nFrameworks : Framework 1"}, } @@ -171,11 +172,11 @@ func TestShortFormatControlRows_HandlesControlRowWithEmptyControlID(t *testing.T // The function handles a control row with an empty control name. func TestShortFormatControlRows_HandlesControlRowWithEmptyControlName(t *testing.T) { - controlRows := [][]string{ + controlRows := []table.Row{ {"ID1", "", "Docs 1", "Framework 1"}, } - want := [][]string{ + want := []table.Row{ {"Control ID : ID1\nControl Name : \nDocs : Docs 1\nFrameworks : Framework 1"}, } @@ -192,7 +193,7 @@ func TestGenerateControlRowsWithAllFields(t *testing.T) { "3|Control 3|Framework 3", } - want := [][]string{ + want := []table.Row{ {"1", "Control 1", "https://hub.armosec.io/docs/1", "Framework\n1"}, {"2", "Control 2", "https://hub.armosec.io/docs/2", "Framework\n2"}, {"3", "Control 3", "https://hub.armosec.io/docs/3", "Framework\n3"}, @@ -215,7 +216,7 @@ func TestGenerateControlRowsHandlesPoliciesWithEmptyStringOrNoPipesOrOnePipeMiss "5|Control 5||Extra 5", } - expectedRows := [][]string{ + expectedRows := []table.Row{ {"", "", "https://hub.armosec.io/docs/", ""}, {"1", "", "https://hub.armosec.io/docs/1", ""}, {"2", "Control 2", "https://hub.armosec.io/docs/2", "Framework\n2"}, @@ -252,18 +253,18 @@ func TestGenerateTableWithCorrectHeadersAndRows(t *testing.T) { os.Stdout = rescueStdout // got := buf.String() - want := `┌────────────┬──────────────┬───────────────────────────────┬────────────┐ + want := `╭────────────┬──────────────┬───────────────────────────────┬────────────╮ │ Control ID │ Control name │ Docs │ Frameworks │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ 1 │ Control 1 │ https://hub.armosec.io/docs/1 │ Framework │ -│ │ │ │ 1 │ +│ │ │ │ 1 │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ 2 │ Control 2 │ https://hub.armosec.io/docs/2 │ Framework │ -│ │ │ │ 2 │ +│ │ │ │ 2 │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ 3 │ Control 3 │ https://hub.armosec.io/docs/3 │ Framework │ -│ │ │ │ 3 │ -└────────────┴──────────────┴───────────────────────────────┴────────────┘ +│ │ │ │ 3 │ +╰────────────┴──────────────┴───────────────────────────────┴────────────╯ ` assert.Equal(t, want, string(got)) @@ -294,7 +295,7 @@ func TestGenerateTableWithMalformedPoliciesAndPrettyPrintHeadersAndRows(t *testi os.Stdout = rescueStdout - want := `┌────────────┬──────────────┬───────────────────────────────┬────────────┐ + want := `╭────────────┬──────────────┬───────────────────────────────┬────────────╮ │ Control ID │ Control name │ Docs │ Frameworks │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ │ │ https://hub.armosec.io/docs/ │ │ @@ -302,18 +303,18 @@ func TestGenerateTableWithMalformedPoliciesAndPrettyPrintHeadersAndRows(t *testi │ 1 │ │ https://hub.armosec.io/docs/1 │ │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ 2 │ Control 2 │ https://hub.armosec.io/docs/2 │ Framework │ -│ │ │ │ 2 │ +│ │ │ │ 2 │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ 3 │ Control 3 │ https://hub.armosec.io/docs/3 │ Framework │ -│ │ │ │ 3 │ +│ │ │ │ 3 │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ 4 │ │ https://hub.armosec.io/docs/4 │ Framework │ -│ │ │ │ 4 │ +│ │ │ │ 4 │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ │ │ https://hub.armosec.io/docs/ │ │ ├────────────┼──────────────┼───────────────────────────────┼────────────┤ │ 5 │ Control 5 │ https://hub.armosec.io/docs/5 │ │ -└────────────┴──────────────┴───────────────────────────────┴────────────┘ +╰────────────┴──────────────┴───────────────────────────────┴────────────╯ ` assert.Equal(t, want, string(got)) diff --git a/core/pkg/resultshandling/gotree/gotree.go b/core/pkg/resultshandling/gotree/gotree.go index 5bfba207..7376c2bf 100644 --- a/core/pkg/resultshandling/gotree/gotree.go +++ b/core/pkg/resultshandling/gotree/gotree.go @@ -9,7 +9,7 @@ const ( emptySpace = " " middleItem = "├── " continueItem = "│ " - lastItem = "└── " + lastItem = "╰── " ) type ( @@ -66,7 +66,7 @@ func (t *tree) Items() []Tree { return t.items } -// Print returns an visual representation of the tree +// Print returns a visual representation of the tree func (t *tree) Print() string { return newPrinter().Print(t) } diff --git a/core/pkg/resultshandling/gotree/gotree_test.go b/core/pkg/resultshandling/gotree/gotree_test.go index 1ec02e65..bdb12e3b 100644 --- a/core/pkg/resultshandling/gotree/gotree_test.go +++ b/core/pkg/resultshandling/gotree/gotree_test.go @@ -31,7 +31,7 @@ func TestTreePrint(t *testing.T) { tree: SimpleTreeMock(), want: "root\n" + "├── child1\n" + - "└── child2\n", + "╰── child2\n", }, { name: "SimpleTreeWithLinesMock", @@ -42,36 +42,36 @@ func TestTreePrint(t *testing.T) { "├── child3\n" + "│ Line2\n" + "│ Line3\n" + - "└── child4\n", + "╰── child4\n", }, { name: "SubTreeMock1", tree: SubTreeMock1(), want: "root\n" + - "└── child1\n" + - " └── child1.1\n", + "╰── child1\n" + + " ╰── child1.1\n", }, { name: "SubTreeMock2", tree: SubTreeMock2(), want: "root\n" + "├── child1\n" + - "│ └── child1.1\n" + + "│ ╰── child1.1\n" + "├── child2\n" + - "└── child3\n" + - " └── child3.1\n", + "╰── child3\n" + + " ╰── child3.1\n", }, { name: "SubTreeWithLinesMock", tree: SubTreeWithLinesMock(), want: "root\n" + "├── child1\n" + - "│ └── child1.1\n" + + "│ ╰── child1.1\n" + "│ Line2\n" + "│ Line3\n" + "├── child2\n" + - "└── child3\n" + - " └── child3.1\n" + + "╰── child3\n" + + " ╰── child3.1\n" + " Line2\n" + " Line3\n", }, @@ -85,8 +85,8 @@ func TestTreePrint(t *testing.T) { } func TestPrintText_LastTree(t *testing.T) { - inputText := "Root\n├── Child1\n└── Child2" - expectedOutput := "└── Root\n ├── Child1\n └── Child2\n" + inputText := "Root\n├── Child1\n╰── Child2" + expectedOutput := "╰── Root\n ├── Child1\n ╰── Child2\n" result := p.printText(inputText, []bool{}, true) @@ -94,8 +94,8 @@ func TestPrintText_LastTree(t *testing.T) { } func TestPrintText_NotLastTree(t *testing.T) { - inputText := "Root\n├── Child1\n└── Child2" - expectedOutput := "├── Root\n│ ├── Child1\n│ └── Child2\n" + inputText := "Root\n├── Child1\n╰── Child2" + expectedOutput := "├── Root\n│ ├── Child1\n│ ╰── Child2\n" result := p.printText(inputText, []bool{}, false) @@ -122,7 +122,7 @@ func Test_printer_printItems(t *testing.T) { name: "SimpleTreeMock", tree: SimpleTreeMock(), want: "├── child1\n" + - "└── child2\n", + "╰── child2\n", }, { name: "SimpleTreeWithLinesMock", @@ -132,33 +132,33 @@ func Test_printer_printItems(t *testing.T) { "├── child3\n" + "│ Line2\n" + "│ Line3\n" + - "└── child4\n", + "╰── child4\n", }, { name: "SubTreeMock1", tree: SubTreeMock1(), - want: "└── child1\n" + - " └── child1.1\n", + want: "╰── child1\n" + + " ╰── child1.1\n", }, { name: "SubTreeMock2", tree: SubTreeMock2(), want: "├── child1\n" + - "│ └── child1.1\n" + + "│ ╰── child1.1\n" + "├── child2\n" + - "└── child3\n" + - " └── child3.1\n", + "╰── child3\n" + + " ╰── child3.1\n", }, { name: "SubTreeWithLinesMock", tree: SubTreeWithLinesMock(), want: "├── child1\n" + - "│ └── child1.1\n" + + "│ ╰── child1.1\n" + "│ Line2\n" + "│ Line3\n" + "├── child2\n" + - "└── child3\n" + - " └── child3.1\n" + + "╰── child3\n" + + " ╰── child3.1\n" + " Line2\n" + " Line3\n", }, diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter.go b/core/pkg/resultshandling/printer/v2/prettyprinter.go index 451c9813..197c581c 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter.go @@ -10,6 +10,8 @@ import ( "github.com/anchore/clio" "github.com/anchore/grype/grype/presenter/models" "github.com/enescakir/emoji" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/jwalton/gchalk" "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" @@ -21,7 +23,6 @@ import ( "github.com/kubescape/opa-utils/objectsenvelopes" "github.com/kubescape/opa-utils/reporthandling/apis" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" - "github.com/olekukonko/tablewriter" "k8s.io/utils/strings/slices" ) @@ -173,20 +174,20 @@ func (pp *PrettyPrinter) printHeader(opaSessionObj *cautils.OPASessionObj) { } else if pp.scanType == cautils.ScanTypeWorkload { cautils.InfoDisplay(pp.writer, "Workload security posture overview for:\n") ns := opaSessionObj.SingleResourceScan.GetNamespace() - rows := [][]string{} + var rows []table.Row if ns != "" { - rows = append(rows, []string{"Namespace", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetNamespace())}) + rows = append(rows, table.Row{"Namespace", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetNamespace())}) } - rows = append(rows, []string{"Kind", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetKind())}) - rows = append(rows, []string{"Name", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetName())}) + rows = append(rows, table.Row{"Kind", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetKind())}) + rows = append(rows, table.Row{"Name", gchalk.WithBrightWhite().Bold(opaSessionObj.SingleResourceScan.GetName())}) - table := tablewriter.NewWriter(pp.writer) + tableWriter := table.NewWriter() + tableWriter.SetOutputMirror(pp.writer) - table.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT}) - table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) - table.AppendBulk(rows) + tableWriter.SetColumnConfigs([]table.ColumnConfig{{Number: 1, Align: text.AlignRight}, {Number: 2, Align: text.AlignLeft}}) + tableWriter.AppendRows(rows) - table.Render() + tableWriter.Render() cautils.SimpleDisplay(pp.writer, "\nIn this overview, Kubescape shows you a summary of the security posture of a workload, including key controls that apply to its configuration, and the vulnerability status of the container image.\n\n\n") } @@ -208,7 +209,7 @@ func (pp *PrettyPrinter) SetWriter(ctx context.Context, outputFile string) { pp.SetMainPrinter() } -func (pp *PrettyPrinter) Score(score float32) { +func (pp *PrettyPrinter) Score(_ float32) { } func (pp *PrettyPrinter) printResults(controls *reportsummary.ControlSummaries, allResources map[string]workloadinterface.IMetadata, sortedControlIDs [][]string) { @@ -217,12 +218,12 @@ func (pp *PrettyPrinter) printResults(controls *reportsummary.ControlSummaries, controlSummary := controls.GetControl(reportsummary.EControlCriteriaID, c) // summaryDetails.Controls ListControls().All() Controls.GetControl(ca) pp.printTitle(controlSummary) pp.printResources(controlSummary, allResources) - pp.printSummary(c, controlSummary) + pp.printSummary(controlSummary) } } } -func (prettyPrinter *PrettyPrinter) printSummary(controlName string, controlSummary reportsummary.IControlSummary) { +func (prettyPrinter *PrettyPrinter) printSummary(controlSummary reportsummary.IControlSummary) { cautils.SimpleDisplay(prettyPrinter.writer, "Summary - ") cautils.SuccessDisplay(prettyPrinter.writer, "Passed:%v ", controlSummary.NumberOfResources().Passed()) cautils.WarningDisplay(prettyPrinter.writer, "Action Required:%v ", controlSummary.NumberOfResources().Skipped()) diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable.go index f54e4ba2..221330f1 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable.go @@ -3,11 +3,11 @@ package configurationprinter import ( "io" - "github.com/jwalton/gchalk" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" - "github.com/olekukonko/tablewriter" ) const ( @@ -21,15 +21,15 @@ const ( ) // initializes the table headers and column alignments based on the category type -func initCategoryTableData(categoryType CategoryType) ([]string, []int) { +func initCategoryTableData(categoryType CategoryType) (table.Row, []table.ColumnConfig) { if categoryType == TypeCounting { return getCategoryCountingTypeHeaders(), getCountingTypeAlignments() } return getCategoryStatusTypeHeaders(), getStatusTypeAlignments() } -func getCategoryStatusTypeHeaders() []string { - headers := make([]string, 3) +func getCategoryStatusTypeHeaders() table.Row { + headers := make(table.Row, 3) headers[0] = statusHeader headers[1] = controlNameHeader headers[2] = docsHeader @@ -37,8 +37,8 @@ func getCategoryStatusTypeHeaders() []string { return headers } -func getCategoryCountingTypeHeaders() []string { - headers := make([]string, 3) +func getCategoryCountingTypeHeaders() table.Row { + headers := make(table.Row, 3) headers[0] = controlNameHeader headers[1] = resourcesHeader headers[2] = runHeader @@ -46,16 +46,16 @@ func getCategoryCountingTypeHeaders() []string { return headers } -func getStatusTypeAlignments() []int { - return []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER} +func getStatusTypeAlignments() []table.ColumnConfig { + return []table.ColumnConfig{{Number: 1, Align: text.AlignCenter}, {Number: 2, Align: text.AlignLeft}, {Number: 3, Align: text.AlignCenter}} } -func getCountingTypeAlignments() []int { - return []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT} +func getCountingTypeAlignments() []table.ColumnConfig { + return []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}} } // returns a row for status type table based on the control summary -func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) []string { +func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary) table.Row { // show only passed, failed and action required controls status := controlSummary.GetStatus() @@ -63,7 +63,7 @@ func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, inf return nil } - rows := make([]string, 3) + rows := make(table.Row, 3) rows[0] = utils.GetStatusIcon(controlSummary.GetStatus().Status()) @@ -80,31 +80,26 @@ func generateCategoryStatusRow(controlSummary reportsummary.IControlSummary, inf } -func getCategoryTableWriter(writer io.Writer, headers []string, columnAligments []int) *tablewriter.Table { - table := tablewriter.NewWriter(writer) - table.SetHeader(headers) - table.SetHeaderLine(true) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAutoFormatHeaders(false) - table.SetColumnAlignment(columnAligments) - table.SetAutoWrapText(false) - table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) - var headerColors []tablewriter.Colors - for range headers { - headerColors = append(headerColors, tablewriter.Colors{tablewriter.FgHiYellowColor}) - } - table.SetHeaderColor(headerColors...) - return table +func getCategoryTableWriter(writer io.Writer, headers table.Row, columnAlignments []table.ColumnConfig) table.Writer { + tableWriter := table.NewWriter() + tableWriter.SetOutputMirror(writer) + tableWriter.AppendHeader(headers) + tableWriter.Style().Options.SeparateHeader = true + tableWriter.Style().Format.HeaderAlign = text.AlignLeft + tableWriter.Style().Format.Header = text.FormatDefault + tableWriter.SetColumnConfigs(columnAlignments) + tableWriter.Style().Box = table.StyleBoxRounded + return tableWriter } -func renderSingleCategory(writer io.Writer, categoryName string, table *tablewriter.Table, rows [][]string, infoToPrintInfo []utils.InfoStars) { +func renderSingleCategory(writer io.Writer, categoryName string, tableWriter table.Writer, rows []table.Row, infoToPrintInfo []utils.InfoStars) { cautils.InfoDisplay(writer, categoryName+"\n") - table.ClearRows() - table.AppendBulk(rows) + tableWriter.ResetRows() + tableWriter.AppendRows(rows) - table.Render() + tableWriter.Render() if len(infoToPrintInfo) > 0 { printCategoryInfo(writer, infoToPrintInfo) diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable_test.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable_test.go index 6c30b78c..ebe04fcc 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable_test.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable_test.go @@ -3,13 +3,13 @@ package configurationprinter import ( "io" "os" - "reflect" "testing" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling/apis" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" - "github.com/olekukonko/tablewriter" "github.com/stretchr/testify/assert" ) @@ -17,20 +17,20 @@ func TestInitCategoryTableData(t *testing.T) { tests := []struct { name string categoryType CategoryType - expectedHeaders []string - expectedAlignments []int + expectedHeaders table.Row + expectedAlignments []table.ColumnConfig }{ { name: "Test1", categoryType: TypeCounting, - expectedHeaders: []string{"Control name", "Resources", "View details"}, - expectedAlignments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}, + expectedHeaders: table.Row{"Control name", "Resources", "View details"}, + expectedAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}}, }, { name: "Test2", categoryType: TypeStatus, - expectedHeaders: []string{"", "Control name", "Docs"}, - expectedAlignments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}, + expectedHeaders: table.Row{"", "Control name", "Docs"}, + expectedAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignCenter}, {Number: 2, Align: text.AlignLeft}, {Number: 3, Align: text.AlignCenter}}, }, } for _, tt := range tests { @@ -42,8 +42,8 @@ func TestInitCategoryTableData(t *testing.T) { if len(alignments) != len(tt.expectedAlignments) { t.Errorf("initCategoryTableData() alignments = %v, want %v", alignments, tt.expectedAlignments) } - assert.True(t, reflect.DeepEqual(headers, tt.expectedHeaders)) - assert.True(t, reflect.DeepEqual(alignments, tt.expectedAlignments)) + assert.Equal(t, headers, tt.expectedHeaders) + assert.Equal(t, alignments, tt.expectedAlignments) }) } } @@ -88,52 +88,12 @@ func TestGetCategoryCountingTypeHeaders(t *testing.T) { } } -func TestGetStatusTypeAlignments(t *testing.T) { - alignments := getStatusTypeAlignments() - - if len(alignments) != 3 { - t.Errorf("Expected 3 alignments, got %d", len(alignments)) - } - - if alignments[0] != tablewriter.ALIGN_CENTER { - t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[0]) - } - - if alignments[1] != tablewriter.ALIGN_LEFT { - t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[1]) - } - - if alignments[2] != tablewriter.ALIGN_CENTER { - t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[2]) - } -} - -func TestGetCountingTypeAlignments(t *testing.T) { - alignments := getCountingTypeAlignments() - - if len(alignments) != 3 { - t.Errorf("Expected 3 alignments, got %d", len(alignments)) - } - - if alignments[0] != tablewriter.ALIGN_LEFT { - t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[0]) - } - - if alignments[1] != tablewriter.ALIGN_CENTER { - t.Errorf("Expected %d, got %d", tablewriter.ALIGN_CENTER, alignments[1]) - } - - if alignments[2] != tablewriter.ALIGN_LEFT { - t.Errorf("Expected %d, got %d", tablewriter.ALIGN_LEFT, alignments[2]) - } -} - func TestGenerateCategoryStatusRow(t *testing.T) { tests := []struct { name string controlSummary reportsummary.IControlSummary infoToPrintInfo []utils.InfoStars - expectedRows []string + expectedRows table.Row }{ { name: "failed control", @@ -142,7 +102,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) { Status: apis.StatusFailed, ControlID: "ctrlID", }, - expectedRows: []string{"❌", "test", "https://kubescape.io/docs/ctrlid"}, + expectedRows: table.Row{"❌", "test", "https://kubescape.io/docs/ctrlid"}, }, { name: "skipped control", @@ -154,7 +114,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) { }, ControlID: "ctrlID", }, - expectedRows: []string{"⚠️", "test", "https://kubescape.io/docs/ctrlid"}, + expectedRows: table.Row{"⚠️", "test", "https://kubescape.io/docs/ctrlid"}, infoToPrintInfo: []utils.InfoStars{ { Info: "testInfo", @@ -169,7 +129,7 @@ func TestGenerateCategoryStatusRow(t *testing.T) { Status: apis.StatusPassed, ControlID: "ctrlID", }, - expectedRows: []string{"✅", "test", "https://kubescape.io/docs/ctrlid"}, + expectedRows: table.Row{"✅", "test", "https://kubescape.io/docs/ctrlid"}, }, { name: "big name", @@ -178,13 +138,13 @@ func TestGenerateCategoryStatusRow(t *testing.T) { Status: apis.StatusFailed, ControlID: "ctrlID", }, - expectedRows: []string{"❌", "testtesttesttesttesttesttesttesttesttesttesttestte...", "https://kubescape.io/docs/ctrlid"}, + expectedRows: table.Row{"❌", "testtesttesttesttesttesttesttesttesttesttesttestte...", "https://kubescape.io/docs/ctrlid"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - row := generateCategoryStatusRow(tt.controlSummary, tt.infoToPrintInfo) + row := generateCategoryStatusRow(tt.controlSummary) assert.Equal(t, tt.expectedRows, row) }) } @@ -192,22 +152,22 @@ func TestGenerateCategoryStatusRow(t *testing.T) { func TestGetCategoryTableWriter(t *testing.T) { tests := []struct { - name string - headers []string - columnAligments []int - want string + name string + headers table.Row + columnAlignments []table.ColumnConfig + want string }{ { - name: "Test1", - headers: []string{"Control name", "Resources", "View details"}, - columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}, - want: "┌──────────────┬───────────┬──────────────┐\n│ Control name │ Resources │ View details │\n├──────────────┼───────────┼──────────────┤\n└──────────────┴───────────┴──────────────┘\n", + name: "Test1", + headers: table.Row{"Control name", "Resources", "View details"}, + columnAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}}, + want: "╭──────────────┬───────────┬──────────────╮\n│ Control name │ Resources │ View details │\n├──────────────┼───────────┼──────────────┤\n╰──────────────┴───────────┴──────────────╯\n", }, { - name: "Test2", - headers: []string{"", "Control name", "Docs"}, - columnAligments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}, - want: "┌──┬──────────────┬──────┐\n│ │ Control name │ Docs │\n├──┼──────────────┼──────┤\n└──┴──────────────┴──────┘\n", + name: "Test2", + headers: table.Row{"", "Control name", "Docs"}, + columnAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignCenter}, {Number: 2, Align: text.AlignLeft}, {Number: 3, Align: text.AlignCenter}}, + want: "╭──┬──────────────┬──────╮\n│ │ Control name │ Docs │\n├──┼──────────────┼──────┤\n╰──┴──────────────┴──────╯\n", }, } for _, tt := range tests { @@ -219,7 +179,7 @@ func TestGetCategoryTableWriter(t *testing.T) { } defer f.Close() - tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAligments) + tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAlignments) // Redirect stderr to the temporary file oldStderr := os.Stderr @@ -245,61 +205,61 @@ func TestGetCategoryTableWriter(t *testing.T) { func TestRenderSingleCategory(t *testing.T) { tests := []struct { - name string - categoryName string - rows [][]string - infoToPrintInfo []utils.InfoStars - headers []string - columnAligments []int - want string + name string + categoryName string + rows []table.Row + infoToPrintInfo []utils.InfoStars + headers table.Row + columnAlignments []table.ColumnConfig + want string }{ { name: "Test1", categoryName: "Resources", - rows: [][]string{ + rows: []table.Row{ {"Regular", "regular line", "1"}, {"Thick", "particularly thick line", "2"}, {"Double", "double line", "3"}, }, infoToPrintInfo: []utils.InfoStars{ - utils.InfoStars{ + { Stars: "1", Info: "Low severity", }, - utils.InfoStars{ + { Stars: "5", Info: "Critical severity", }, }, - headers: []string{"Control name", "Resources", "View details"}, - columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}, - want: "Resources\n┌──────────────┬─────────────────────────┬──────────────┐\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n└──────────────┴─────────────────────────┴──────────────┘\n1 Low severity\n5 Critical severity\n\n", + headers: table.Row{"Control name", "Resources", "View details"}, + columnAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}}, + want: "Resources\n╭──────────────┬─────────────────────────┬──────────────╮\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n╰──────────────┴─────────────────────────┴──────────────╯\n1 Low severity\n5 Critical severity\n\n", }, { name: "Test2", categoryName: "Control name", - rows: [][]string{ + rows: []table.Row{ {"Regular", "regular line", "1"}, {"Thick", "particularly thick line", "2"}, {"Double", "double line", "3"}, }, infoToPrintInfo: []utils.InfoStars{ - utils.InfoStars{ + { Stars: "1", Info: "Low severity", }, - utils.InfoStars{ + { Stars: "5", Info: "Critical severity", }, - utils.InfoStars{ + { Stars: "4", Info: "High severity", }, }, - headers: []string{"Control name", "Resources", "View details"}, - columnAligments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}, - want: "Control name\n┌──────────────┬─────────────────────────┬──────────────┐\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n└──────────────┴─────────────────────────┴──────────────┘\n1 Low severity\n5 Critical severity\n4 High severity\n\n", + headers: table.Row{"Control name", "Resources", "View details"}, + columnAlignments: []table.ColumnConfig{{Number: 1, Align: text.AlignLeft}, {Number: 2, Align: text.AlignCenter}, {Number: 3, Align: text.AlignLeft}}, + want: "Control name\n╭──────────────┬─────────────────────────┬──────────────╮\n│ Control name │ Resources │ View details │\n├──────────────┼─────────────────────────┼──────────────┤\n│ Regular │ regular line │ 1 │\n│ Thick │ particularly thick line │ 2 │\n│ Double │ double line │ 3 │\n╰──────────────┴─────────────────────────┴──────────────╯\n1 Low severity\n5 Critical severity\n4 High severity\n\n", }, } for _, tt := range tests { @@ -311,7 +271,7 @@ func TestRenderSingleCategory(t *testing.T) { } defer f.Close() - tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAligments) + tableWriter := getCategoryTableWriter(f, tt.headers, tt.columnAlignments) // Redirect stderr to the temporary file oldStderr := os.Stderr diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/clusterscan.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/clusterscan.go index 9e6c63a7..c0462a56 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/clusterscan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/clusterscan.go @@ -4,6 +4,7 @@ import ( "fmt" "io" + "github.com/jedib0t/go-pretty/v6/table" "github.com/jwalton/gchalk" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" @@ -17,11 +18,11 @@ func NewClusterPrinter() *ClusterPrinter { var _ TablePrinter = &ClusterPrinter{} -func (cp *ClusterPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) { +func (cp *ClusterPrinter) PrintSummaryTable(_ io.Writer, _ *reportsummary.SummaryDetails, _ [][]string) { } -func (cp *ClusterPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) { +func (cp *ClusterPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, _ [][]string) { categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapClusterControlsToCategories) @@ -38,17 +39,17 @@ func (cp *ClusterPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails func (cp *ClusterPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) { sortControlSummaries(controlSummaries) - headers, columnAligments := initCategoryTableData(categoryType) + headers, columnAlignments := initCategoryTableData(categoryType) - table := getCategoryTableWriter(writer, headers, columnAligments) + tableWriter := getCategoryTableWriter(writer, headers, columnAlignments) - var rows [][]string + var rows []table.Row for _, ctrls := range controlSummaries { - var row []string + var row table.Row if categoryType == TypeCounting { row = cp.generateCountingCategoryRow(ctrls) } else { - row = generateCategoryStatusRow(ctrls, infoToPrintInfo) + row = generateCategoryStatusRow(ctrls) } if len(row) > 0 { rows = append(rows, row) @@ -59,19 +60,19 @@ func (cp *ClusterPrinter) renderSingleCategoryTable(categoryName string, categor return } - renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo) + renderSingleCategory(writer, categoryName, tableWriter, rows, infoToPrintInfo) } -func (cp *ClusterPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary) []string { +func (cp *ClusterPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary) table.Row { - row := make([]string, 3) + row := make(table.Row, 3) row[0] = controlSummary.GetName() failedResources := controlSummary.NumberOfResources().Failed() if failedResources > 0 { - row[1] = string(gchalk.WithYellow().Bold(fmt.Sprintf("%d", failedResources))) + row[1] = gchalk.WithYellow().Bold(fmt.Sprintf("%d", failedResources)) } else { row[1] = fmt.Sprintf("%d", failedResources) } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go index 0f871301..9f467591 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go @@ -6,11 +6,11 @@ import ( "strconv" "strings" - "github.com/jwalton/gchalk" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" - "github.com/olekukonko/tablewriter" ) type FrameworkPrinter struct { @@ -38,19 +38,21 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails * // When scanning controls the framework list will be empty cautils.SimpleDisplay(writer, utils.FrameworksScoresToString(summaryDetails.ListFrameworks())+"\n") - controlCountersTable := tablewriter.NewWriter(writer) + controlCountersTable := table.NewWriter() + controlCountersTable.SetOutputMirror(writer) - controlCountersTable.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT}) - controlCountersTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) - controlCountersTable.AppendBulk(ControlCountersForSummary(summaryDetails.NumberOfControls())) + controlCountersTable.SetColumnConfigs([]table.ColumnConfig{{Number: 1, Align: text.AlignRight}, {Number: 2, Align: text.AlignLeft}}) + controlCountersTable.Style().Box = table.StyleBoxRounded + controlCountersTable.AppendRows(ControlCountersForSummary(summaryDetails.NumberOfControls())) controlCountersTable.Render() cautils.SimpleDisplay(writer, "\nFailed resources by severity:\n\n") - severityCountersTable := tablewriter.NewWriter(writer) - severityCountersTable.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT}) - severityCountersTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) - severityCountersTable.AppendBulk(renderSeverityCountersSummary(summaryDetails.GetResourcesSeverityCounters())) + severityCountersTable := table.NewWriter() + severityCountersTable.SetOutputMirror(writer) + severityCountersTable.SetColumnConfigs([]table.ColumnConfig{{Number: 1, Align: text.AlignRight}, {Number: 2, Align: text.AlignLeft}}) + severityCountersTable.Style().Box = table.StyleBoxRounded + severityCountersTable.AppendRows(renderSeverityCountersSummary(summaryDetails.GetResourcesSeverityCounters())) severityCountersTable.Render() cautils.SimpleDisplay(writer, "\n") @@ -59,14 +61,15 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails * cautils.SimpleDisplay(writer, "Run with '--verbose'/'-v' to see control failures for each resource.\n\n") } - summaryTable := tablewriter.NewWriter(writer) + summaryTable := table.NewWriter() + summaryTable.SetOutputMirror(writer) - summaryTable.SetAutoWrapText(false) - summaryTable.SetHeaderLine(true) - summaryTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - summaryTable.SetAutoFormatHeaders(false) - summaryTable.SetColumnAlignment(GetColumnsAlignments()) - summaryTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) + summaryTable.Style().Options.SeparateHeader = true + summaryTable.Style().Format.HeaderAlign = text.AlignLeft + summaryTable.Style().Format.Header = text.FormatDefault + summaryTable.Style().Format.Footer = text.FormatDefault + summaryTable.SetColumnConfigs(GetColumnsAlignments()) + summaryTable.Style().Box = table.StyleBoxRounded printAll := fp.getVerboseMode() if summaryDetails.NumberOfResources().Failed() == 0 { @@ -74,7 +77,7 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails * printAll = true } - dataRows := [][]string{} + var dataRows []table.Row infoToPrintInfo := utils.MapInfoToPrintInfo(summaryDetails.Controls) for i := len(sortedControlIDs) - 1; i >= 0; i-- { @@ -88,28 +91,23 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails * short := utils.CheckShortTerminalWidth(dataRows, GetControlTableHeaders(false)) if short { - summaryTable.SetRowLine(true) + summaryTable.Style().Options.SeparateRows = true dataRows = shortFormatRow(dataRows) } else { - summaryTable.SetColumnAlignment(GetColumnsAlignments()) + summaryTable.SetColumnConfigs(GetColumnsAlignments()) + summaryTable.Style().Format.FooterAlign = text.AlignCenter } - summaryTable.SetHeader(GetControlTableHeaders(short)) - summaryTable.SetFooter(GenerateFooter(summaryDetails, short)) + summaryTable.AppendHeader(GetControlTableHeaders(short)) + summaryTable.AppendFooter(GenerateFooter(summaryDetails, short)) - var headerColors []tablewriter.Colors - for range dataRows[0] { - headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor}) - } - summaryTable.SetHeaderColor(headerColors...) - - summaryTable.AppendBulk(dataRows) + summaryTable.AppendRows(dataRows) summaryTable.Render() utils.PrintInfo(writer, infoToPrintInfo) } -func shortFormatRow(dataRows [][]string) [][]string { - rows := [][]string{} +func shortFormatRow(dataRows []table.Row) []table.Row { + rows := make([]table.Row, 0, len(dataRows)) for _, dataRow := range dataRows { // Define the row content using a formatted string rowContent := fmt.Sprintf("Severity%s: %+v\nControl Name%s: %+v\nFailed Resources%s: %+v\nAll Resources%s: %+v\n%% Compliance-Score%s: %+v", @@ -125,22 +123,22 @@ func shortFormatRow(dataRows [][]string) [][]string { dataRow[summaryColumnComplianceScore]) // Append the formatted row content to the rows slice - rows = append(rows, []string{rowContent}) + rows = append(rows, table.Row{rowContent}) } return rows } -func (fp *FrameworkPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) { +func (fp *FrameworkPrinter) PrintCategoriesTables(_ io.Writer, _ *reportsummary.SummaryDetails, _ [][]string) { } -func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) [][]string { +func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) []table.Row { - rows := [][]string{} - rows = append(rows, []string{"Critical", utils.GetColorForVulnerabilitySeverity("Critical")(strconv.Itoa(counters.NumberOfCriticalSeverity()))}) - rows = append(rows, []string{"High", utils.GetColorForVulnerabilitySeverity("High")(strconv.Itoa(counters.NumberOfHighSeverity()))}) - rows = append(rows, []string{"Medium", utils.GetColorForVulnerabilitySeverity("Medium")(strconv.Itoa(counters.NumberOfMediumSeverity()))}) - rows = append(rows, []string{"Low", utils.GetColorForVulnerabilitySeverity("Low")(strconv.Itoa(counters.NumberOfLowSeverity()))}) + rows := make([]table.Row, 0, 4) + rows = append(rows, table.Row{"Critical", utils.GetColorForVulnerabilitySeverity("Critical")(strconv.Itoa(counters.NumberOfCriticalSeverity()))}) + rows = append(rows, table.Row{"High", utils.GetColorForVulnerabilitySeverity("High")(strconv.Itoa(counters.NumberOfHighSeverity()))}) + rows = append(rows, table.Row{"Medium", utils.GetColorForVulnerabilitySeverity("Medium")(strconv.Itoa(counters.NumberOfMediumSeverity()))}) + rows = append(rows, table.Row{"Low", utils.GetColorForVulnerabilitySeverity("Low")(strconv.Itoa(counters.NumberOfLowSeverity()))}) return rows } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan_test.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan_test.go index 2c8c553f..91c887a9 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan_test.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan_test.go @@ -5,6 +5,7 @@ import ( "os" "testing" + "github.com/jedib0t/go-pretty/v6/table" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" "github.com/stretchr/testify/assert" ) @@ -32,7 +33,7 @@ func (m *MockISeverityCounters) NumberOfLowSeverity() int { return m.LowCount } -func (m *MockISeverityCounters) Increase(severity string, amount int) { +func (m *MockISeverityCounters) Increase(_ string, _ int) { } func TestNewFrameworkPrinter(t *testing.T) { @@ -60,28 +61,28 @@ func TestGetVerboseMode(t *testing.T) { func TestShortRowFormat(t *testing.T) { tests := []struct { name string - rows [][]string - expectedRows [][]string + rows []table.Row + expectedRows []table.Row }{ { name: "Test Empty rows", - rows: [][]string{}, - expectedRows: [][]string{}, + rows: []table.Row{}, + expectedRows: []table.Row{}, }, { name: "Test Non empty row", - rows: [][]string{ + rows: []table.Row{ {"Medium", "Control 1", "2", "20", "0.8"}, }, - expectedRows: [][]string{[]string{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}}, + expectedRows: []table.Row{{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}}, }, { name: "Test Non empty rows", - rows: [][]string{ + rows: []table.Row{ {"Medium", "Control 1", "2", "20", "0.8"}, {"Low", "Control 2", "0", "30", "1.0"}, }, - expectedRows: [][]string{[]string{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}, []string{"Severity : Low\nControl Name : Control 2\nFailed Resources : 0\nAll Resources : 30\n% Compliance-Score : 1.0"}}, + expectedRows: []table.Row{{"Severity : Medium\nControl Name : Control 1\nFailed Resources : 2\nAll Resources : 20\n% Compliance-Score : 0.8"}, {"Severity : Low\nControl Name : Control 2\nFailed Resources : 0\nAll Resources : 30\n% Compliance-Score : 1.0"}}, }, } @@ -96,12 +97,12 @@ func TestRenderSeverityCountersSummary(t *testing.T) { tests := []struct { name string counters MockISeverityCounters - expected [][]string + expected []table.Row }{ { name: "All empty", counters: MockISeverityCounters{}, - expected: [][]string{[]string{"Critical", "0"}, []string{"High", "0"}, []string{"Medium", "0"}, []string{"Low", "0"}}, + expected: []table.Row{{"Critical", "0"}, {"High", "0"}, {"Medium", "0"}, {"Low", "0"}}, }, { name: "All different", @@ -111,7 +112,7 @@ func TestRenderSeverityCountersSummary(t *testing.T) { MediumCount: 27, LowCount: 37, }, - expected: [][]string{[]string{"Critical", "7"}, []string{"High", "17"}, []string{"Medium", "27"}, []string{"Low", "37"}}, + expected: []table.Row{{"Critical", "7"}, {"High", "17"}, {"Medium", "27"}, {"Low", "37"}}, }, { name: "All equal", @@ -121,7 +122,7 @@ func TestRenderSeverityCountersSummary(t *testing.T) { MediumCount: 7, LowCount: 7, }, - expected: [][]string{[]string{"Critical", "7"}, []string{"High", "7"}, []string{"Medium", "7"}, []string{"Low", "7"}}, + expected: []table.Row{{"Critical", "7"}, {"High", "7"}, {"Medium", "7"}, {"Low", "7"}}, }, } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/reposcan.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/reposcan.go index 37aa8705..e38215f6 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/reposcan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/reposcan.go @@ -5,6 +5,7 @@ import ( "io" "strings" + "github.com/jedib0t/go-pretty/v6/table" "github.com/jwalton/gchalk" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling" @@ -24,15 +25,15 @@ func NewRepoPrinter(inputPatterns []string) *RepoPrinter { var _ TablePrinter = &RepoPrinter{} -func (rp *RepoPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) { +func (rp *RepoPrinter) PrintSummaryTable(_ io.Writer, _ *reportsummary.SummaryDetails, _ [][]string) { } -func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) { +func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, _ [][]string) { categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapRepoControlsToCategories) - tableRended := false + tableRendered := false for _, id := range repoCategoriesDisplayOrder { categoryControl, ok := categoriesToCategoryControls[id] if !ok { @@ -43,10 +44,10 @@ func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *r continue } - tableRended = tableRended || rp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries)) + tableRendered = tableRendered || rp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries)) } - if !tableRended { + if !tableRendered { fmt.Fprintln(writer, gchalk.WithGreen().Bold("All controls passed. No issues found")) } @@ -55,21 +56,21 @@ func (rp *RepoPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *r func (rp *RepoPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) bool { sortControlSummaries(controlSummaries) - headers, columnAligments := initCategoryTableData(categoryType) + headers, columnAlignments := initCategoryTableData(categoryType) - table := getCategoryTableWriter(writer, headers, columnAligments) + tableWriter := getCategoryTableWriter(writer, headers, columnAlignments) - var rows [][]string + var rows []table.Row for _, ctrls := range controlSummaries { if ctrls.NumberOfResources().Failed() == 0 { continue } - var row []string + var row table.Row if categoryType == TypeCounting { row = rp.generateCountingCategoryRow(ctrls, rp.inputPatterns) } else { - row = generateCategoryStatusRow(ctrls, infoToPrintInfo) + row = generateCategoryStatusRow(ctrls) } if len(row) > 0 { rows = append(rows, row) @@ -80,18 +81,18 @@ func (rp *RepoPrinter) renderSingleCategoryTable(categoryName string, categoryTy return false } - renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo) + renderSingleCategory(writer, categoryName, tableWriter, rows, infoToPrintInfo) return true } -func (rp *RepoPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary, inputPatterns []string) []string { - rows := make([]string, 3) +func (rp *RepoPrinter) generateCountingCategoryRow(controlSummary reportsummary.IControlSummary, inputPatterns []string) table.Row { + rows := make(table.Row, 3) rows[0] = controlSummary.GetName() failedResources := controlSummary.NumberOfResources().Failed() if failedResources > 0 { - rows[1] = string(gchalk.WithYellow().Bold(fmt.Sprintf("%d", failedResources))) + rows[1] = gchalk.WithYellow().Bold(fmt.Sprintf("%d", failedResources)) } else { rows[1] = fmt.Sprintf("%d", failedResources) } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/summarytable.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/summarytable.go index 5ed8cb97..a57e919b 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/summarytable.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/summarytable.go @@ -5,11 +5,12 @@ import ( "strconv" "strings" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling/apis" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" - "github.com/olekukonko/tablewriter" ) const ( @@ -21,12 +22,12 @@ const ( _summaryRowLen = iota ) -func ControlCountersForSummary(counters reportsummary.ICounters) [][]string { - rows := [][]string{} - rows = append(rows, []string{"Controls", strconv.Itoa(counters.All())}) - rows = append(rows, []string{"Passed", strconv.Itoa(counters.Passed())}) - rows = append(rows, []string{"Failed", strconv.Itoa(counters.Failed())}) - rows = append(rows, []string{"Action Required", strconv.Itoa(counters.Skipped())}) +func ControlCountersForSummary(counters reportsummary.ICounters) []table.Row { + rows := make([]table.Row, 0, 4) + rows = append(rows, table.Row{"Controls", strconv.Itoa(counters.All())}) + rows = append(rows, table.Row{"Passed", strconv.Itoa(counters.Passed())}) + rows = append(rows, table.Row{"Failed", strconv.Itoa(counters.Failed())}) + rows = append(rows, table.Row{"Action Required", strconv.Itoa(counters.Skipped())}) return rows } @@ -35,13 +36,13 @@ func GetSeverityColumn(controlSummary reportsummary.IControlSummary) string { return utils.GetColor(apis.ControlSeverityToInt(controlSummary.GetScoreFactor()))(apis.ControlSeverityToString(controlSummary.GetScoreFactor())) } -func GetControlTableHeaders(short bool) []string { - var headers []string +func GetControlTableHeaders(short bool) table.Row { + var headers table.Row if short { - headers = make([]string, 1) + headers = make(table.Row, 1) headers[0] = "Controls" } else { - headers = make([]string, _summaryRowLen) + headers = make(table.Row, _summaryRowLen) headers[summaryColumnName] = "Control name" headers[summaryColumnCounterFailed] = "Failed resources" headers[summaryColumnCounterAll] = "All Resources" @@ -51,22 +52,22 @@ func GetControlTableHeaders(short bool) []string { return headers } -func GetColumnsAlignments() []int { - alignments := make([]int, _summaryRowLen) - alignments[summaryColumnSeverity] = tablewriter.ALIGN_CENTER - alignments[summaryColumnName] = tablewriter.ALIGN_LEFT - alignments[summaryColumnCounterFailed] = tablewriter.ALIGN_CENTER - alignments[summaryColumnCounterAll] = tablewriter.ALIGN_CENTER - alignments[summaryColumnComplianceScore] = tablewriter.ALIGN_CENTER - return alignments +func GetColumnsAlignments() []table.ColumnConfig { + return []table.ColumnConfig{ + {Number: summaryColumnSeverity + 1, Align: text.AlignCenter}, + {Number: summaryColumnName + 1, Align: text.AlignLeft}, + {Number: summaryColumnCounterFailed + 1, Align: text.AlignCenter}, + {Number: summaryColumnCounterAll + 1, Align: text.AlignCenter}, + {Number: summaryColumnComplianceScore + 1, Align: text.AlignCenter}, + } } -func GenerateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars, verbose bool) []string { - row := make([]string, _summaryRowLen) +func GenerateRow(controlSummary reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars, verbose bool) table.Row { + row := make(table.Row, _summaryRowLen) // ignore passed results if !verbose && (controlSummary.GetStatus().IsPassed()) { - return []string{} + return table.Row{} } row[summaryColumnSeverity] = GetSeverityColumn(controlSummary) @@ -98,14 +99,14 @@ func GetInfoColumn(controlSummary reportsummary.IControlSummary, infoToPrintInfo return "" } -func GenerateFooter(summaryDetails *reportsummary.SummaryDetails, short bool) []string { - var row []string +func GenerateFooter(summaryDetails *reportsummary.SummaryDetails, short bool) table.Row { + var row table.Row if short { - row = make([]string, 1) + row = make(table.Row, 1) row[0] = fmt.Sprintf("Resource Summary"+strings.Repeat(" ", 0)+"\n\nFailed Resources"+strings.Repeat(" ", 1)+": %d\nAll Resources"+strings.Repeat(" ", 4)+": %d\n%% Compliance-Score"+strings.Repeat(" ", 4)+": %.2f%%", summaryDetails.NumberOfResources().Failed(), summaryDetails.NumberOfResources().All(), summaryDetails.ComplianceScore) } else { // Severity | Control name | failed resources | all resources | % success - row = make([]string, _summaryRowLen) + row = make(table.Row, _summaryRowLen) row[summaryColumnName] = "Resource Summary" row[summaryColumnCounterFailed] = fmt.Sprintf("%d", summaryDetails.NumberOfResources().Failed()) row[summaryColumnCounterAll] = fmt.Sprintf("%d", summaryDetails.NumberOfResources().All()) diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/workloadscan.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/workloadscan.go index d3a80bd3..3f99b225 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/workloadscan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/workloadscan.go @@ -3,6 +3,7 @@ package configurationprinter import ( "io" + "github.com/jedib0t/go-pretty/v6/table" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" ) @@ -16,11 +17,11 @@ func NewWorkloadPrinter() *WorkloadPrinter { return &WorkloadPrinter{} } -func (wp *WorkloadPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) { +func (wp *WorkloadPrinter) PrintSummaryTable(_ io.Writer, _ *reportsummary.SummaryDetails, _ [][]string) { } -func (wp *WorkloadPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) { +func (wp *WorkloadPrinter) PrintCategoriesTables(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, _ [][]string) { categoriesToCategoryControls := mapCategoryToSummary(summaryDetails.ListControls(), mapWorkloadControlsToCategories) @@ -30,21 +31,20 @@ func (wp *WorkloadPrinter) PrintCategoriesTables(writer io.Writer, summaryDetail continue } - wp.renderSingleCategoryTable(categoryControl.CategoryName, mapCategoryToType[id], writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries)) + wp.renderSingleCategoryTable(categoryControl.CategoryName, writer, categoryControl.controlSummaries, utils.MapInfoToPrintInfoFromIface(categoryControl.controlSummaries)) } } -func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, categoryType CategoryType, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) { +func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, writer io.Writer, controlSummaries []reportsummary.IControlSummary, infoToPrintInfo []utils.InfoStars) { sortControlSummaries(controlSummaries) - headers, columnAligments := wp.initCategoryTableData() + headers, columnAlignments := wp.initCategoryTableData() - table := getCategoryTableWriter(writer, headers, columnAligments) + tableWriter := getCategoryTableWriter(writer, headers, columnAlignments) - var rows [][]string + var rows []table.Row for _, ctrls := range controlSummaries { - var row []string - row = generateCategoryStatusRow(ctrls, infoToPrintInfo) + row := generateCategoryStatusRow(ctrls) if len(row) > 0 { rows = append(rows, row) } @@ -54,9 +54,9 @@ func (wp *WorkloadPrinter) renderSingleCategoryTable(categoryName string, catego return } - renderSingleCategory(writer, categoryName, table, rows, infoToPrintInfo) + renderSingleCategory(writer, categoryName, tableWriter, rows, infoToPrintInfo) } -func (wp *WorkloadPrinter) initCategoryTableData() ([]string, []int) { +func (wp *WorkloadPrinter) initCategoryTableData() (table.Row, []table.ColumnConfig) { return getCategoryStatusTypeHeaders(), getStatusTypeAlignments() } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/workloadscan_test.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/workloadscan_test.go index becf19d9..b56be325 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/workloadscan_test.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/workloadscan_test.go @@ -3,17 +3,19 @@ package configurationprinter import ( "testing" - "github.com/olekukonko/tablewriter" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" + "github.com/stretchr/testify/assert" ) func TestWorkloadScan_InitCategoryTableData(t *testing.T) { expectedHeader := []string{"", "Control name", "Docs"} - expectedAlign := []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER} + expectedAlign := []table.ColumnConfig{{Number: 1, Align: text.AlignCenter}, {Number: 2, Align: text.AlignLeft}, {Number: 3, Align: text.AlignCenter}} workloadPrinter := NewWorkloadPrinter() - headers, columnAligments := workloadPrinter.initCategoryTableData() + headers, columnAlignments := workloadPrinter.initCategoryTableData() for i := range headers { if headers[i] != expectedHeader[i] { @@ -21,10 +23,8 @@ func TestWorkloadScan_InitCategoryTableData(t *testing.T) { } } - for i := range columnAligments { - if columnAligments[i] != expectedAlign[i] { - t.Errorf("Expected column alignment %d, got %d", expectedAlign[i], columnAligments[i]) - } + for i := range columnAlignments { + assert.Equal(t, expectedAlign[i], columnAlignments[i]) } } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/tablewriter_test.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/tablewriter_test.go index 07f6e3d9..92cffc9b 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/tablewriter_test.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/tablewriter_test.go @@ -42,7 +42,7 @@ func TestPrintImageScanningTable(t *testing.T) { }, }, }, - want: "┌──────────┬───────────────┬───────────┬─────────┬──────────┐\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ │\n│ Medium │ CVE-2020-0003 │ package3 │ 1.0.0 │ │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n└──────────┴───────────────┴───────────┴─────────┴──────────┘\n", + want: "╭──────────┬───────────────┬───────────┬─────────┬──────────╮\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ │\n│ Medium │ CVE-2020-0003 │ package3 │ 1.0.0 │ │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n╰──────────┴───────────────┴───────────┴─────────┴──────────╯\n", }, { name: "check fixed CVEs show versions", @@ -65,7 +65,7 @@ func TestPrintImageScanningTable(t *testing.T) { }, }, }, - want: "┌──────────┬───────────────┬───────────┬─────────┬──────────┐\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ v1,v2 │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n└──────────┴───────────────┴───────────┴─────────┴──────────┘\n", + want: "╭──────────┬───────────────┬───────────┬─────────┬──────────╮\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ v1,v2 │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n╰──────────┴───────────────┴───────────┴─────────┴──────────╯\n", }, } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils.go index 53828d60..79209fba 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils.go @@ -6,33 +6,28 @@ import ( "strings" v5 "github.com/anchore/grype/grype/db/v5" - "github.com/jwalton/gchalk" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" - "github.com/olekukonko/tablewriter" ) -func renderTable(writer io.Writer, headers []string, columnAlignments []int, rows [][]string) { - table := tablewriter.NewWriter(writer) - table.SetHeader(headers) - table.SetHeaderLine(true) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAutoFormatHeaders(false) - table.SetColumnAlignment(columnAlignments) - table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) +func renderTable(writer io.Writer, headers table.Row, columnAlignments []table.ColumnConfig, rows []table.Row) { + tableWriter := table.NewWriter() + tableWriter.SetOutputMirror(writer) + tableWriter.AppendHeader(headers) + tableWriter.Style().Options.SeparateHeader = true + tableWriter.Style().Format.HeaderAlign = text.AlignLeft + tableWriter.Style().Format.Header = text.FormatDefault + tableWriter.SetColumnConfigs(columnAlignments) + tableWriter.Style().Box = table.StyleBoxRounded - var headerColors []tablewriter.Colors - for range rows[0] { - headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor}) - } - table.SetHeaderColor(headerColors...) + tableWriter.AppendRows(rows) - table.AppendBulk(rows) - - table.Render() + tableWriter.Render() } -func generateRows(summary ImageScanSummary) [][]string { - rows := make([][]string, 0, len(summary.CVEs)) +func generateRows(summary ImageScanSummary) []table.Row { + rows := make([]table.Row, 0, len(summary.CVEs)) // sort CVEs by severity sort.Slice(summary.CVEs, func(i, j int) bool { @@ -46,8 +41,8 @@ func generateRows(summary ImageScanSummary) [][]string { return rows } -func generateRow(cve CVE) []string { - row := make([]string, 5) +func generateRow(cve CVE) table.Row { + row := make(table.Row, 5) row[imageColumnSeverity] = utils.GetColorForVulnerabilitySeverity(cve.Severity)(cve.Severity) row[imageColumnName] = cve.ID row[imageColumnComponent] = cve.Package @@ -59,13 +54,15 @@ func generateRow(cve CVE) []string { // if the CVE is not fixed, show the state } else if cve.FixedState == string(v5.WontFixState) { row[imageColumnFixedIn] = cve.FixedState + } else { + row[imageColumnFixedIn] = "" } return row } -func getImageScanningHeaders() []string { - headers := make([]string, 5) +func getImageScanningHeaders() table.Row { + headers := make(table.Row, 5) headers[imageColumnSeverity] = "Severity" headers[imageColumnName] = "Vulnerability" headers[imageColumnComponent] = "Component" @@ -74,6 +71,12 @@ func getImageScanningHeaders() []string { return headers } -func getImageScanningColumnsAlignments() []int { - return []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT} +func getImageScanningColumnsAlignments() []table.ColumnConfig { + return []table.ColumnConfig{ + {Number: 1, Align: text.AlignCenter}, + {Number: 2, Align: text.AlignLeft}, + {Number: 3, Align: text.AlignLeft}, + {Number: 4, Align: text.AlignLeft}, + {Number: 5, Align: text.AlignLeft}, + } } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils_test.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils_test.go index 7166a6a9..fe0f4a5f 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils_test.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils_test.go @@ -6,7 +6,6 @@ import ( "testing" v5 "github.com/anchore/grype/grype/db/v5" - "github.com/olekukonko/tablewriter" "github.com/stretchr/testify/assert" ) @@ -46,7 +45,7 @@ func TestRenderTable(t *testing.T) { }, }, }, - want: "┌──────────┬───────────────┬───────────┬─────────┬──────────┐\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ │\n│ Medium │ CVE-2020-0003 │ package3 │ 1.0.0 │ │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n└──────────┴───────────────┴───────────┴─────────┴──────────┘\n", + want: "╭──────────┬───────────────┬───────────┬─────────┬──────────╮\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ │\n│ Medium │ CVE-2020-0003 │ package3 │ 1.0.0 │ │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n╰──────────┴───────────────┴───────────┴─────────┴──────────╯\n", }, { name: "check fixed CVEs show versions", @@ -69,7 +68,7 @@ func TestRenderTable(t *testing.T) { }, }, }, - want: "┌──────────┬───────────────┬───────────┬─────────┬──────────┐\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ v1,v2 │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n└──────────┴───────────────┴───────────┴─────────┴──────────┘\n", + want: "╭──────────┬───────────────┬───────────┬─────────┬──────────╮\n│ Severity │ Vulnerability │ Component │ Version │ Fixed in │\n├──────────┼───────────────┼───────────┼─────────┼──────────┤\n│ High │ CVE-2020-0002 │ package2 │ 1.0.0 │ v1,v2 │\n│ Low │ CVE-2020-0001 │ package1 │ 1.0.0 │ │\n╰──────────┴───────────────┴───────────┴─────────┴──────────╯\n", }, } @@ -247,15 +246,3 @@ func TestGetImageScanningHeaders(t *testing.T) { } } } - -func TestGetImageScanningColumnsAlignments(t *testing.T) { - alignments := getImageScanningColumnsAlignments() - - expectedAlignments := []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT} - - for i := range alignments { - if alignments[i] != expectedAlignments[i] { - t.Errorf("expected %d, got %d", expectedAlignments[i], alignments[i]) - } - } -} diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils/utils.go b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils/utils.go index ad433636..bc5e6634 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils/utils.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils/utils.go @@ -6,6 +6,7 @@ import ( "os" "github.com/enescakir/emoji" + "github.com/jedib0t/go-pretty/v6/table" "github.com/jwalton/gchalk" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/opa-utils/reporthandling/apis" @@ -138,19 +139,19 @@ func GetStatusIcon(status apis.ScanningStatus) string { } } -func CheckShortTerminalWidth(rows [][]string, headers []string) bool { +func CheckShortTerminalWidth(rows []table.Row, headers table.Row) bool { maxWidth := 0 for _, row := range rows { rowWidth := 0 for idx, cell := range row { - cellLen := len(cell) + cellLen := len(cell.(string)) if cellLen > 50 { // Take only 50 characters of each sentence for counting size cellLen = 50 } - if cellLen > len(headers[idx]) { + if cellLen > len(headers[idx].(string)) { rowWidth += cellLen } else { - rowWidth += len(headers[idx]) + rowWidth += len(headers[idx].(string)) } rowWidth += 2 } diff --git a/core/pkg/resultshandling/printer/v2/resourcetable.go b/core/pkg/resultshandling/printer/v2/resourcetable.go index fa681583..92507d5c 100644 --- a/core/pkg/resultshandling/printer/v2/resourcetable.go +++ b/core/pkg/resultshandling/printer/v2/resourcetable.go @@ -3,18 +3,17 @@ package printer import ( "fmt" "regexp" - "sort" "strconv" "strings" - "github.com/jwalton/gchalk" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" "github.com/kubescape/opa-utils/reporthandling/results/v1/resourcesresults" - "github.com/olekukonko/tablewriter" ) const ( @@ -48,52 +47,35 @@ func (prettyPrinter *PrettyPrinter) resourceTable(opaSessionObj *cautils.OPASess } fmt.Fprintf(prettyPrinter.writer, "\n%s\n\n", prettyprinter.ControlCountersForResource(result.ListControlsIDs(nil))) - summaryTable := tablewriter.NewWriter(prettyPrinter.writer) + summaryTable := table.NewWriter() + summaryTable.SetOutputMirror(prettyPrinter.writer) - summaryTable.SetAutoWrapText(true) - summaryTable.SetAutoMergeCells(true) - summaryTable.SetHeaderLine(true) - summaryTable.SetRowLine(true) - summaryTable.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - summaryTable.SetAutoFormatHeaders(false) - summaryTable.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) + summaryTable.Style().Options.SeparateHeader = true + summaryTable.Style().Options.SeparateRows = true + summaryTable.Style().Format.HeaderAlign = text.AlignLeft + summaryTable.Style().Format.Header = text.FormatDefault + summaryTable.Style().Box = table.StyleBoxRounded - resourceRows := [][]string{} - if raw := generateResourceRows(result.ListControls(), &opaSessionObj.Report.SummaryDetails, resource); len(raw) > 0 { - resourceRows = append(resourceRows, raw...) - } + resourceRows := generateResourceRows(result.ListControls(), &opaSessionObj.Report.SummaryDetails, resource) short := utils.CheckShortTerminalWidth(resourceRows, generateResourceHeader(false)) if short { - summaryTable.SetAutoWrapText(false) - summaryTable.SetAutoMergeCells(false) resourceRows = shortFormatResource(resourceRows) } - summaryTable.SetHeader(generateResourceHeader(short)) + summaryTable.AppendHeader(generateResourceHeader(short)) - var headerColors []tablewriter.Colors - for range resourceRows[0] { - headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor}) - } - summaryTable.SetHeaderColor(headerColors...) - - data := Matrix{} - data = append(data, resourceRows...) - // For control scan framework will be nil - - sort.Sort(data) - summaryTable.AppendBulk(data) + summaryTable.AppendRows(resourceRows) summaryTable.Render() } } -func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl, summaryDetails *reportsummary.SummaryDetails, resource workloadinterface.IMetadata) [][]string { - rows := [][]string{} +func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl, summaryDetails *reportsummary.SummaryDetails, resource workloadinterface.IMetadata) []table.Row { + var rows []table.Row for i := range controls { - row := make([]string, _resourceRowLen) + row := make(table.Row, _resourceRowLen) if !controls[i].GetStatus(nil).IsFailed() { continue @@ -111,12 +93,13 @@ func generateResourceRows(controls []resourcesresults.ResourceAssociatedControl, rows = append(rows, row) } + return rows } func addContainerNameToAssistedRemediation(resource workloadinterface.IMetadata, paths *[]string) { for i := range *paths { - re := regexp.MustCompile(`spec\.containers\[(\d+)\]`) + re := regexp.MustCompile(`spec\.containers\[(\d+)]`) match := re.FindStringSubmatch((*paths)[i]) if len(match) == 2 { index, _ := strconv.Atoi(match[1]) @@ -128,22 +111,18 @@ func addContainerNameToAssistedRemediation(resource workloadinterface.IMetadata, } } -func generateResourceHeader(short bool) []string { - headers := make([]string, 0) - +func generateResourceHeader(short bool) table.Row { if short { - headers = append(headers, "Resources") + return table.Row{"Resources"} } else { - headers = append(headers, []string{"Severity", "Control name", "Docs", "Assisted remediation"}...) + return table.Row{"Severity", "Control name", "Docs", "Assisted remediation"} } - - return headers } -func shortFormatResource(resourceRows [][]string) [][]string { - rows := [][]string{} - for _, resourceRow := range resourceRows { - rows = append(rows, []string{fmt.Sprintf("Severity"+strings.Repeat(" ", 13)+": %+v\nControl Name"+strings.Repeat(" ", 9)+": %+v\nDocs"+strings.Repeat(" ", 17)+": %+v\nAssisted Remediation"+strings.Repeat(" ", 1)+": %+v", resourceRow[resourceColumnSeverity], resourceRow[resourceColumnName], resourceRow[resourceColumnURL], strings.Replace(resourceRow[resourceColumnPath], "\n", "\n"+strings.Repeat(" ", 23), -1))}) +func shortFormatResource(resourceRows []table.Row) []table.Row { + rows := make([]table.Row, len(resourceRows)) + for i, resourceRow := range resourceRows { + rows[i] = table.Row{fmt.Sprintf("Severity"+strings.Repeat(" ", 13)+": %+v\nControl Name"+strings.Repeat(" ", 9)+": %+v\nDocs"+strings.Repeat(" ", 17)+": %+v\nAssisted Remediation"+strings.Repeat(" ", 1)+": %+v", resourceRow[resourceColumnSeverity], resourceRow[resourceColumnName], resourceRow[resourceColumnURL], strings.Replace(resourceRow[resourceColumnPath].(string), "\n", "\n"+strings.Repeat(" ", 23), -1))} } return rows } diff --git a/core/pkg/resultshandling/printer/v2/resourcetable_test.go b/core/pkg/resultshandling/printer/v2/resourcetable_test.go index 6bf7c312..02f63fee 100644 --- a/core/pkg/resultshandling/printer/v2/resourcetable_test.go +++ b/core/pkg/resultshandling/printer/v2/resourcetable_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/armosec/armoapi-go/armotypes" + "github.com/jedib0t/go-pretty/v6/table" "github.com/kubescape/k8s-interface/workloadinterface" "github.com/kubescape/opa-utils/reporthandling/apis" "github.com/kubescape/opa-utils/reporthandling/results/v1/reportsummary" @@ -327,15 +328,15 @@ func TestFailedPathsToString(t *testing.T) { func TestShortFormatResource(t *testing.T) { // Create a test case with an empty resourceRows slice - emptyResourceRows := [][]string{} + emptyResourceRows := []table.Row{} // Create a test case with a single resource row - singleResourceRow := [][]string{ + singleResourceRow := []table.Row{ {"High", "Control1", "https://example.com/doc1", "Path1"}, } // Create a test case with multiple resource rows - multipleResourceRows := [][]string{ + multipleResourceRows := []table.Row{ {"Medium", "Control2", "https://example.com/doc2", "Path2"}, {"Low", "Control3", "https://example.com/doc3", "Path3"}, } @@ -344,11 +345,11 @@ func TestShortFormatResource(t *testing.T) { assert.Empty(t, actualRows) actualRows = shortFormatResource(singleResourceRow) - expectedRows := [][]string{{"Severity : High\nControl Name : Control1\nDocs : https://example.com/doc1\nAssisted Remediation : Path1"}} + expectedRows := []table.Row{{"Severity : High\nControl Name : Control1\nDocs : https://example.com/doc1\nAssisted Remediation : Path1"}} assert.Equal(t, expectedRows, actualRows) actualRows = shortFormatResource(multipleResourceRows) - expectedRows = [][]string{{"Severity : Medium\nControl Name : Control2\nDocs : https://example.com/doc2\nAssisted Remediation : Path2"}, + expectedRows = []table.Row{{"Severity : Medium\nControl Name : Control2\nDocs : https://example.com/doc2\nAssisted Remediation : Path2"}, {"Severity : Low\nControl Name : Control3\nDocs : https://example.com/doc3\nAssisted Remediation : Path3"}} assert.Equal(t, expectedRows, actualRows) } @@ -356,12 +357,12 @@ func TestShortFormatResource(t *testing.T) { func TestGenerateResourceHeader(t *testing.T) { // Test case 1: Short headers shortHeaders := generateResourceHeader(true) - expectedShortHeaders := []string{"Resources"} + expectedShortHeaders := table.Row{"Resources"} assert.Equal(t, expectedShortHeaders, shortHeaders) // Test case 2: Full headers fullHeaders := generateResourceHeader(false) - expectedFullHeaders := []string{"Severity", "Control name", "Docs", "Assisted remediation"} + expectedFullHeaders := table.Row{"Severity", "Control name", "Docs", "Assisted remediation"} assert.Equal(t, expectedFullHeaders, fullHeaders) } @@ -396,7 +397,7 @@ func TestGenerateResourceRows_Loop(t *testing.T) { name: "2 Failed Controls", summaryDetails: reportsummary.SummaryDetails{}, controls: []resourcesresults.ResourceAssociatedControl{ - resourcesresults.ResourceAssociatedControl{ + { ControlID: "control-1", Name: "Control 1", Status: apis.StatusInfo{}, @@ -417,7 +418,7 @@ func TestGenerateResourceRows_Loop(t *testing.T) { }, }, }, - resourcesresults.ResourceAssociatedControl{ + { ControlID: "control-2", Name: "Control 2", Status: apis.StatusInfo{}, @@ -456,7 +457,7 @@ func TestGenerateResourceRows_Loop(t *testing.T) { name: "One failed control", summaryDetails: reportsummary.SummaryDetails{}, controls: []resourcesresults.ResourceAssociatedControl{ - resourcesresults.ResourceAssociatedControl{ + { ControlID: "control-1", Name: "Control 1", Status: apis.StatusInfo{}, @@ -477,7 +478,7 @@ func TestGenerateResourceRows_Loop(t *testing.T) { }, }, }, - resourcesresults.ResourceAssociatedControl{ + { ControlID: "control-2", Name: "Control 2", Status: apis.StatusInfo{}, diff --git a/docs/getting-started.md b/docs/getting-started.md index 33ea9643..88686d11 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -28,7 +28,7 @@ Kubescape security posture overview for cluster: minikube In this overview, Kubescape shows you a summary of your cluster security posture, including the number of users who can perform administrative actions. For each result greater than 0, you should evaluate its need, and then define an exception to allow it. This baseline can be used to detect drift in future. Control plane -┌────┬─────────────────────────────────────┬──────────────────────────────────────────────┐ +╭────┬─────────────────────────────────────┬──────────────────────────────────────────────╮ │ │ Control Name │ Docs │ ├────┼─────────────────────────────────────┼──────────────────────────────────────────────┤ │ ✅ │ API server insecure port is enabled │ https://kubescape.io/docs/controls/c-0005/ │ @@ -36,10 +36,10 @@ Control plane │ ❌ │ Audit logs enabled │ https://kubescape.io/docs/controls/c-0067/ │ │ ✅ │ RBAC enabled │ https://kubescape.io/docs/controls/c-0088/ │ │ ❌ │ Secret/etcd encryption enabled │ https://kubescape.io/docs/controls/c-0066/ │ -└────┴─────────────────────────────────────┴──────────────────────────────────────────────┘ +╰────┴─────────────────────────────────────┴──────────────────────────────────────────────╯ Access control -┌─────────────────────────────────────────────────┬───────────┬────────────────────────────────────┐ +╭─────────────────────────────────────────────────┬───────────┬────────────────────────────────────╮ │ Control Name │ Resources │ View Details │ ├─────────────────────────────────────────────────┼───────────┼────────────────────────────────────┤ │ Cluster-admin binding │ 1 │ $ kubescape scan control C-0035 -v │ @@ -51,24 +51,24 @@ Access control │ Portforwarding privileges │ 1 │ $ kubescape scan control C-0063 -v │ │ Validate admission controller (mutating) │ 0 │ $ kubescape scan control C-0039 -v │ │ Validate admission controller (validating) │ 0 │ $ kubescape scan control C-0036 -v │ -└─────────────────────────────────────────────────┴───────────┴────────────────────────────────────┘ +╰─────────────────────────────────────────────────┴───────────┴────────────────────────────────────╯ Secrets -┌─────────────────────────────────────────────────┬───────────┬────────────────────────────────────┐ +╭─────────────────────────────────────────────────┬───────────┬────────────────────────────────────╮ │ Control Name │ Resources │ View Details │ ├─────────────────────────────────────────────────┼───────────┼────────────────────────────────────┤ │ Applications credentials in configuration files │ 1 │ $ kubescape scan control C-0012 -v │ -└─────────────────────────────────────────────────┴───────────┴────────────────────────────────────┘ +╰─────────────────────────────────────────────────┴───────────┴────────────────────────────────────╯ Network -┌────────────────────────┬───────────┬────────────────────────────────────┐ +╭────────────────────────┬───────────┬────────────────────────────────────╮ │ Control Name │ Resources │ View Details │ ├────────────────────────┼───────────┼────────────────────────────────────┤ │ Missing network policy │ 13 │ $ kubescape scan control C-0260 -v │ -└────────────────────────┴───────────┴────────────────────────────────────┘ +╰────────────────────────┴───────────┴────────────────────────────────────╯ Workload -┌─────────────────────────┬───────────┬────────────────────────────────────┐ +╭─────────────────────────┬───────────┬────────────────────────────────────╮ │ Control Name │ Resources │ View Details │ ├─────────────────────────┼───────────┼────────────────────────────────────┤ │ Host PID/IPC privileges │ 2 │ $ kubescape scan control C-0038 -v │ @@ -76,7 +76,7 @@ Workload │ HostPath mount │ 1 │ $ kubescape scan control C-0048 -v │ │ Non-root containers │ 6 │ $ kubescape scan control C-0013 -v │ │ Privileged container │ 1 │ $ kubescape scan control C-0057 -v │ -└─────────────────────────┴───────────┴────────────────────────────────────┘ +╰─────────────────────────┴───────────┴────────────────────────────────────╯ Highest-stake workloads ──────────────────────── diff --git a/go.mod b/go.mod index 5aabb3de..f98c87bb 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/go-git/go-git/v5 v5.13.2 github.com/google/go-containerregistry v0.20.3 github.com/google/uuid v1.6.0 + github.com/jedib0t/go-pretty/v6 v6.6.4 github.com/johnfercher/go-tree v1.1.0 github.com/johnfercher/maroto/v2 v2.2.2 github.com/json-iterator/go v1.1.12 @@ -44,7 +45,6 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/mikefarah/yq/v4 v4.29.1 github.com/moby/buildkit v0.21.0 - github.com/olekukonko/tablewriter v0.0.6-0.20230417144759-edd1a71a5576 github.com/open-policy-agent/opa v1.4.0 github.com/owenrumney/go-sarif/v2 v2.2.0 github.com/project-copacetic/copacetic v0.10.0 @@ -370,6 +370,7 @@ require ( github.com/nwaples/rardecode v1.1.3 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/oleiade/reflections v1.0.1 // indirect + github.com/olekukonko/tablewriter v0.0.6-0.20230417144759-edd1a71a5576 // indirect github.com/olvrng/ujson v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect @@ -546,9 +547,6 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect ) -// Using the forked version of tablewriter -replace github.com/olekukonko/tablewriter => github.com/kubescape/tablewriter v0.0.6-0.20231106230230-aac7d2659c94 - replace github.com/anchore/stereoscope => github.com/matthyx/stereoscope v0.0.0-20250211130420-468901f0e973 replace github.com/google/go-containerregistry => github.com/matthyx/go-containerregistry v0.0.0-20240227132928-63ceb71ae0b9 diff --git a/go.sum b/go.sum index 9135aaa5..e1a5d84b 100644 --- a/go.sum +++ b/go.sum @@ -2388,6 +2388,8 @@ github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jedib0t/go-pretty/v6 v6.6.4 h1:B51RjA+Sytv0C0Je7PHGDXZBF2JpS5dZEWWRueBLP6U= +github.com/jedib0t/go-pretty/v6 v6.6.4/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= @@ -2499,8 +2501,6 @@ github.com/kubescape/sizing-checker v0.0.0-20250323151332-73a18561dc73 h1:XEAryA github.com/kubescape/sizing-checker v0.0.0-20250323151332-73a18561dc73/go.mod h1:raFzO50jkNPNdBCFNTMn2EGQtmAOcBDvzVTJiArQvfo= github.com/kubescape/storage v0.0.184 h1:SERswcPINJLexrWmsTkwZOHqgDxdpQdMFUmtnD8m4X8= github.com/kubescape/storage v0.0.184/go.mod h1:c/m/RfadAIXRUC0JwT+18D0ei8uuT0LQdlVPy+RYvUU= -github.com/kubescape/tablewriter v0.0.6-0.20231106230230-aac7d2659c94 h1:uhabZUyrxo60JQrzGCQOp1gsJz06+6+PeDBTvXiKD7k= -github.com/kubescape/tablewriter v0.0.6-0.20231106230230-aac7d2659c94/go.mod h1:clwQfF3MN2cpaf7R6hc84aB6fQsRr+bnm66pXXSNAv8= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= @@ -2669,6 +2669,8 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= +github.com/olekukonko/tablewriter v0.0.6-0.20230417144759-edd1a71a5576 h1:V9mPAyDD3S448NMuNKE3GbGSx8vDk8KW5CXPhW4SU/g= +github.com/olekukonko/tablewriter v0.0.6-0.20230417144759-edd1a71a5576/go.mod h1:8Hf+pH6thup1sPZPD+NLg7d6vbpsdilu9CPIeikvgMQ= github.com/olvrng/ujson v1.1.0 h1:8xVUzVlqwdMVWh5d1UHBtLQ1D50nxoPuPEq9Wozs8oA= github.com/olvrng/ujson v1.1.0/go.mod h1:Mz4G3RODTUfbkKyvi0lgmPx/7vd3Saksk+1jgk8s9xo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/httphandler/go.mod b/httphandler/go.mod index c3bd2365..d6c78436 100644 --- a/httphandler/go.mod +++ b/httphandler/go.mod @@ -294,6 +294,7 @@ require ( github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jedib0t/go-pretty/v6 v6.6.4 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/jinzhu/copier v0.4.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -455,7 +456,7 @@ require ( github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect github.com/transparency-dev/merkle v0.0.2 // indirect - github.com/ulikunitz/xz v0.5.12 // indirect + github.com/ulikunitz/xz v0.5.14 // indirect github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.2 // indirect github.com/uptrace/uptrace-go v1.30.1 // indirect @@ -550,9 +551,6 @@ require ( sigs.k8s.io/yaml v1.5.0 // indirect ) -// Using the forked version of tablewriter -replace github.com/olekukonko/tablewriter => github.com/kubescape/tablewriter v0.0.6-0.20231106230230-aac7d2659c94 - replace github.com/docker/distribution v2.8.3+incompatible => github.com/docker/distribution v2.8.2+incompatible replace github.com/docker/docker v27.1.1+incompatible => github.com/docker/docker v26.1.5+incompatible diff --git a/httphandler/go.sum b/httphandler/go.sum index 271ecab2..3551f0ac 100644 --- a/httphandler/go.sum +++ b/httphandler/go.sum @@ -2394,6 +2394,8 @@ github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jedib0t/go-pretty/v6 v6.6.4 h1:B51RjA+Sytv0C0Je7PHGDXZBF2JpS5dZEWWRueBLP6U= +github.com/jedib0t/go-pretty/v6 v6.6.4/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= @@ -2503,8 +2505,6 @@ github.com/kubescape/regolibrary/v2 v2.0.1 h1:7lKj171gslgTbbcmmGVHk34AZNqxForOXZ github.com/kubescape/regolibrary/v2 v2.0.1/go.mod h1:s0/Mi9PYw7s91vIf1VJTkuu1Blsl5ZLpYn5UA7yk/vM= github.com/kubescape/storage v0.0.184 h1:SERswcPINJLexrWmsTkwZOHqgDxdpQdMFUmtnD8m4X8= github.com/kubescape/storage v0.0.184/go.mod h1:c/m/RfadAIXRUC0JwT+18D0ei8uuT0LQdlVPy+RYvUU= -github.com/kubescape/tablewriter v0.0.6-0.20231106230230-aac7d2659c94 h1:uhabZUyrxo60JQrzGCQOp1gsJz06+6+PeDBTvXiKD7k= -github.com/kubescape/tablewriter v0.0.6-0.20231106230230-aac7d2659c94/go.mod h1:clwQfF3MN2cpaf7R6hc84aB6fQsRr+bnm66pXXSNAv8= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= @@ -2667,6 +2667,8 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= +github.com/olekukonko/tablewriter v0.0.6-0.20230417144759-edd1a71a5576 h1:V9mPAyDD3S448NMuNKE3GbGSx8vDk8KW5CXPhW4SU/g= +github.com/olekukonko/tablewriter v0.0.6-0.20230417144759-edd1a71a5576/go.mod h1:8Hf+pH6thup1sPZPD+NLg7d6vbpsdilu9CPIeikvgMQ= github.com/olvrng/ujson v1.1.0 h1:8xVUzVlqwdMVWh5d1UHBtLQ1D50nxoPuPEq9Wozs8oA= github.com/olvrng/ujson v1.1.0/go.mod h1:Mz4G3RODTUfbkKyvi0lgmPx/7vd3Saksk+1jgk8s9xo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -3060,6 +3062,7 @@ github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0= github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw= github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.2 h1:cj/Z6FKTTYBnstI0Lni9PA+k2foounKIPUmj1LBwNiQ=