scorecard/pkg/scorecard_result.go
Naveen 6c1c789dc5
🌱 v3 upgrade changes (#1118)
v3 go.mod changes
2021-10-07 18:16:01 -05:00

172 lines
4.4 KiB
Go

// Copyright 2020 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pkg
import (
"fmt"
"io"
"os"
"time"
"github.com/olekukonko/tablewriter"
"go.uber.org/zap/zapcore"
"github.com/ossf/scorecard/v3/checker"
docs "github.com/ossf/scorecard/v3/docs/checks"
sce "github.com/ossf/scorecard/v3/errors"
)
// ScorecardInfo contains information about the scorecard code that was run.
type ScorecardInfo struct {
Version string
CommitSHA string
}
// RepoInfo contains information about the repo that was analyzed.
type RepoInfo struct {
Name string
CommitSHA string
}
// ScorecardResult struct is returned on a successful Scorecard run.
type ScorecardResult struct {
Repo RepoInfo
Date time.Time
Scorecard ScorecardInfo
Checks []checker.CheckResult
Metadata []string
}
func scoreToString(s float64) string {
if s == checker.InconclusiveResultScore {
return "?"
}
return fmt.Sprintf("%.1f", s)
}
// GetAggregateScore returns the aggregate score.
func (r *ScorecardResult) GetAggregateScore(checkDocs docs.Doc) (float64, error) {
// TODO: calculate the score and make it a field
// of ScorecardResult
weights := map[string]float64{"Critical": 10, "High": 7.5, "Medium": 5, "Low": 2.5}
// Note: aggregate score changes depending on which checks are run.
total := float64(0)
score := float64(0)
for i := range r.Checks {
check := r.Checks[i]
doc, e := checkDocs.GetCheck(check.Name)
if e != nil {
return checker.InconclusiveResultScore,
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("GetCheck: %s: %v", check.Name, e))
}
risk := doc.GetRisk()
rs, exists := weights[risk]
if !exists {
return checker.InconclusiveResultScore,
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("Invalid risk for %s: '%s'", check.Name, risk))
}
// This indicates an inconclusive score.
if check.Score < checker.MinResultScore {
continue
}
total += rs
score += rs * float64(check.Score)
}
// Inconclusive result.
if total == 0 {
return checker.InconclusiveResultScore, nil
}
return score / total, nil
}
// AsString returns ScorecardResult in string format.
func (r *ScorecardResult) AsString(showDetails bool, logLevel zapcore.Level,
checkDocs docs.Doc, writer io.Writer) error {
data := make([][]string, len(r.Checks))
//nolint
for i, row := range r.Checks {
const withdetails = 5
const withoutdetails = 4
var x []string
if showDetails {
x = make([]string, withdetails)
} else {
x = make([]string, withoutdetails)
}
// UPGRADEv2: rename variable.
if row.Score == checker.InconclusiveResultScore {
x[0] = "?"
} else {
x[0] = fmt.Sprintf("%d / %d", row.Score, checker.MaxResultScore)
}
cdoc, e := checkDocs.GetCheck(row.Name)
if e != nil {
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("GetCheck: %s: %v", row.Name, e))
}
doc := cdoc.GetDocumentationURL(r.Scorecard.CommitSHA)
x[1] = row.Name
x[2] = row.Reason
if showDetails {
details, show := detailsToString(row.Details2, logLevel)
if show {
x[3] = details
}
x[4] = doc
} else {
x[3] = doc
}
data[i] = x
}
score, err := r.GetAggregateScore(checkDocs)
if err != nil {
return err
}
s := fmt.Sprintf("Aggregate score: %s / %d\n\n", scoreToString(score), checker.MaxResultScore)
if score == checker.InconclusiveResultScore {
s = "Aggregate score: ?\n\n"
}
fmt.Fprint(os.Stdout, s)
fmt.Fprintln(os.Stdout, "Check scores:")
table := tablewriter.NewWriter(os.Stdout)
header := []string{"Score", "Name", "Reason"}
if showDetails {
header = append(header, "Details")
}
header = append(header, "Documentation/Remediation")
table.SetHeader(header)
table.SetBorders(tablewriter.Border{Left: true, Top: true, Right: true, Bottom: true})
table.SetRowSeparator("-")
table.SetRowLine(true)
table.SetCenterSeparator("|")
table.AppendBulk(data)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetRowLine(true)
table.Render()
return nil
}