Skip to content

Commit

Permalink
add support of color for bsi, oct, ntia compliances
Browse files Browse the repository at this point in the history
Signed-off-by: Vivek Kumar Sahu <[email protected]>
  • Loading branch information
viveksahu26 committed Dec 1, 2024
1 parent 3ae2929 commit 1afa6ac
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 42 deletions.
4 changes: 2 additions & 2 deletions pkg/compliance/bsi.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const (
SBOM_VULNERABILITES
)

func bsiResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string) {
func bsiResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string, colorOutput bool) {
log := logger.FromContext(ctx)
log.Debug("compliance.bsiResult()")

Expand All @@ -107,7 +107,7 @@ func bsiResult(ctx context.Context, doc sbom.Document, fileName string, outForma
}

if outFormat == "detailed" {
bsiDetailedReport(dtb, fileName)
bsiDetailedReport(dtb, fileName, colorOutput)
}
}

Expand Down
59 changes: 56 additions & 3 deletions pkg/compliance/bsi_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ import (
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/google/uuid"
"github.com/interlynk-io/sbomqs/pkg/compliance/common"
db "github.com/interlynk-io/sbomqs/pkg/compliance/db"
"github.com/olekukonko/tablewriter"
"sigs.k8s.io/release-utils/version"
Expand Down Expand Up @@ -173,7 +175,7 @@ func constructSections(dtb *db.DB) []bsiSection {
return sortedSections
}

func bsiDetailedReport(dtb *db.DB, fileName string) {
func bsiDetailedReport(dtb *db.DB, fileName string, colorOutput bool) {
table := tablewriter.NewWriter(os.Stdout)
score := bsiAggregateScore(dtb)

Expand All @@ -189,15 +191,66 @@ func bsiDetailedReport(dtb *db.DB, fileName string) {
for _, section := range sections {
sectionID := section.ID
if !section.Required {
sectionID = sectionID + "*"
sectionID += "*"
}

if colorOutput {
// disable tablewriter's auto-wrapping
table.SetAutoWrapText(false)
columnWidth := 30
common.SetHeaderColor(table, 5)

table = common.ColorTable(table,
section.ElementID,
section.ID,
section.ElementResult,
section.DataField,
section.Score,
columnWidth)
} else {
table.Append([]string{section.ElementID, sectionID, section.DataField, section.ElementResult, fmt.Sprintf("%0.1f", section.Score)})
}
table.Append([]string{section.ElementID, sectionID, section.DataField, section.ElementResult, fmt.Sprintf("%0.1f", section.Score)})
}
table.Render()
}

// Custom wrapping function to ensure consistent coloring.
func wrapAndColoredContent(content string, width int, color int) string {

Check failure on line 218 in pkg/compliance/bsi_report.go

View workflow job for this annotation

GitHub Actions / lint

func `wrapAndColoredContent` is unused (unused)
words := strings.Fields(content) // Split into words for wrapping
var wrappedContent []string
var currentLine string

for _, word := range words {
if len(currentLine)+len(word)+1 > width {
// Wrap the current line and color it
wrappedContent = append(wrappedContent, fmt.Sprintf("\033[%d;%dm%s\033[0m", 1, color, currentLine))
currentLine = word // Start a new line
} else {
if currentLine != "" {
currentLine += " "
}
currentLine += word
}
}
// Add the last line
if currentLine != "" {
wrappedContent = append(wrappedContent, fmt.Sprintf("\033[%d;%dm%s\033[0m", 1, color, currentLine))
}

return strings.Join(wrappedContent, "\n")
}

func bsiBasicReport(dtb *db.DB, fileName string) {
score := bsiAggregateScore(dtb)
fmt.Printf("BSI TR-03183-2 v1.1 Compliance Report\n")
fmt.Printf("Score:%0.1f RequiredScore:%0.1f OptionalScore:%0.1f for %s\n", score.totalScore(), score.totalRequiredScore(), score.totalOptionalScore(), fileName)
}

func getScoreColor(score float64) tablewriter.Colors {

Check failure on line 249 in pkg/compliance/bsi_report.go

View workflow job for this annotation

GitHub Actions / lint

func `getScoreColor` is unused (unused)
if score == 0.0 {
return tablewriter.Colors{tablewriter.FgRedColor, tablewriter.Bold}
} else if score < 5.0 {
return tablewriter.Colors{tablewriter.FgHiYellowColor, tablewriter.Bold}
}
return tablewriter.Colors{tablewriter.FgGreenColor, tablewriter.Bold}
}
73 changes: 73 additions & 0 deletions pkg/compliance/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package common

import (
"fmt"
"path"
"strings"
"time"
Expand All @@ -25,6 +26,7 @@ import (
"github.com/interlynk-io/sbomqs/pkg/sbom"
"github.com/interlynk-io/sbomqs/pkg/swhid"
"github.com/interlynk-io/sbomqs/pkg/swid"
"github.com/olekukonko/tablewriter"
"github.com/samber/lo"
)

Expand Down Expand Up @@ -375,3 +377,74 @@ func IsComponentPartOfPrimaryDependency(primaryCompDeps []string, comp string) b
}
return false
}

func SetHeaderColor(table *tablewriter.Table, header int) {
colors := make([]tablewriter.Colors, header)

// each column with same color and style
for i := 0; i < header; i++ {
colors[i] = tablewriter.Colors{tablewriter.FgHiWhiteColor, tablewriter.Bold}
}

table.SetHeaderColor(colors...)
}

func ColorTable(table *tablewriter.Table, elementID, id string, elementResult string, dataFields string, score float64, columnWidth int) *tablewriter.Table {
elementRe := wrapAndColoredContent(elementResult, columnWidth, tablewriter.FgHiCyanColor)
dataField := wrapAndColoredContent(dataFields, columnWidth, tablewriter.FgHiBlueColor)

scoreColor := GetScoreColor(score)

table.Rich([]string{
elementID,
id,
dataField,
elementRe,
fmt.Sprintf("%0.1f", score),
}, []tablewriter.Colors{
{tablewriter.FgHiMagentaColor, tablewriter.Bold},
{tablewriter.FgHiCyanColor},
{},
{},
scoreColor,
})
return table
}

// custom wrapping function to ensure consistent coloring instead of tablewritter's in-built wrapping
// 1. split content into multiple lines, each fitting within the specified width
// 2. each line of the content is formatted with color and bold styling using ANSI escape codes
// 3. wrapped lines are joined together with newline characters (\n) to maintain proper multi-line formatting.
func wrapAndColoredContent(content string, width int, color int) string {
words := strings.Fields(content)
var wrappedContent []string
var currentLine string

for _, word := range words {
if len(currentLine)+len(word)+1 > width {

// wrap the current line and color it
wrappedContent = append(wrappedContent, fmt.Sprintf("\033[%d;%dm%s\033[0m", 1, color, currentLine))
currentLine = word
} else {
if currentLine != "" {
currentLine += " "
}
currentLine += word
}
}
if currentLine != "" {
wrappedContent = append(wrappedContent, fmt.Sprintf("\033[%d;%dm%s\033[0m", 1, color, currentLine))
}

return strings.Join(wrappedContent, "\n")
}

func GetScoreColor(score float64) tablewriter.Colors {
if score == 0.0 {
return tablewriter.Colors{tablewriter.FgRedColor, tablewriter.Bold}
} else if score < 5.0 {
return tablewriter.Colors{tablewriter.FgHiYellowColor, tablewriter.Bold}
}
return tablewriter.Colors{tablewriter.FgGreenColor, tablewriter.Bold}
}
6 changes: 3 additions & 3 deletions pkg/compliance/compliance.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,20 @@ func ComplianceResult(ctx context.Context, doc sbom.Document, reportType, fileNa

switch {
case reportType == BSI_REPORT:
bsiResult(ctx, doc, fileName, outFormat)
bsiResult(ctx, doc, fileName, outFormat, coloredOutput)

case reportType == BSI_V2_REPORT:
bsiV2Result(ctx, doc, fileName, outFormat)

case reportType == NTIA_REPORT:
ntiaResult(ctx, doc, fileName, outFormat)
ntiaResult(ctx, doc, fileName, outFormat, coloredOutput)

case reportType == OCT_TELCO:
if doc.Spec().GetSpecType() != "spdx" {
fmt.Println("The Provided SBOM spec is other than SPDX. Open Chain Telco only support SPDX specs SBOMs.")
return nil
}
octResult(ctx, doc, fileName, outFormat)
octResult(ctx, doc, fileName, outFormat, coloredOutput)

case reportType == FSCT_V3:
fsct.Result(ctx, doc, fileName, outFormat, coloredOutput)
Expand Down
48 changes: 22 additions & 26 deletions pkg/compliance/fsct/fsct_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

"github.com/google/uuid"
"github.com/interlynk-io/sbomqs/pkg/compliance/common"
"github.com/interlynk-io/sbomqs/pkg/compliance/db"
"github.com/olekukonko/tablewriter"
"sigs.k8s.io/release-utils/version"
Expand Down Expand Up @@ -199,15 +200,7 @@ func fsctDetailedReport(db *db.DB, fileName string, coloredOutput bool) {
table.SetAutoMergeCellsByColumnIndex([]int{0})

if coloredOutput {
// Set header colors if the colors flag is true
table.SetHeaderColor(
tablewriter.Colors{tablewriter.FgHiWhiteColor, tablewriter.Bold},
tablewriter.Colors{tablewriter.FgHiWhiteColor, tablewriter.Bold},
tablewriter.Colors{tablewriter.FgHiWhiteColor, tablewriter.Bold},
tablewriter.Colors{tablewriter.FgHiWhiteColor, tablewriter.Bold},
tablewriter.Colors{tablewriter.FgHiWhiteColor, tablewriter.Bold},
tablewriter.Colors{tablewriter.FgHiWhiteColor, tablewriter.Bold},
)
common.SetHeaderColor(table, 6)
}

sections := fsctConstructSections(db)
Expand All @@ -218,23 +211,11 @@ func fsctDetailedReport(db *db.DB, fileName string, coloredOutput bool) {
sectionID += "*"
}

// Define maturity color based on the flag
var maturityColor tablewriter.Colors
if coloredOutput {
switch section.Maturity {
case "None":
maturityColor = tablewriter.Colors{tablewriter.FgRedColor, tablewriter.Bold}
case "Minimum":
maturityColor = tablewriter.Colors{tablewriter.FgGreenColor, tablewriter.Bold}
case "Recommended":
maturityColor = tablewriter.Colors{tablewriter.FgCyanColor, tablewriter.Bold}
case "Aspirational":
maturityColor = tablewriter.Colors{tablewriter.FgHiYellowColor, tablewriter.Bold}
}
}

// Use Rich() with color settings only if colors is true
if coloredOutput {
var maturityColor tablewriter.Colors

Check failure on line 216 in pkg/compliance/fsct/fsct_report.go

View workflow job for this annotation

GitHub Actions / lint

S1021: should merge variable declaration with assignment on next line (gosimple)
maturityColor = getMaturityColor(section.Maturity)

table.Rich([]string{
section.ElementID,
sectionID,
Expand All @@ -244,9 +225,9 @@ func fsctDetailedReport(db *db.DB, fileName string, coloredOutput bool) {
section.Maturity,
}, []tablewriter.Colors{
{tablewriter.FgHiMagentaColor, tablewriter.Bold},
{},
{tablewriter.FgHiCyanColor},
{tablewriter.FgHiBlueColor, tablewriter.Bold},
{tablewriter.FgHiWhiteColor, tablewriter.Bold},
{tablewriter.FgHiCyanColor, tablewriter.Bold},
maturityColor,
maturityColor,
})
Expand All @@ -269,3 +250,18 @@ func fsctBasicReport(db *db.DB, fileName string) {
fmt.Printf("Framing Software Component Transparency (v3)\n")
fmt.Printf("Score:%0.1f for %s\n", score.totalScore(), fileName)
}

func getMaturityColor(maturity string) tablewriter.Colors {
switch maturity {
case "None":
return tablewriter.Colors{tablewriter.FgRedColor, tablewriter.Bold}
case "Minimum":
return tablewriter.Colors{tablewriter.FgGreenColor, tablewriter.Bold}
case "Recommended":
return tablewriter.Colors{tablewriter.FgCyanColor, tablewriter.Bold}
case "Aspirational":
return tablewriter.Colors{tablewriter.FgHiYellowColor, tablewriter.Bold}
default:
return tablewriter.Colors{}
}
}
4 changes: 2 additions & 2 deletions pkg/compliance/ntia.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const (
SCORE_ZERO = 0.0
)

func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string) {
func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string, colorOutput bool) {
log := logger.FromContext(ctx)
log.Debug("compliance.ntiaResult()")

Expand All @@ -59,7 +59,7 @@ func ntiaResult(ctx context.Context, doc sbom.Document, fileName string, outForm
}

if outFormat == "detailed" {
ntiaDetailedReport(db, fileName)
ntiaDetailedReport(db, fileName, colorOutput)
}
}

Expand Down
21 changes: 19 additions & 2 deletions pkg/compliance/ntia_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/google/uuid"
"github.com/interlynk-io/sbomqs/pkg/compliance/common"
"github.com/interlynk-io/sbomqs/pkg/compliance/db"
"github.com/olekukonko/tablewriter"
"sigs.k8s.io/release-utils/version"
Expand Down Expand Up @@ -136,7 +137,7 @@ func ntiaConstructSections(db *db.DB) []ntiaSection {
return sortedSections
}

func ntiaDetailedReport(db *db.DB, fileName string) {
func ntiaDetailedReport(db *db.DB, fileName string, colorOutput bool) {
table := tablewriter.NewWriter(os.Stdout)
score := ntiaAggregateScore(db)

Expand All @@ -162,7 +163,23 @@ func ntiaDetailedReport(db *db.DB, fileName string) {
if !section.Required {
sectionID = sectionID + "*"
}
table.Append([]string{section.ElementID, sectionID, section.DataField, section.ElementResult, fmt.Sprintf("%0.1f", section.Score)})

if colorOutput {
// disable tablewriter's auto-wrapping
table.SetAutoWrapText(false)
columnWidth := 30
common.SetHeaderColor(table, 5)

table = common.ColorTable(table,
section.ElementID,
section.ID,
section.ElementResult,
section.DataField,
section.Score,
columnWidth)
} else {
table.Append([]string{section.ElementID, sectionID, section.DataField, section.ElementResult, fmt.Sprintf("%0.1f", section.Score)})
}
}
table.Render()
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/compliance/oct.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
"github.com/samber/lo"
)

func octResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string) {
func octResult(ctx context.Context, doc sbom.Document, fileName string, outFormat string, colorOutput bool) {
log := logger.FromContext(ctx)
log.Debug("compliance.octResult()")
dtb := db.NewDB()
Expand Down Expand Up @@ -58,7 +58,7 @@ func octResult(ctx context.Context, doc sbom.Document, fileName string, outForma
}

if outFormat == "detailed" {
octDetailedReport(dtb, fileName)
octDetailedReport(dtb, fileName, colorOutput)
}
}

Expand Down
Loading

0 comments on commit 1afa6ac

Please sign in to comment.