diff --git a/README.md b/README.md index 91011846..b97d6b25 100644 --- a/README.md +++ b/README.md @@ -99,12 +99,12 @@ Set-ExecutionPolicy RemoteSigned -scope CurrentUser | `--include-namespaces` | Scan all namespaces | Scan specific namespaces | | | `-s`/`--silent` | Display progress messages | Silent progress messages | | | `-t`/`--fail-threshold` | `100` (do not fail) | fail command (return exit code 1) if result is above threshold | `0` -> `100` | -| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit`/`prometheus` | +| `-f`/`--format` | `pretty-printer` | Output format | `pretty-printer`/`json`/`junit`/`prometheus`/`pdf` | | `-o`/`--output` | print to stdout | Save scan result in file | | -| `--use-from` | | Load local framework object from specified path. If not used will download latest | +| `--use-from` | | Load local framework object from specified path. If not used will download latest || | `--use-artifacts-from` | | Load artifacts (frameworks, control-config, exceptions) from local directory. If not used will download them | | | `--use-default` | `false` | Load local framework object from default path. If not used will download latest | `true`/`false` | -| `--exceptions` | | Path to an exceptions obj, [examples](examples/exceptions/README.md). Default will download exceptions from Kubescape SaaS | +| `--exceptions` | | Path to an exceptions obj, [examples](examples/exceptions/README.md). Default will download exceptions from Kubescape SaaS || | `--controls-config` | | Path to a controls-config obj. If not set will download controls-config from ARMO management portal | | | `--submit` | `false` | If set, Kubescape will send the scan results to Armo management portal where you can see the results in a user-friendly UI, choose your preferred compliance framework, check risk results history and trends, manage exceptions, get remediation recommendations and much more. By default the results are not sent | `true`/`false` | | `--keep-local` | `false` | Kubescape will not send scan results to Armo management portal. Use this flag if you ran with the `--submit` flag in the past and you do not want to submit your current scan results | `true`/`false` | @@ -176,7 +176,14 @@ kubescape scan --format json --output results.json kubescape scan --format junit --output results.xml ``` +#### Output in `pdf` format + +``` +kubescape scan --format pdf --output results.pdf +``` + #### Output in `prometheus` metrics format - Contributed by [@Joibel](https://github.com/Joibel) + ``` kubescape scan --format prometheus ``` diff --git a/clihandler/cmd/scan.go b/clihandler/cmd/scan.go index cdb62449..b2861228 100644 --- a/clihandler/cmd/scan.go +++ b/clihandler/cmd/scan.go @@ -48,7 +48,7 @@ func init() { scanCmd.PersistentFlags().StringVar(&scanInfo.UseArtifactsFrom, "use-artifacts-from", "", "Load artifacts from local directory. If not used will download them") scanCmd.PersistentFlags().StringVarP(&scanInfo.ExcludedNamespaces, "exclude-namespaces", "e", "", "Namespaces to exclude from scanning. Recommended: kube-system,kube-public") scanCmd.PersistentFlags().Uint16VarP(&scanInfo.FailThreshold, "fail-threshold", "t", 100, "Failure threshold is the percent above which the command fails and returns exit code 1") - scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer"/"json"/"junit"/"prometheus"`) + scanCmd.PersistentFlags().StringVarP(&scanInfo.Format, "format", "f", "pretty-printer", `Output format. Supported formats: "pretty-printer","json","junit","prometheus","pdf"`) scanCmd.PersistentFlags().StringVar(&scanInfo.IncludeNamespaces, "include-namespaces", "", "scan specific namespaces. e.g: --include-namespaces ns-a,ns-b") scanCmd.PersistentFlags().BoolVarP(&scanInfo.Local, "keep-local", "", false, "If you do not want your Kubescape results reported to Armo backend. Use this flag if you ran with the '--submit' flag in the past and you do not want to submit your current scan results") scanCmd.PersistentFlags().StringVarP(&scanInfo.Output, "output", "o", "", "Output file. Print output to file and not stdout") diff --git a/go.mod b/go.mod index 35cdc24c..ca9009c0 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/fatih/color v1.13.0 github.com/francoispqt/gojay v1.2.13 github.com/gofrs/uuid v4.1.0+incompatible + github.com/johnfercher/maroto v0.34.0 github.com/mattn/go-isatty v0.0.14 github.com/olekukonko/tablewriter v0.0.5 github.com/open-policy-agent/opa v0.33.1 @@ -50,6 +51,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.13.0 // indirect github.com/aws/smithy-go v1.9.1 // indirect + github.com/boombuler/barcode v1.0.0 // indirect github.com/coreos/go-oidc v2.2.1+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/docker v20.10.9+incompatible // indirect @@ -66,12 +68,14 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.6 // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/googleapis/gnostic v0.5.5 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.11 // indirect + github.com/jung-kurt/gofpdf v1.4.2 // indirect github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -82,6 +86,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect + github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/go.sum b/go.sum index c1d4f78f..6c8dffcb 100644 --- a/go.sum +++ b/go.sum @@ -141,6 +141,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/briandowns/spinner v1.18.0 h1:SJs0maNOs4FqhBwiJ3Gr7Z1D39/rukIVGQvpNZVHVcM= @@ -362,6 +363,7 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= @@ -416,6 +418,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/johnfercher/maroto v0.34.0 h1:kmqlO280WbjzeaPn8HtqUE3gooauVwqO/3cmrBtCL4A= +github.com/johnfercher/maroto v0.34.0/go.mod h1:UeLY7evCe2Au8KwHFzaSGffKGADEZK+u6O8C74mdudM= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -431,6 +435,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.4.2 h1:3u2ojTwxPPu3ysIOc5iTwcECpvkFCAe2RJ/tQrvfLi0= +github.com/jung-kurt/gofpdf v1.4.2/go.mod h1:rZsO0wEsunjT/L9stF3fJjYbAHgqNYuQB4B8FWvBck0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -573,6 +579,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 h1:nlG4Wa5+minh3S9LVFtNoY+GVRiudA2e3EVfcCi3RCA= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= @@ -636,6 +643,8 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -742,6 +751,7 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= diff --git a/resultshandling/printer/printresults.go b/resultshandling/printer/printresults.go index 1ac75455..30aaf302 100644 --- a/resultshandling/printer/printresults.go +++ b/resultshandling/printer/printresults.go @@ -15,6 +15,7 @@ const ( JsonFormat string = "json" JunitResultFormat string = "junit" PrometheusFormat string = "prometheus" + PdfFormat string = "pdf" ) type IPrinter interface { diff --git a/resultshandling/printer/v2/pdf.go b/resultshandling/printer/v2/pdf.go new file mode 100644 index 00000000..d594b4e6 --- /dev/null +++ b/resultshandling/printer/v2/pdf.go @@ -0,0 +1,246 @@ +package v2 + +import ( + _ "embed" + b64 "encoding/base64" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/armosec/kubescape/cautils" + "github.com/armosec/kubescape/resultshandling/printer" + "github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary" + "github.com/johnfercher/maroto/pkg/color" + "github.com/johnfercher/maroto/pkg/consts" + "github.com/johnfercher/maroto/pkg/pdf" + "github.com/johnfercher/maroto/pkg/props" +) + +const ( + pdfOutputFile = "report" + pdfOutputExt = ".pdf" +) + +var ( + //go:embed pdf/logo.png + kubescapeLogo []byte +) + +type PdfPrinter struct { + writer *os.File + sortedControlNames []string +} + +func NewPdfPrinter() *PdfPrinter { + return &PdfPrinter{} +} + +func (pdfPrinter *PdfPrinter) SetWriter(outputFile string) { + // Ensure to have an available output file, otherwise create it. + if outputFile == "" { + outputFile = pdfOutputFile + } + // Ensure to have the right file extension. + if filepath.Ext(strings.TrimSpace(outputFile)) != pdfOutputExt { + outputFile = outputFile + pdfOutputExt + } + pdfPrinter.writer = printer.GetWriter(outputFile) +} + +func (pdfPrinter *PdfPrinter) Score(score float32) { + fmt.Fprintf(os.Stderr, "\nOverall risk-score (0- Excellent, 100- All failed): %d\n", int(score)) +} + +func (pdfPrinter *PdfPrinter) ActionPrint(opaSessionObj *cautils.OPASessionObj) { + pdfPrinter.sortedControlNames = getSortedControlsNames(opaSessionObj.Report.SummaryDetails.Controls) + + m := pdf.NewMaroto(consts.Portrait, consts.A4) + pdfPrinter.printHeader(m) + pdfPrinter.printFramework(m, opaSessionObj.Report.SummaryDetails.ListFrameworks().All()) + pdfPrinter.printTable(m, &opaSessionObj.Report.SummaryDetails) + pdfPrinter.printFinalResult(m, &opaSessionObj.Report.SummaryDetails) + + // Extrat output buffer. + outBuff, err := m.Output() + if err != nil { + fmt.Println("Could not save PDF:", err) + os.Exit(1) + } + pdfPrinter.writer.Write(outBuff.Bytes()) +} + +// Print Kubescape logo and report date. +func (pdfPrinter *PdfPrinter) printHeader(m pdf.Maroto) { + // Retrieve current time (we need it for the report timestamp). + t := time.Now() + // Enconde PNG into Base64 to embed it into the pdf. + kubescapeLogoEnc := b64.StdEncoding.EncodeToString(kubescapeLogo) + + m.SetPageMargins(10, 15, 10) + m.Row(40, func() { + //m.Text(fmt.Sprintf("Security Assessment"), props.Text{ + // Align: consts.Center, + // Size: 24, + // Family: consts.Arial, + // Style: consts.Bold, + //}) + _ = m.Base64Image(kubescapeLogoEnc, consts.Png, props.Rect{ + Center: true, + Percent: 100, + }) + }) + m.Row(6, func() { + m.Text(fmt.Sprintf("Report date: %d-%02d-%02dT%02d:%02d:%02d", + t.Year(), + t.Month(), + t.Day(), + t.Hour(), + t.Minute(), + t.Second()), props.Text{ + Align: consts.Left, + Size: 6.0, + Style: consts.Bold, + Family: consts.Arial, + }) + }) + m.Line(1) +} + +// Print pdf frameworks after pdf header. +func (pdfPrinter *PdfPrinter) printFramework(m pdf.Maroto, frameworks []reportsummary.IPolicies) { + var p string + if len(frameworks) == 1 { + if frameworks[0].GetName() != "" { + p = fmt.Sprintf("FRAMEWORK %s\n", frameworks[0].GetName()) + } + } else if len(frameworks) > 1 { + p = "FRAMEWORKS: " + i := 0 + for ; i < len(frameworks)-1; i++ { + p += fmt.Sprintf("%s (risk: %.2f), ", frameworks[i].GetName(), frameworks[i].GetScore()) + } + p += fmt.Sprintf("%s (risk: %.2f)\n", frameworks[i].GetName(), frameworks[i].GetScore()) + } + m.Row(10, func() { + m.Text(fmt.Sprintf(p), props.Text{ + Align: consts.Center, + Size: 8, + Family: consts.Arial, + Style: consts.Bold, + }) + }) +} + +// Create pdf table +func (pdfPrinter *PdfPrinter) printTable(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) { + headers := []string{"CONTROL NAME", "FAILED RESOURCES", "EXCLUDED RESOURCES", "ALL RESOURCES", "% RISK-SCORE"} + + controls := make([][]string, len(pdfPrinter.sortedControlNames)) + for i := range controls { + controls[i] = make([]string, len(headers)) + } + for i := 0; i < len(pdfPrinter.sortedControlNames); i++ { + controls[i] = generateRow(summaryDetails.Controls.GetControl(reportsummary.EControlCriteriaName, pdfPrinter.sortedControlNames[i])) + } + + m.TableList(headers, controls, props.TableList{ + HeaderProp: props.TableListContent{ + Family: consts.Arial, + Style: consts.Bold, + Size: 8.0, + }, + ContentProp: props.TableListContent{ + Family: consts.Courier, + Style: consts.Normal, + Size: 8.0, + }, + Align: consts.Center, + AlternatedBackground: &color.Color{ + Red: 224, + Green: 224, + Blue: 224, + }, + HeaderContentSpace: 2.0, + Line: false, + }) + m.Line(1) + m.Row(2, func(){}) +} + +// Add final results. +func (pdfPrinter *PdfPrinter) printFinalResult(m pdf.Maroto, summaryDetails *reportsummary.SummaryDetails) { + m.Row(5, func() { + m.Col(3, func() { + m.Text("Resource summary", props.Text{ + Align: consts.Left, + Size: 8.0, + Style: consts.Bold, + Family: consts.Arial, + }) + }) + m.Col(2, func() { + m.Text(fmt.Sprintf("%d", summaryDetails.NumberOfResources().Failed()), props.Text{ + Align: consts.Left, + Size: 8.0, + Style: consts.Bold, + Family: consts.Arial, + }) + }) + m.Col(2, func() { + m.Text(fmt.Sprintf("%d", summaryDetails.NumberOfResources().Excluded()), props.Text{ + Align: consts.Left, + Size: 8.0, + Style: consts.Bold, + Family: consts.Arial, + }) + }) + m.Col(2, func() { + m.Text(fmt.Sprintf("%d", summaryDetails.NumberOfResources().All()), props.Text{ + Align: consts.Left, + Size: 8.0, + Style: consts.Bold, + Family: consts.Arial, + }) + }) + m.Col(2, func() { + m.Text(fmt.Sprintf("%.2f%s", summaryDetails.Score, "%"), props.Text{ + Align: consts.Left, + Size: 8.0, + Style: consts.Bold, + Family: consts.Arial, + }) + }) + }) +} + +// Return sorted control names. +func getSortedControlsNames(controls reportsummary.ControlSummaries) []string { + controlNames := make([]string, 0, len(controls)) + for k := range controls { + c := controls[k] + controlNames = append(controlNames, c.GetName()) + } + sort.Strings(controlNames) + return controlNames +} + +// Generate rows to be appended to the table. +func generateRow(controlSummary reportsummary.IControlSummary) []string { + // We use ReplaceAll because some control name + // has a too long name that unformat the pdf view. + controlName := strings.ReplaceAll(controlSummary.GetName(), "-", " ") + row := []string{controlName} + row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Failed())) + row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().Excluded())) + row = append(row, fmt.Sprintf("%d", controlSummary.NumberOfResources().All())) + + if !controlSummary.GetStatus().IsSkipped() { + row = append(row, fmt.Sprintf("%d", int(controlSummary.GetScore()))+"%") + } else { + row = append(row, "skipped") + } + return row +} diff --git a/resultshandling/printer/v2/pdf/logo.png b/resultshandling/printer/v2/pdf/logo.png new file mode 100644 index 00000000..cfa8ffc1 Binary files /dev/null and b/resultshandling/printer/v2/pdf/logo.png differ diff --git a/resultshandling/printer/v2/printresults.go b/resultshandling/printer/v2/printresults.go index 04b0aa28..8dcda6c4 100644 --- a/resultshandling/printer/v2/printresults.go +++ b/resultshandling/printer/v2/printresults.go @@ -15,6 +15,8 @@ func GetPrinter(printFormat string, verboseMode bool) printer.IPrinter { return NewJunitPrinter() // case printer.PrometheusFormat: // return NewPrometheusPrinter(verboseMode) + case printer.PdfFormat: + return NewPdfPrinter() default: return resourcemapping.NewPrettyPrinter(verboseMode) }