diff --git a/cmd/operator/configscan.go b/cmd/operator/configscan.go index d1534da7..4f0acef8 100644 --- a/cmd/operator/configscan.go +++ b/cmd/operator/configscan.go @@ -21,7 +21,7 @@ var operatorScanConfigExamples = fmt.Sprintf(` func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorInfo) *cobra.Command { configCmd := &cobra.Command{ Use: "configurations", - Short: "Trigger configuration scanning from the Kubescape-Operator microservice", + Short: "Trigger configuration scanning from the Kubescape Operator microservice", Long: ``, Example: operatorScanConfigExamples, Args: func(cmd *cobra.Command, args []string) error { @@ -33,13 +33,13 @@ func getOperatorScanConfigCmd(ks meta.IKubescape, operatorInfo cautils.OperatorI if err != nil { return err } - logger.L().Start("Kubescape-Operator Triggering for configuration scanning") + logger.L().Start("Kubescape Operator Triggering for configuration scanning") _, err = operatorAdapter.OperatorScan() if err != nil { - logger.L().StopError("Failed to triggering Kubescape-Operator for configuration scanning", helpers.Error(err)) + logger.L().StopError("Failed to triggering Kubescape Operator for configuration scanning", helpers.Error(err)) return err } - logger.L().StopSuccess("Triggered Kubescape-Operator for configuration scanning") + logger.L().StopSuccess("Triggered Kubescape Operator for configuration scanning") return nil }, } diff --git a/cmd/operator/operator.go b/cmd/operator/operator.go index b90161c8..4c4217d1 100644 --- a/cmd/operator/operator.go +++ b/cmd/operator/operator.go @@ -29,7 +29,7 @@ func GetOperatorCmd(ks meta.IKubescape) *cobra.Command { operatorCmd := &cobra.Command{ Use: "operator", - Short: "The operator is used to communicate with the Kubescape-Operator within the cluster components.", + Short: "The operator is used to communicate with the Kubescape Operator within the cluster components.", Long: ``, Example: operatorExamples, Args: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/operator/vulnerabilitiesscan.go b/cmd/operator/vulnerabilitiesscan.go index 8743aab7..e8025fda 100644 --- a/cmd/operator/vulnerabilitiesscan.go +++ b/cmd/operator/vulnerabilitiesscan.go @@ -34,13 +34,13 @@ func getOperatorScanVulnerabilitiesCmd(ks meta.IKubescape, operatorInfo cautils. if err != nil { return err } - logger.L().Start("Triggering the Kubescape-Operator for vulnerability scanning") + logger.L().Start("Triggering the Kubescape Operator for vulnerability scanning") _, err = operatorAdapter.OperatorScan() if err != nil { - logger.L().StopError("Failed to trigger the Kubescape-Operator for vulnerability scanning", helpers.Error(err)) + logger.L().StopError("Failed to trigger the Kubescape Operator for vulnerability scanning", helpers.Error(err)) return err } - logger.L().StopSuccess("Triggered Kubescape-Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"") + logger.L().StopSuccess("Triggered Kubescape Operator for vulnerability scanning. View the scanning results once they are ready using the following command: \"kubectl get vulnerabilitysummaries\"") return err }, } diff --git a/cmd/scan/framework.go b/cmd/scan/framework.go index b62daf10..8ade0a68 100644 --- a/cmd/scan/framework.go +++ b/cmd/scan/framework.go @@ -126,9 +126,6 @@ func getFrameworkCmd(ks meta.IKubescape, scanInfo *cautils.ScanInfo) *cobra.Comm logger.L().Fatal(err.Error()) } - if !scanInfo.VerboseMode && scanInfo.ScanType == cautils.ScanTypeFramework { - logger.L().Info("Run with '--verbose'/'-v' flag for detailed resources view\n") - } if results.GetRiskScore() > float32(scanInfo.FailThreshold) { logger.L().Fatal("scan risk-score is above permitted threshold", helpers.String("risk-score", fmt.Sprintf("%.2f", results.GetRiskScore())), helpers.String("fail-threshold", fmt.Sprintf("%.2f", scanInfo.FailThreshold))) } diff --git a/cmd/update/update.go b/cmd/update/update.go index 55b49f24..acc5566f 100644 --- a/cmd/update/update.go +++ b/cmd/update/update.go @@ -14,7 +14,7 @@ import ( ) const ( - installationLink string = "https://github.com/kubescape/kubescape/blob/master/docs/installation.md" + installationLink string = "https://kubescape.io/docs/install-cli/" ) var updateCmdExamples = fmt.Sprintf(` @@ -32,9 +32,9 @@ func GetUpdateCmd() *cobra.Command { //Checking the user's version of kubescape to the latest release if cautils.BuildNumber == cautils.LatestReleaseVersion { //your version == latest version - logger.L().Info(("Nothing to update, you are running the latest version"), helpers.String("Version", cautils.BuildNumber)) + logger.L().Info(("Nothing to update: you are running the latest version"), helpers.String("Version", cautils.BuildNumber)) } else { - fmt.Printf("Please refer to our installation docs in the following link: %s\n", installationLink) + fmt.Printf("Please refer to our installation documentation: %s\n", installationLink) } return nil }, diff --git a/core/cautils/display.go b/core/cautils/display.go index dbb6a8c3..26f302af 100644 --- a/core/cautils/display.go +++ b/core/cautils/display.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "strings" "time" spinnerpkg "github.com/briandowns/spinner" @@ -27,7 +28,7 @@ func FailureTextDisplay(w io.Writer, format string, a ...interface{}) { } func InfoDisplay(w io.Writer, format string, a ...interface{}) { - fmt.Fprintf(w, gchalk.WithCyan().Bold(format), a...) + fmt.Fprintf(w, gchalk.WithBrightWhite().Bold(format), a...) } func InfoTextDisplay(w io.Writer, format string, a ...interface{}) { @@ -50,6 +51,20 @@ func BoldDisplay(w io.Writer, format string, a ...interface{}) { fmt.Fprintf(w, gchalk.Bold(format), a...) } +func LineDisplay(w io.Writer, format string, a ...interface{}) { + fmt.Fprintf(w, gchalk.WithAnsi256(238).Bold(format), a...) +} + +func SectionHeadingDisplay(w io.Writer, format string, a ...interface{}) { + fmt.Fprintf(w, "\n"+ + gchalk.WithBrightWhite().Bold(format)+ + gchalk.WithAnsi256(238).Bold(fmt.Sprintf("\n%s\n\n", strings.Repeat("─", len(format)))), a...) +} + +func StarDisplay(w io.Writer, format string, a ...interface{}) { + fmt.Fprintf(w, gchalk.WithAnsi256(238).Bold("* ")+gchalk.White(format), a...) +} + var spinner *spinnerpkg.Spinner func StartSpinner() { diff --git a/core/cautils/versioncheck.go b/core/cautils/versioncheck.go index 16d1828d..dba17adc 100644 --- a/core/cautils/versioncheck.go +++ b/core/cautils/versioncheck.go @@ -31,7 +31,7 @@ type IVersionCheckHandler interface { func NewIVersionCheckHandler(ctx context.Context) IVersionCheckHandler { if BuildNumber == "" { - logger.L().Ctx(ctx).Warning("Unknown build number, this might affect your scan results. Please make sure that you are running the latest version") + logger.L().Ctx(ctx).Warning("Unknown build number: this might affect your scan results. Please ensure that you are running the latest version.") } if v, ok := os.LookupEnv(CLIENT_ENV); ok && v != "" { diff --git a/core/core/clusterconnector.go b/core/core/clusterconnector.go index 4a7f6b63..0590c707 100644 --- a/core/core/clusterconnector.go +++ b/core/core/clusterconnector.go @@ -36,7 +36,7 @@ func getOperatorPod(k8sClient *k8sinterface.KubernetesApi, ns string) (*v1.Pod, return nil, err } if len(pods.Items) != 1 { - return nil, errors.New("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts") + return nil, errors.New("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts") } return &pods.Items[0], nil diff --git a/core/core/clusterconnector_test.go b/core/core/clusterconnector_test.go index c2d43841..e58ee2fb 100644 --- a/core/core/clusterconnector_test.go +++ b/core/core/clusterconnector_test.go @@ -23,13 +23,13 @@ func Test_getOperatorPod(t *testing.T) { name: "test error no operator exist", createOperatorPod: false, createAnotherOperatorPodWithSameLabel: false, - expectedError: fmt.Errorf("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"), + expectedError: fmt.Errorf("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"), }, { name: "test error several operators exist", createOperatorPod: true, createAnotherOperatorPodWithSameLabel: true, - expectedError: fmt.Errorf("Could not find the Kubescape-Operator chart, please validate that the Kubescape-Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"), + expectedError: fmt.Errorf("Could not find the Kubescape Operator chart, please validate that the Kubescape Operator helm chart is installed and running -> https://github.com/kubescape/helm-charts"), }, { name: "test no error", diff --git a/core/core/image_scan.go b/core/core/image_scan.go index 827b3fdf..e0f317ab 100644 --- a/core/core/image_scan.go +++ b/core/core/image_scan.go @@ -13,7 +13,7 @@ import ( ) func (ks *Kubescape) ScanImage(ctx context.Context, imgScanInfo *ksmetav1.ImageScanInfo, scanInfo *cautils.ScanInfo) (*models.PresenterConfig, error) { - logger.L().Start(fmt.Sprintf("Scanning image: %s", imgScanInfo.Image)) + logger.L().Start(fmt.Sprintf("Scanning image %s...", imgScanInfo.Image)) dbCfg, _ := imagescan.NewDefaultDBConfig() svc := imagescan.NewScanService(dbCfg) diff --git a/core/core/list.go b/core/core/list.go index 43b4a8a5..d00a2376 100644 --- a/core/core/list.go +++ b/core/core/list.go @@ -134,13 +134,13 @@ func prettyPrintControls(ctx context.Context, policies []string) { controlRows := generateControlRows(policies) - short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control Name", "Docs", "Frameworks"}) + short := utils.CheckShortTerminalWidth(controlRows, []string{"Control ID", "Control name", "Docs", "Frameworks"}) if short { controlsTable.SetAutoWrapText(false) controlsTable.SetHeader([]string{"Controls"}) controlRows = shortFormatControlRows(controlRows) } else { - controlsTable.SetHeader([]string{"Control ID", "Control Name", "Docs", "Frameworks"}) + controlsTable.SetHeader([]string{"Control ID", "Control name", "Docs", "Frameworks"}) } var headerColors []tablewriter.Colors for range controlRows[0] { diff --git a/core/core/patch.go b/core/core/patch.go index 5352ee0a..b5e1ed08 100644 --- a/core/core/patch.go +++ b/core/core/patch.go @@ -71,7 +71,7 @@ func (ks *Kubescape) Patch(ctx context.Context, patchInfo *ksmetav1.PatchInfo, s // ===================== Re-scan the image ===================== - logger.L().Start(fmt.Sprintf("Re-Scanning image: %s", patchedImageName)) + logger.L().Start(fmt.Sprintf("Re-scanning image: %s", patchedImageName)) scanResultsPatched, err := svc.Scan(ctx, patchedImageName, creds) if err != nil { diff --git a/core/core/scan.go b/core/core/scan.go index ea03433b..2f2848f5 100644 --- a/core/core/scan.go +++ b/core/core/scan.go @@ -119,7 +119,7 @@ func GetOutputPrinters(scanInfo *cautils.ScanInfo, ctx context.Context, clusterN func (ks *Kubescape) Scan(ctx context.Context, scanInfo *cautils.ScanInfo) (*resultshandling.ResultsHandler, error) { ctxInit, spanInit := otel.Tracer("").Start(ctx, "initialization") - logger.L().Start("Kubescape scanner initializing") + logger.L().Start("Kubescape scanner initializing...") // ===================== Initialization ===================== scanInfo.Init(ctxInit) // initialize scan info @@ -246,7 +246,7 @@ func scanImages(scanType cautils.ScanTypes, scanData *cautils.OPASessionObj, ctx if err := scanSingleImage(ctx, img, svc, resultsHandling); err != nil { logger.L().StopError("failed to scan", helpers.String("image", img), helpers.Error(err)) } - logger.L().StopSuccess("Scanned successfully", helpers.String("image", img)) + logger.L().StopSuccess("Scan successful: ", helpers.String("image", img)) } } diff --git a/core/pkg/policyhandler/handlepullpolicies.go b/core/pkg/policyhandler/handlepullpolicies.go index bcef8270..051dadc8 100644 --- a/core/pkg/policyhandler/handlepullpolicies.go +++ b/core/pkg/policyhandler/handlepullpolicies.go @@ -70,7 +70,7 @@ func (policyHandler *PolicyHandler) getPolicies(ctx context.Context, policyIdent ctx, span := otel.Tracer("").Start(ctx, "policyHandler.getPolicies") defer span.End() - logger.L().Start("Loading policies") + logger.L().Start("Loading policies...") // get policies policies, err = policyHandler.getScanPolicies(ctx, policyIdentifier) @@ -82,7 +82,7 @@ func (policyHandler *PolicyHandler) getPolicies(ctx context.Context, policyIdent } logger.L().StopSuccess("Loaded policies") - logger.L().Start("Loading exceptions") + logger.L().Start("Loading exceptions...") // get exceptions if exceptions, err = policyHandler.getExceptions(); err != nil { @@ -90,7 +90,7 @@ func (policyHandler *PolicyHandler) getPolicies(ctx context.Context, policyIdent } logger.L().StopSuccess("Loaded exceptions") - logger.L().Start("Loading account configurations") + logger.L().Start("Loading account configurations...") // get account configuration if controlInputs, err = policyHandler.getControlInputs(); err != nil { diff --git a/core/pkg/resourcehandler/filesloader.go b/core/pkg/resourcehandler/filesloader.go index 2fd341e2..774543ed 100644 --- a/core/pkg/resourcehandler/filesloader.go +++ b/core/pkg/resourcehandler/filesloader.go @@ -34,7 +34,7 @@ func (fileHandler *FileResourceHandler) GetResources(ctx context.Context, sessio return nil, nil, nil, nil, fmt.Errorf("missing input") } - logger.L().Start("Accessing local objects") + logger.L().Start("Accessing local objects...") // load resources from all input paths mappedResources := map[string][]workloadinterface.IMetadata{} diff --git a/core/pkg/resourcehandler/k8sresources.go b/core/pkg/resourcehandler/k8sresources.go index 8dd36dbb..caf0b880 100644 --- a/core/pkg/resourcehandler/k8sresources.go +++ b/core/pkg/resourcehandler/k8sresources.go @@ -61,7 +61,7 @@ func NewK8sResourceHandler(k8s *k8sinterface.KubernetesApi, hostSensorHandler ho } func (k8sHandler *K8sResourceHandler) GetResources(ctx context.Context, sessionObj *cautils.OPASessionObj, progressListener opaprocessor.IJobProgressNotificationClient, scanInfo *cautils.ScanInfo) (cautils.K8SResources, map[string]workloadinterface.IMetadata, cautils.ExternalResources, map[string]bool, error) { - logger.L().Start("Accessing Kubernetes objects") + logger.L().Start("Accessing Kubernetes objects...") var err error globalFieldSelectors := getFieldSelectorFromScanInfo(scanInfo) @@ -130,7 +130,7 @@ func (k8sHandler *K8sResourceHandler) GetResources(ctx context.Context, sessionO cautils.StopSpinner() logger.L().Success("Requested Host scanner data") } else { - cautils.SetInfoMapForResources("This control requires the host-scanner capability. To activate the host scanner capability, proceed with the installation of the kubescape operator chart found here: https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator", hostResources, sessionObj.InfoMap) + cautils.SetInfoMapForResources("This control requires the Kubescape operator installed. To install it, go to\n https://kubescape.io/docs/install-operator/.", hostResources, sessionObj.InfoMap) } } @@ -212,7 +212,7 @@ func (k8sHandler *K8sResourceHandler) collectCloudResources(ctx context.Context, return fmt.Errorf("failed to get cloud provider, cluster: %s", k8sHandler.clusterName) } - logger.L().Start("Downloading cloud resources") + logger.L().Start("Downloading cloud resources...") if sessionObj.Metadata != nil && sessionObj.Metadata.ContextMetadata.ClusterContextMetadata != nil { sessionObj.Metadata.ContextMetadata.ClusterContextMetadata.CloudProvider = k8sHandler.cloudProvider @@ -423,7 +423,7 @@ func (k8sHandler *K8sResourceHandler) collectRbacResources(allResources map[stri return nil } - logger.L().Start("Collecting RBAC resources") + logger.L().Start("Collecting RBAC resources...") allRbacResources, err := k8sHandler.rbacObjectsAPI.ListAllResources() if err != nil { return err diff --git a/core/pkg/resultshandling/printer/v2/controltable.go b/core/pkg/resultshandling/printer/v2/controltable.go index ef94db0d..e2b97f57 100644 --- a/core/pkg/resultshandling/printer/v2/controltable.go +++ b/core/pkg/resultshandling/printer/v2/controltable.go @@ -144,11 +144,11 @@ func getControlTableHeaders(short bool) []string { headers[0] = "Controls" } else { headers = make([]string, _rowLen) - headers[columnName] = "Control Name" - headers[columnCounterFailed] = "Failed Resources" - headers[columnCounterAll] = "All Resources" + headers[columnName] = "Control name" + headers[columnCounterFailed] = "Failed resources" + headers[columnCounterAll] = "All resources" headers[columnSeverity] = "Severity" - headers[columnComplianceScore] = "% Compliane-Score" + headers[columnComplianceScore] = "Compliance score" } return headers } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter.go b/core/pkg/resultshandling/printer/v2/prettyprinter.go index fb8acbca..4ca8a3b2 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter.go @@ -10,6 +10,7 @@ import ( "github.com/anchore/grype/grype/presenter/models" "github.com/enescakir/emoji" + "github.com/jwalton/gchalk" logger "github.com/kubescape/go-logger" "github.com/kubescape/go-logger/helpers" "github.com/kubescape/k8s-interface/workloadinterface" @@ -20,6 +21,7 @@ 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" ) @@ -118,8 +120,11 @@ func (pp *PrettyPrinter) PrintImageScan(imageScanData []cautils.ImageScanData) { func (pp *PrettyPrinter) ActionPrint(_ context.Context, opaSessionObj *cautils.OPASessionObj, imageScanData []cautils.ImageScanData) { if opaSessionObj != nil { + // TODO line is currently printed on framework scan only if isPrintSeparatorType(pp.scanType) { - fmt.Fprintf(pp.writer, "\n"+getSeparator("^")+"\n") + fmt.Fprintf(pp.writer, "\n"+ + gchalk.WithAnsi256(238).Bold(fmt.Sprintf("%s\n", strings.Repeat("─", 50)))+ + "\n") } else { fmt.Fprintf(pp.writer, "\n") } @@ -166,13 +171,26 @@ func (pp *PrettyPrinter) printHeader(opaSessionObj *cautils.OPASessionObj) { cautils.InfoDisplay(pp.writer, fmt.Sprintf("\nKubescape security posture overview for cluster: %s\n\n", pp.clusterName)) cautils.SimpleDisplay(pp.writer, "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.\n\n") } else if pp.scanType == cautils.ScanTypeWorkload { + cautils.InfoDisplay(pp.writer, "Workload security posture overview for:\n") ns := opaSessionObj.SingleResourceScan.GetNamespace() - if ns == "" { - cautils.InfoDisplay(pp.writer, "Workload - Kind: %s, Name: %s\n\n", opaSessionObj.SingleResourceScan.GetKind(), opaSessionObj.SingleResourceScan.GetName()) - } else { - cautils.InfoDisplay(pp.writer, "Workload - Namespace: %s, Kind: %s, Name: %s\n\n", opaSessionObj.SingleResourceScan.GetNamespace(), opaSessionObj.SingleResourceScan.GetKind(), opaSessionObj.SingleResourceScan.GetName()) + rows := [][]string{} + if ns != "" { + rows = append(rows, []string{"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())}) + + table := tablewriter.NewWriter(pp.writer) + + table.SetColumnAlignment([]int{tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT}) + table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) + table.AppendBulk(rows) + + table.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") } + } func (pp *PrettyPrinter) SetWriter(ctx context.Context, outputFile string) { @@ -317,16 +335,15 @@ func generateRelatedObjectsStr(workload WorkloadSummary) string { func frameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string { if len(frameworks) == 1 { if frameworks[0].GetName() != "" { - return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName()) - // cautils.InfoTextDisplay(prettyPrinter.writer, )) + return fmt.Sprintf("Framework scanned: %s\n", frameworks[0].GetName()) } } else if len(frameworks) > 1 { - p := "FRAMEWORKS: " + p := "Frameworks scanned: " i := 0 for ; i < len(frameworks)-1; i++ { - p += fmt.Sprintf("%s (compliance: %.2f), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore()) + p += fmt.Sprintf("%s (compliance score: %.2f%%), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore()) } - p += fmt.Sprintf("%s (compliance: %.2f)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore()) + p += fmt.Sprintf("%s (compliance score: %.2f%%)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore()) return p } return "" diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/clusterscan.go b/core/pkg/resultshandling/printer/v2/prettyprinter/clusterscan.go index d477d823..e528dcdf 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/clusterscan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/clusterscan.go @@ -3,7 +3,6 @@ package prettyprinter import ( "fmt" "os" - "strings" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter" @@ -43,7 +42,7 @@ func (cp *ClusterPrinter) PrintConfigurationsScanning(summaryDetails *reportsumm } func (cp *ClusterPrinter) PrintNextSteps() { - printNextSteps(cp.writer, cp.getNextSteps(), false) + printNextSteps(cp.writer, cp.getNextSteps(), true) } func (cp *ClusterPrinter) getNextSteps() []string { @@ -57,10 +56,7 @@ func (cp *ClusterPrinter) getNextSteps() []string { func (cp *ClusterPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IResource) { txt := getTopWorkloadsTitle(len(topWorkloadsByScore)) - cautils.InfoTextDisplay(cp.writer, txt) - - cautils.SimpleDisplay(cp.writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt)))) - + cautils.SectionHeadingDisplay(cp.writer, txt) cautils.SimpleDisplay(cp.writer, highStakesWlsText) for i, wl := range topWorkloadsByScore { @@ -71,7 +67,7 @@ func (cp *ClusterPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling cautils.SimpleDisplay(cp.writer, fmt.Sprintf(" '%s'\n", getCallToActionString(cp.getWorkloadScanCommand(ns, kind, name)))) } - cautils.InfoTextDisplay(cp.writer, "\n") + cautils.SimpleDisplay(cp.writer, "\n") } func (cp *ClusterPrinter) getWorkloadScanCommand(namespace, kind, name string) string { diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/imagescan.go b/core/pkg/resultshandling/printer/v2/prettyprinter/imagescan.go index 874bcf56..67faa964 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/imagescan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/imagescan.go @@ -41,7 +41,7 @@ func (ip *ImagePrinter) PrintImageScanningTable(summary imageprinter.ImageScanSu } ip.imageTablePrinter.PrintImageScanningTable(ip.writer, summary) - cautils.InfoTextDisplay(ip.writer, "\n") + cautils.SimpleDisplay(ip.writer, "\n") } func (ip *ImagePrinter) PrintConfigurationsScanning(summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string, topWorkloadsByScore []reporthandling.IResource) { diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/reposcan.go b/core/pkg/resultshandling/printer/v2/prettyprinter/reposcan.go index 6a1b96f2..38ae54ed 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/reposcan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/reposcan.go @@ -3,7 +3,6 @@ package prettyprinter import ( "fmt" "os" - "strings" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/kubescape/v3/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter" @@ -56,10 +55,7 @@ func (rp *RepoPrinter) getNextSteps() []string { func (rp *RepoPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IResource) { txt := getTopWorkloadsTitle(len(topWorkloadsByScore)) - cautils.InfoTextDisplay(rp.writer, txt) - - cautils.SimpleDisplay(rp.writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt)))) - + cautils.SectionHeadingDisplay(rp.writer, txt) cautils.SimpleDisplay(rp.writer, highStakesWlsText) for i, wl := range topWorkloadsByScore { @@ -71,7 +67,7 @@ func (rp *RepoPrinter) printTopWorkloads(topWorkloadsByScore []reporthandling.IR cautils.SimpleDisplay(rp.writer, fmt.Sprintf(" %s\n", getCallToActionString(rp.getWorkloadScanCommand(ns, kind, name, *wl.GetSource())))) } - cautils.InfoTextDisplay(rp.writer, "\n") + cautils.SimpleDisplay(rp.writer, "\n") } func (rp *RepoPrinter) getWorkloadScanCommand(ns, kind, name string, source reporthandling.Source) string { 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 d68c0d57..988f0f55 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/categorytable.go @@ -13,11 +13,11 @@ import ( const ( docsPrefix = "https://hub.armosec.io/docs" scanControlPrefix = "$ kubescape scan control" - controlNameHeader = "Control Name" + controlNameHeader = "Control name" statusHeader = "" docsHeader = "Docs" resourcesHeader = "Resources" - runHeader = "View Details" + runHeader = "View details" ) // initializes the table headers and column alignments based on the category type @@ -91,14 +91,15 @@ func getCategoryTableWriter(writer io.Writer, headers []string, columnAligments table.SetUnicodeHVC(tablewriter.Regular, tablewriter.Regular, gchalk.Ansi256(238)) var headerColors []tablewriter.Colors for range headers { - headerColors = append(headerColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiYellowColor}) + headerColors = append(headerColors, tablewriter.Colors{tablewriter.FgHiYellowColor}) } table.SetHeaderColor(headerColors...) return table } func renderSingleCategory(writer io.Writer, categoryName string, table *tablewriter.Table, rows [][]string, infoToPrintInfo []utils.InfoStars) { - cautils.InfoTextDisplay(writer, categoryName+"\n") + + cautils.InfoDisplay(writer, categoryName+"\n") table.ClearRows() table.AppendBulk(rows) 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 c5319aa9..dbabbe7d 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 @@ -21,13 +21,13 @@ func TestInitCategoryTableData(t *testing.T) { { name: "Test1", categoryType: TypeCounting, - expectedHeaders: []string{"Control Name", "Resources", "View Details"}, + expectedHeaders: []string{"Control name", "Resources", "View details"}, expectedAlignments: []int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT}, }, { name: "Test2", categoryType: TypeStatus, - expectedHeaders: []string{"", "Control Name", "Docs"}, + expectedHeaders: []string{"", "Control name", "Docs"}, expectedAlignments: []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER}, }, } 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 3a03ff46..8d20eb01 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/frameworkscan.go @@ -3,6 +3,7 @@ package configurationprinter import ( "fmt" "io" + "strconv" "strings" "github.com/jwalton/gchalk" @@ -30,11 +31,33 @@ func (fp *FrameworkPrinter) getVerboseMode() bool { func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails *reportsummary.SummaryDetails, sortedControlIDs [][]string) { if summaryDetails.NumberOfControls().All() == 0 { - fmt.Fprintf(writer, "\nKubescape did not scan any of the resources, make sure you are scanning valid kubernetes manifests (Deployments, Pods, etc.)\n") + fmt.Fprintf(writer, "\nKubescape did not scan any resources. Make sure you are scanning valid manifests (Deployments, Pods, etc.)\n") return } - cautils.InfoTextDisplay(writer, "\n"+ControlCountersForSummary(summaryDetails.NumberOfControls())+"\n") - cautils.InfoTextDisplay(writer, renderSeverityCountersSummary(summaryDetails.GetResourcesSeverityCounters())+"\n\n") + + // When scanning controls the framework list will be empty + cautils.SimpleDisplay(writer, utils.FrameworksScoresToString(summaryDetails.ListFrameworks())+"\n") + + controlCountersTable := tablewriter.NewWriter(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.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.Render() + + cautils.SimpleDisplay(writer, "\n") + + if !fp.getVerboseMode() { + cautils.SimpleDisplay(writer, "Run with '--verbose'/'-v' to see control failures for each resource.\n\n") + } summaryTable := tablewriter.NewWriter(writer) @@ -82,9 +105,6 @@ func (fp *FrameworkPrinter) PrintSummaryTable(writer io.Writer, summaryDetails * summaryTable.AppendBulk(dataRows) summaryTable.Render() - // When scanning controls the framework list will be empty - cautils.InfoTextDisplay(writer, utils.FrameworksScoresToString(summaryDetails.ListFrameworks())) - utils.PrintInfo(writer, infoToPrintInfo) } @@ -100,14 +120,13 @@ func (fp *FrameworkPrinter) PrintCategoriesTables(writer io.Writer, summaryDetai } -func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) string { - critical := counters.NumberOfCriticalSeverity() - high := counters.NumberOfHighSeverity() - medium := counters.NumberOfMediumSeverity() - low := counters.NumberOfLowSeverity() +func renderSeverityCountersSummary(counters reportsummary.ISeverityCounters) [][]string { - return fmt.Sprintf( - "Failed Resources by Severity: Critical — %d, High — %d, Medium — %d, Low — %d", - critical, high, medium, low, - ) + 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()))}) + + return rows } 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 574f036b..5ed8cb97 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/summarytable.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/configurationprinter/summarytable.go @@ -2,6 +2,7 @@ package configurationprinter import ( "fmt" + "strconv" "strings" "github.com/kubescape/kubescape/v3/core/cautils" @@ -20,8 +21,14 @@ const ( _summaryRowLen = iota ) -func ControlCountersForSummary(counters reportsummary.ICounters) string { - return fmt.Sprintf("Controls: %d (Failed: %d, Passed: %d, Action Required: %d)", counters.All(), counters.Failed(), counters.Passed(), counters.Skipped()) +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())}) + + return rows } func GetSeverityColumn(controlSummary reportsummary.IControlSummary) string { @@ -35,21 +42,21 @@ func GetControlTableHeaders(short bool) []string { headers[0] = "Controls" } else { headers = make([]string, _summaryRowLen) - headers[summaryColumnName] = "Control Name" - headers[summaryColumnCounterFailed] = "Failed Resources" + headers[summaryColumnName] = "Control name" + headers[summaryColumnCounterFailed] = "Failed resources" headers[summaryColumnCounterAll] = "All Resources" headers[summaryColumnSeverity] = "Severity" - headers[summaryColumnComplianceScore] = "% Compliance-Score" + headers[summaryColumnComplianceScore] = "Compliance score" } 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[summaryColumnSeverity] = tablewriter.ALIGN_LEFT alignments[summaryColumnComplianceScore] = tablewriter.ALIGN_CENTER return alignments } 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 3fb622ef..becf19d9 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 @@ -8,7 +8,7 @@ import ( func TestWorkloadScan_InitCategoryTableData(t *testing.T) { - expectedHeader := []string{"", "Control Name", "Docs"} + expectedHeader := []string{"", "Control name", "Docs"} expectedAlign := []int{tablewriter.ALIGN_CENTER, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_CENTER} workloadPrinter := NewWorkloadPrinter() 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 6f996e2e..53828d60 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/imageprinter/utils.go @@ -70,7 +70,7 @@ func getImageScanningHeaders() []string { headers[imageColumnName] = "Vulnerability" headers[imageColumnComponent] = "Component" headers[imageColumnVersion] = "Version" - headers[imageColumnFixedIn] = "Fixed In" + headers[imageColumnFixedIn] = "Fixed in" return headers } 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 e2315f47..72a471f2 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 @@ -140,7 +140,7 @@ func TestGenerateRow(t *testing.T) { func TestGetImageScanningHeaders(t *testing.T) { headers := getImageScanningHeaders() - expectedHeaders := []string{"Severity", "Vulnerability", "Component", "Version", "Fixed In"} + expectedHeaders := []string{"Severity", "Vulnerability", "Component", "Version", "Fixed in"} for i := range headers { if headers[i] != expectedHeaders[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 c07d076d..4b2201a5 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils/utils.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/tableprinter/utils/utils.go @@ -5,6 +5,7 @@ import ( "io" "os" + "github.com/enescakir/emoji" "github.com/jwalton/gchalk" "github.com/kubescape/kubescape/v3/core/cautils" "github.com/kubescape/opa-utils/reporthandling/apis" @@ -90,16 +91,15 @@ func ImageSeverityToInt(severity string) int { func FrameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) string { if len(frameworks) == 1 { if frameworks[0].GetName() != "" { - return fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName()) - // cautils.InfoTextDisplay(prettyPrinter.writer, )) + return fmt.Sprintf("Framework scanned: %s\n", frameworks[0].GetName()) } } else if len(frameworks) > 1 { - p := "FRAMEWORKS: " + p := "Frameworks scanned: " i := 0 for ; i < len(frameworks)-1; i++ { - p += fmt.Sprintf("%s (compliance: %.2f), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore()) + p += fmt.Sprintf("%s (compliance score: %.2f%%), ", frameworks[i].GetName(), frameworks[i].GetComplianceScore()) } - p += fmt.Sprintf("%s (compliance: %.2f)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore()) + p += fmt.Sprintf("%s (compliance score: %.2f%%)\n", frameworks[i].GetName(), frameworks[i].GetComplianceScore()) return p } return "" @@ -108,7 +108,7 @@ func FrameworksScoresToString(frameworks []reportsummary.IFrameworkSummary) stri func PrintInfo(writer io.Writer, infoToPrintInfo []InfoStars) { fmt.Println() for i := range infoToPrintInfo { - cautils.InfoDisplay(writer, fmt.Sprintf("%s %s\n", infoToPrintInfo[i].Stars, infoToPrintInfo[i].Info)) + cautils.InfoDisplay(writer, fmt.Sprintf("%s %s %s\n", emoji.PoliceCarLight, infoToPrintInfo[i].Stars, infoToPrintInfo[i].Info)) } } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/utils.go b/core/pkg/resultshandling/printer/v2/prettyprinter/utils.go index 7dc19f69..d605e4cd 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/utils.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/utils.go @@ -20,7 +20,7 @@ const ( configScanVerboseRunText = "Run with '--verbose'/'-v' flag for detailed resources view" imageScanVerboseRunText = "Run with '--verbose'/'-v' flag for detailed vulnerabilities view" runCommandsText = "Run one of the suggested commands to learn more about a failed control failure" - ksHelmChartLink = "https://github.com/kubescape/helm-charts/tree/main/charts/kubescape-operator" + ksHelmChartLink = "https://kubescape.io/docs/install-operator/" highStakesWlsText = "High-stakes workloads are defined as those which Kubescape estimates would have the highest impact if they were to be exploited.\n\n" ) @@ -55,7 +55,7 @@ func getWorkloadPrefixForCmd(namespace, kind, name string) string { func getTopWorkloadsTitle(topWLsLen int) string { if topWLsLen > 0 { - return "Highest-stake workloads\n" + return "Highest-stake workloads" } return "" } @@ -167,24 +167,23 @@ func printTopComponents(writer *os.File, summary imageprinter.ImageScanSummary) } txt := "Components with most vulnerabilities" - cautils.InfoTextDisplay(writer, "\n"+txt+"\n") - cautils.SimpleDisplay(writer, strings.Repeat("─", len(txt))+"\n") + cautils.SectionHeadingDisplay(writer, txt) sortedPkgScores := getSortPackageScores(summary.PackageScores) for i := 0; i < len(sortedPkgScores) && i < TopPackagesNumber; i++ { topPkg := summary.PackageScores[sortedPkgScores[i]] - output := fmt.Sprintf(" * %s (%s) -", topPkg.Name, topPkg.Version) + output := fmt.Sprintf("%s (%s) -", topPkg.Name, topPkg.Version) sortedCVEs := getSortedCVEsBySeverity(topPkg.MapSeverityToCVEsNumber) for j := range sortedCVEs { - output += fmt.Sprintf(" %d %s,", topPkg.MapSeverityToCVEsNumber[sortedCVEs[j]], sortedCVEs[j]) + output += fmt.Sprintf(" %d %s,", topPkg.MapSeverityToCVEsNumber[sortedCVEs[j]], utils.GetColorForVulnerabilitySeverity(sortedCVEs[j])(sortedCVEs[j])) } output = output[:len(output)-1] - cautils.SimpleDisplay(writer, output+"\n") + cautils.StarDisplay(writer, output+"\n") } cautils.SimpleDisplay(writer, "\n") @@ -206,25 +205,26 @@ func printImageScanningSummary(writer *os.File, summary imageprinter.ImageScanSu if len(summary.CVEs) == 0 { txt := "No vulnerabilities were found!" - cautils.InfoTextDisplay(writer, txt+"\n") - cautils.SimpleDisplay(writer, strings.Repeat("─", len(txt))+"\n") + + cautils.InfoDisplay(writer, txt+"\n") return } - txt := fmt.Sprintf("%d vulnerabilities found:", len(summary.CVEs)) - cautils.InfoTextDisplay(writer, txt+"\n") - cautils.SimpleDisplay(writer, strings.Repeat("─", len(txt))+"\n") + txt := fmt.Sprintf("%d vulnerabilities found", len(summary.CVEs)) + cautils.SectionHeadingDisplay(writer, txt) if len(summary.Images) == 1 { - cautils.SimpleDisplay(writer, "Image: %s\n", summary.Images[0]) + cautils.SimpleDisplay(writer, "Image: %s\n\n", summary.Images[0]) } else if len(summary.Images) < 4 { - cautils.SimpleDisplay(writer, "Images: %s\n", strings.Join(summary.Images, ", ")) + cautils.SimpleDisplay(writer, "Images: %s\n\n", strings.Join(summary.Images, ", ")) } for _, k := range keys { - cautils.SimpleDisplay(writer, " * %d %s \n", mapSeverityTSummary[k].NumberOfCVEs, utils.GetColorForVulnerabilitySeverity(k)(k)) + cautils.StarDisplay(writer, "%d %s \n", mapSeverityTSummary[k].NumberOfCVEs, utils.GetColorForVulnerabilitySeverity(k)(k)) } + cautils.SimpleDisplay(writer, "\n") + } func printImagesCommands(writer *os.File, summary imageprinter.ImageScanSummary) { @@ -233,21 +233,19 @@ func printImagesCommands(writer *os.File, summary imageprinter.ImageScanSummary) } else { for _, img := range summary.Images { imgWithoutTag := strings.Split(img, ":")[0] - cautils.SimpleDisplay(writer, fmt.Sprintf("Receive full report for %s image by running: %s\n", imgWithoutTag, getCallToActionString(fmt.Sprintf("'$ kubescape scan image %s'", img)))) + cautils.SimpleDisplay(writer, fmt.Sprintf("Receive a full report for %s by running: %s\n", imgWithoutTag, getCallToActionString(fmt.Sprintf("'$ kubescape scan image %s'", img)))) } } - cautils.InfoTextDisplay(writer, "\n") + cautils.SimpleDisplay(writer, "\n") } func printNextSteps(writer *os.File, nextSteps []string, addLine bool) { txt := "What now?" - cautils.InfoTextDisplay(writer, fmt.Sprintf("%s\n", txt)) - - cautils.SimpleDisplay(writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt)))) + cautils.SectionHeadingDisplay(writer, txt) for _, ns := range nextSteps { - cautils.SimpleDisplay(writer, "* "+ns+"\n") + cautils.StarDisplay(writer, ns+"\n") } if addLine { cautils.SimpleDisplay(writer, "\n") @@ -256,21 +254,18 @@ func printNextSteps(writer *os.File, nextSteps []string, addLine bool) { func printComplianceScore(writer *os.File, frameworks []reportsummary.IFrameworkSummary) { txt := "Compliance Score" - cautils.InfoTextDisplay(writer, fmt.Sprintf("%s\n", txt)) - - cautils.SimpleDisplay(writer, fmt.Sprintf("%s\n", strings.Repeat("─", len(txt)))) - + cautils.SectionHeadingDisplay(writer, txt) cautils.SimpleDisplay(writer, "The compliance score is calculated by multiplying control failures by the number of failures against supported compliance frameworks. Remediate controls, or configure your cluster baseline with exceptions, to improve this score.\n\n") for _, fw := range frameworks { - cautils.SimpleDisplay(writer, "* %s: %s", fw.GetName(), gchalk.WithYellow().Bold(fmt.Sprintf("%.2f%%\n", fw.GetComplianceScore()))) + cautils.StarDisplay(writer, "%s: %s", fw.GetName(), gchalk.WithBrightYellow().Bold(fmt.Sprintf("%.2f%%\n", fw.GetComplianceScore()))) } cautils.SimpleDisplay(writer, fmt.Sprintf("\nView a full compliance report by running %s or %s\n", getCallToActionString("'$ kubescape scan framework nsa'"), getCallToActionString("'$ kubescape scan framework mitre'"))) - cautils.InfoTextDisplay(writer, "\n") + cautils.SimpleDisplay(writer, "\n") } func getCallToActionString(action string) string { - return gchalk.WithBrightBlue().Bold(action) + return gchalk.WithBrightWhite().Bold(action) } diff --git a/core/pkg/resultshandling/printer/v2/prettyprinter/utils_test.go b/core/pkg/resultshandling/printer/v2/prettyprinter/utils_test.go index bfaaf33e..db4fbf26 100644 --- a/core/pkg/resultshandling/printer/v2/prettyprinter/utils_test.go +++ b/core/pkg/resultshandling/printer/v2/prettyprinter/utils_test.go @@ -88,13 +88,13 @@ func TestGetTopWorkloadsTitle(t *testing.T) { assert.Equal(t, "", title) title = getTopWorkloadsTitle(1) - assert.Equal(t, "Highest-stake workloads\n", title) + assert.Equal(t, "Highest-stake workloads", title) title = getTopWorkloadsTitle(2) - assert.Equal(t, "Highest-stake workloads\n", title) + assert.Equal(t, "Highest-stake workloads", title) title = getTopWorkloadsTitle(10) - assert.Equal(t, "Highest-stake workloads\n", title) + assert.Equal(t, "Highest-stake workloads", title) } func TestGetSeverityToSummaryMap(t *testing.T) { diff --git a/core/pkg/resultshandling/printer/v2/resourcetable.go b/core/pkg/resultshandling/printer/v2/resourcetable.go index 493091d1..dfa4decf 100644 --- a/core/pkg/resultshandling/printer/v2/resourcetable.go +++ b/core/pkg/resultshandling/printer/v2/resourcetable.go @@ -119,9 +119,9 @@ func generateResourceHeader(short bool) []string { } else { headers = make([]string, _resourceRowLen) headers[resourceColumnSeverity] = "Severity" - headers[resourceColumnName] = "Control Name" + headers[resourceColumnName] = "Control name" headers[resourceColumnURL] = "Docs" - headers[resourceColumnPath] = "Assisted Remediation" + headers[resourceColumnPath] = "Assisted remediation" } return headers } diff --git a/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go b/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go index e4c1f102..0c5533ee 100644 --- a/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go +++ b/core/pkg/resultshandling/reporter/v2/reporteventreceiver.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "os" - "strings" "time" "github.com/armosec/armoapi-go/apis" @@ -275,10 +274,7 @@ func (report *ReportEventReceiver) DisplayMessage() { // print if logger level is lower than warning (debug/info) if report.message != "" && helpers.ToLevel(logger.L().GetLevel()) < helpers.WarningLevel { txt := "View results" - cautils.InfoTextDisplay(os.Stderr, fmt.Sprintf("\n%s\n", txt)) - - cautils.SimpleDisplay(os.Stderr, strings.Repeat("─", len(txt))) - - cautils.SimpleDisplay(os.Stderr, fmt.Sprintf("\n%s\n\n", report.message)) + cautils.SectionHeadingDisplay(os.Stdout, txt) + cautils.SimpleDisplay(os.Stdout, fmt.Sprintf("%s\n\n", report.message)) } } diff --git a/core/pkg/resultshandling/reporter/v2/reporteventreceiver_test.go b/core/pkg/resultshandling/reporter/v2/reporteventreceiver_test.go index 991dc13a..5d8c9712 100644 --- a/core/pkg/resultshandling/reporter/v2/reporteventreceiver_test.go +++ b/core/pkg/resultshandling/reporter/v2/reporteventreceiver_test.go @@ -88,7 +88,7 @@ func TestDisplayMessage(t *testing.T) { getter.GetKSCloudAPIConnector(), ) - capture, clean := captureStderr(t) + capture, clean := captureStdout(t) defer clean() reporter.DisplayMessage() @@ -114,7 +114,7 @@ func TestDisplayMessage(t *testing.T) { ) reporter.setMessage("message returned from server") - capture, clean := captureStderr(t) + capture, clean := captureStdout(t) defer clean() reporter.DisplayMessage() @@ -232,7 +232,7 @@ func TestSubmit(t *testing.T) { opaSession := mockOPASessionObj(t) - capture, clean := captureStderr(t) + capture, clean := captureStdout(t) if pretty, ok := logger.L().(*prettylogger.PrettyLogger); ok { pretty.SetWriter(capture) } @@ -274,7 +274,7 @@ func TestSubmit(t *testing.T) { opaSession := mockOPASessionObj(t) opaSession.Metadata.ScanMetadata.ScanningTarget = reporthandlingv2.Cluster - capture, clean := captureStderr(t) + capture, clean := captureStdout(t) if pretty, ok := logger.L().(*prettylogger.PrettyLogger); ok { pretty.SetWriter(capture) } @@ -357,3 +357,25 @@ func captureStderr(t testing.TB) (*os.File, func()) { mxStdio.Unlock() } } + +func captureStdout(t testing.TB) (*os.File, func()) { + mxStdio.Lock() + saved := os.Stdout + capture, err := os.CreateTemp("", "stdout") + if !assert.NoError(t, err) { + mxStdio.Unlock() + + t.FailNow() + + return nil, nil + } + os.Stdout = capture + + return capture, func() { + _ = capture.Close() + _ = os.Remove(capture.Name()) + + os.Stdout = saved + mxStdio.Unlock() + } +}