Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 70 additions & 20 deletions cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ type controlSummary struct {
compliance float64
issues int
skipped bool
codes []string
}

func printBanner() {
Expand Down Expand Up @@ -659,11 +660,16 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
controlName = "Container images must not use forbidden tags (pinned by digest)"
}

imageCodes := []string{string(control.CodeImageForbiddenTag)}
if result.ImageForbiddenTagsResult.MustBePinnedByDigest {
imageCodes = []string{string(control.CodeImageNotPinnedByDigest)}
}
ctrl := controlSummary{
name: controlName,
compliance: result.ImageForbiddenTagsResult.Compliance,
issues: len(result.ImageForbiddenTagsResult.Issues),
skipped: result.ImageForbiddenTagsResult.Skipped,
codes: imageCodes,
}
controls = append(controls, ctrl)

Expand All @@ -680,7 +686,8 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if len(result.ImageForbiddenTagsResult.Issues) > 0 {
fmt.Printf("\n %sImages Not Pinned By Digest Found:%s\n", colorYellow, colorReset)
for _, issue := range result.ImageForbiddenTagsResult.Issues {
fmt.Printf(" %s•%s Job '%s' uses image without digest pinning: %s\n", colorYellow, colorReset, issue.Job, issue.Link)
fmt.Printf(" %s•%s [%s] Job '%s' uses image without digest pinning: %s\n", colorYellow, colorReset, issue.Code, issue.Job, issue.Link)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
} else {
Expand All @@ -691,7 +698,8 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if len(result.ImageForbiddenTagsResult.Issues) > 0 {
fmt.Printf("\n %sForbidden Tags Found:%s\n", colorYellow, colorReset)
for _, issue := range result.ImageForbiddenTagsResult.Issues {
fmt.Printf(" %s•%s Job '%s' uses forbidden tag '%s' (image: %s)\n", colorYellow, colorReset, issue.Job, issue.Tag, issue.Link)
fmt.Printf(" %s•%s [%s] Job '%s' uses forbidden tag '%s' (image: %s)\n", colorYellow, colorReset, issue.Code, issue.Job, issue.Tag, issue.Link)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand All @@ -705,6 +713,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
compliance: result.ImageAuthorizedSourcesResult.Compliance,
issues: len(result.ImageAuthorizedSourcesResult.Issues),
skipped: result.ImageAuthorizedSourcesResult.Skipped,
codes: []string{string(control.CodeImageUnauthorizedSource)},
}
controls = append(controls, ctrl)

Expand All @@ -720,7 +729,8 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if len(result.ImageAuthorizedSourcesResult.Issues) > 0 {
fmt.Printf("\n %sUnauthorized Images Found:%s\n", colorYellow, colorReset)
for _, issue := range result.ImageAuthorizedSourcesResult.Issues {
fmt.Printf(" %s•%s Job '%s' uses unauthorized image: %s\n", colorYellow, colorReset, issue.Job, issue.Link)
fmt.Printf(" %s•%s [%s] Job '%s' uses unauthorized image: %s\n", colorYellow, colorReset, issue.Code, issue.Job, issue.Link)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand All @@ -734,6 +744,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
compliance: result.BranchProtectionResult.Compliance,
issues: len(result.BranchProtectionResult.Issues),
skipped: result.BranchProtectionResult.Skipped,
codes: []string{string(control.CodeBranchUnprotected), string(control.CodeBranchNonCompliant)},
}
controls = append(controls, ctrl)

Expand All @@ -754,9 +765,10 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
fmt.Printf("\n %sIssues Found:%s\n", colorYellow, colorReset)
for _, issue := range result.BranchProtectionResult.Issues {
if issue.Type == "unprotected" {
fmt.Printf(" %s•%s Branch '%s' is not protected\n", colorYellow, colorReset, issue.BranchName)
fmt.Printf(" %s•%s [%s] Branch '%s' is not protected\n", colorYellow, colorReset, issue.Code, issue.BranchName)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
} else {
fmt.Printf(" %s•%s Branch '%s' has non-compliant protection settings\n", colorYellow, colorReset, issue.BranchName)
fmt.Printf(" %s•%s [%s] Branch '%s' has non-compliant protection settings\n", colorYellow, colorReset, issue.Code, issue.BranchName)
if issue.AllowForcePushDisplay {
fmt.Printf(" └─ Force push is allowed (should be disabled)\n")
}
Expand All @@ -769,6 +781,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if issue.MinPushAccessLevelDisplay {
fmt.Printf(" └─ Push access level is too low (%d, minimum: %d)\n", issue.MinPushAccessLevel, issue.AuthorizedMinPushAccessLevel)
}
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand All @@ -783,6 +796,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
compliance: result.HardcodedJobsResult.Compliance,
issues: len(result.HardcodedJobsResult.Issues),
skipped: result.HardcodedJobsResult.Skipped,
codes: []string{string(control.CodeJobHardcoded)},
}
controls = append(controls, ctrl)

Expand All @@ -797,7 +811,8 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if len(result.HardcodedJobsResult.Issues) > 0 {
fmt.Printf("\n %sHardcoded Jobs Found:%s\n", colorYellow, colorReset)
for _, issue := range result.HardcodedJobsResult.Issues {
fmt.Printf(" %s•%s Job '%s' is hardcoded (not from include/component)\n", colorYellow, colorReset, issue.JobName)
fmt.Printf(" %s•%s [%s] Job '%s' is hardcoded (not from include/component)\n", colorYellow, colorReset, issue.Code, issue.JobName)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand All @@ -811,6 +826,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
compliance: result.OutdatedIncludesResult.Compliance,
issues: len(result.OutdatedIncludesResult.Issues),
skipped: result.OutdatedIncludesResult.Skipped,
codes: []string{string(control.CodeIncludeOutdated)},
}
controls = append(controls, ctrl)

Expand All @@ -825,7 +841,8 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if len(result.OutdatedIncludesResult.Issues) > 0 {
fmt.Printf("\n %sOutdated Includes Found:%s\n", colorYellow, colorReset)
for _, issue := range result.OutdatedIncludesResult.Issues {
fmt.Printf(" %s•%s %s uses version '%s' (latest: %s)\n", colorYellow, colorReset, issue.GitlabIncludeLocation, issue.Version, issue.LatestVersion)
fmt.Printf(" %s•%s [%s] %s uses version '%s' (latest: %s)\n", colorYellow, colorReset, issue.Code, issue.GitlabIncludeLocation, issue.Version, issue.LatestVersion)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand All @@ -839,6 +856,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
compliance: result.ForbiddenVersionsIncludesResult.Compliance,
issues: len(result.ForbiddenVersionsIncludesResult.Issues),
skipped: result.ForbiddenVersionsIncludesResult.Skipped,
codes: []string{string(control.CodeIncludeForbiddenVersion)},
}
controls = append(controls, ctrl)

Expand All @@ -854,7 +872,8 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if len(result.ForbiddenVersionsIncludesResult.Issues) > 0 {
fmt.Printf("\n %sForbidden Versions Found:%s\n", colorYellow, colorReset)
for _, issue := range result.ForbiddenVersionsIncludesResult.Issues {
fmt.Printf(" %s•%s %s uses forbidden version '%s'\n", colorYellow, colorReset, issue.GitlabIncludeLocation, issue.Version)
fmt.Printf(" %s•%s [%s] %s uses forbidden version '%s'\n", colorYellow, colorReset, issue.Code, issue.GitlabIncludeLocation, issue.Version)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand All @@ -869,6 +888,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
compliance: result.RequiredComponentsResult.Compliance,
issues: totalComponentIssues,
skipped: result.RequiredComponentsResult.Skipped,
codes: []string{string(control.CodeComponentMissing), string(control.CodeComponentOverridden)},
}
controls = append(controls, ctrl)

Expand All @@ -883,17 +903,19 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if len(result.RequiredComponentsResult.Issues) > 0 {
fmt.Printf("\n %sMissing Components:%s\n", colorYellow, colorReset)
for _, issue := range result.RequiredComponentsResult.Issues {
fmt.Printf(" %s•%s %s (group %d)\n", colorYellow, colorReset, issue.ComponentPath, issue.GroupIndex+1)
fmt.Printf(" %s•%s [%s] %s (group %d)\n", colorYellow, colorReset, issue.Code, issue.ComponentPath, issue.GroupIndex+1)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}

if len(result.RequiredComponentsResult.OverriddenIssues) > 0 {
fmt.Printf("\n %sOverridden Components:%s\n", colorYellow, colorReset)
for _, issue := range result.RequiredComponentsResult.OverriddenIssues {
fmt.Printf(" %s•%s %s (group %d)\n", colorYellow, colorReset, issue.ComponentPath, issue.GroupIndex+1)
fmt.Printf(" %s•%s [%s] %s (group %d)\n", colorYellow, colorReset, issue.Code, issue.ComponentPath, issue.GroupIndex+1)
for _, job := range issue.OverriddenJobs {
fmt.Printf(" job %s%s%s overrides: %s\n", colorDim, job.JobName, colorReset, strings.Join(job.OverriddenKeys, ", "))
}
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand All @@ -908,6 +930,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
compliance: result.RequiredTemplatesResult.Compliance,
issues: totalTemplateIssues,
skipped: result.RequiredTemplatesResult.Skipped,
codes: []string{string(control.CodeTemplateMissing), string(control.CodeTemplateOverridden)},
}
controls = append(controls, ctrl)

Expand All @@ -922,17 +945,19 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
if len(result.RequiredTemplatesResult.Issues) > 0 {
fmt.Printf("\n %sMissing Templates:%s\n", colorYellow, colorReset)
for _, issue := range result.RequiredTemplatesResult.Issues {
fmt.Printf(" %s•%s %s (group %d)\n", colorYellow, colorReset, issue.TemplatePath, issue.GroupIndex+1)
fmt.Printf(" %s•%s [%s] %s (group %d)\n", colorYellow, colorReset, issue.Code, issue.TemplatePath, issue.GroupIndex+1)
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}

if len(result.RequiredTemplatesResult.OverriddenIssues) > 0 {
fmt.Printf("\n %sOverridden Templates:%s\n", colorYellow, colorReset)
for _, issue := range result.RequiredTemplatesResult.OverriddenIssues {
fmt.Printf(" %s•%s %s (group %d)\n", colorYellow, colorReset, issue.TemplatePath, issue.GroupIndex+1)
fmt.Printf(" %s•%s [%s] %s (group %d)\n", colorYellow, colorReset, issue.Code, issue.TemplatePath, issue.GroupIndex+1)
for _, job := range issue.OverriddenJobs {
fmt.Printf(" job %s%s%s overrides: %s\n", colorDim, job.JobName, colorReset, strings.Join(job.OverriddenKeys, ", "))
}
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand All @@ -946,6 +971,7 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
compliance: result.DebugTraceResult.Compliance,
issues: len(result.DebugTraceResult.Issues),
skipped: result.DebugTraceResult.Skipped,
codes: []string{string(control.CodeDebugTraceEnabled)},
}
controls = append(controls, ctrl)

Expand All @@ -962,10 +988,11 @@ func outputText(result *control.AnalysisResult, threshold, compliance float64, c
for _, issue := range result.DebugTraceResult.Issues {
location := issue.Location
if location == "global" {
fmt.Printf(" %s•%s %s = \"%s\" (global variables)\n", colorYellow, colorReset, issue.VariableName, issue.Value)
fmt.Printf(" %s•%s [%s] %s = \"%s\" (global variables)\n", colorYellow, colorReset, issue.Code, issue.VariableName, issue.Value)
} else {
fmt.Printf(" %s•%s %s = \"%s\" (job '%s')\n", colorYellow, colorReset, issue.VariableName, issue.Value, location)
fmt.Printf(" %s•%s [%s] %s = \"%s\" (job '%s')\n", colorYellow, colorReset, issue.Code, issue.VariableName, issue.Value, location)
}
fmt.Printf(" %s↳ docs: %s%s\n", colorDim, issue.DocURL, colorReset)
}
}
}
Expand Down Expand Up @@ -1022,35 +1049,47 @@ func printSectionHeader(name string) {
func printIssuesTable(controls []controlSummary) {
fmt.Printf(" %sIssues%s\n", colorBold, colorReset)

// Calculate column widths dynamically based on longest control name
// Calculate column widths dynamically based on longest control name and codes
controlWidth := 52 // minimum width
for _, ctrl := range controls {
needed := len(ctrl.name) + 2 // +2 for padding
if needed > controlWidth {
controlWidth = needed
}
}
codesWidth := 22 // enough for "PLB-0101, PLB-0102"
for _, ctrl := range controls {
codesStr := strings.Join(ctrl.codes, ", ")
needed := len(codesStr) + 2
if needed > codesWidth {
codesWidth = needed
}
}
issuesWidth := 10

// Top border
fmt.Printf(" %s╔%s╤%s╗%s\n",
fmt.Printf(" %s╔%s╤%s╤%s╗%s\n",
colorCyan,
strings.Repeat("═", controlWidth),
strings.Repeat("═", codesWidth),
strings.Repeat("═", issuesWidth),
colorReset)

// Header row
fmt.Printf(" %s║%s %-*s %s│%s %*s %s║%s\n",
fmt.Printf(" %s║%s %-*s %s│%s %-*s %s│%s %*s %s║%s\n",
colorCyan, colorReset,
controlWidth-2, "Control",
colorCyan, colorReset,
codesWidth-2, "Codes",
colorCyan, colorReset,
issuesWidth-2, "Issues",
colorCyan, colorReset)

// Header separator
fmt.Printf(" %s╟%s┼%s╢%s\n",
fmt.Printf(" %s╟%s┼%s┼%s╢%s\n",
colorCyan,
strings.Repeat("─", controlWidth),
strings.Repeat("─", codesWidth),
strings.Repeat("─", issuesWidth),
colorReset)

Expand All @@ -1068,20 +1107,31 @@ func printIssuesTable(controls []controlSummary) {
issueColor = colorRed
}

fmt.Printf(" %s║%s %-*s %s│%s %s%*s%s %s║%s\n",
codesStr := strings.Join(ctrl.codes, ", ")
if ctrl.skipped {
codesStr = "-"
}

fmt.Printf(" %s║%s %-*s %s│%s %-*s %s│%s %s%*s%s %s║%s\n",
colorCyan, colorReset,
controlWidth-2, ctrl.name,
colorCyan, colorReset,
codesWidth-2, codesStr,
colorCyan, colorReset,
issueColor, issuesWidth-2, issueStr, colorReset,
colorCyan, colorReset)
}

// Bottom border
fmt.Printf(" %s╚%s╧%s╝%s\n",
fmt.Printf(" %s╚%s╧%s╧%s╝%s\n",
colorCyan,
strings.Repeat("═", controlWidth),
strings.Repeat("═", codesWidth),
strings.Repeat("═", issuesWidth),
colorReset)

// Docs footer
fmt.Printf(" %s↳ docs: https://getplumber.io/e/<code>%s\n", colorDim, colorReset)
}

func printComplianceTable(controls []controlSummary, overallCompliance, threshold float64) {
Expand Down
Loading
Loading