Add CIIClient interface (#1262)

Co-authored-by: Azeem Shaikh <azeems@google.com>
This commit is contained in:
Azeem Shaikh 2021-11-14 21:46:41 -05:00 committed by GitHub
parent d4904555b4
commit 6223b6620a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 411 additions and 105 deletions

View File

@ -109,14 +109,16 @@ cron/data/metadata.pb.go: cron/data/metadata.proto | $(PROTOC)
protoc --go_out=../../../ cron/data/metadata.proto
generate-mocks: ## Compiles and generates all mocks using mockgen.
generate-mocks: clients/mockrepo/client.go clients/mockrepo/repo.go
clients/mockrepo/client.go: clients/repo_client.go
generate-mocks: clients/mockclients/repo_client.go clients/mockclients/repo.go clients/mockclients/cii_client.go
clients/mockclients/repo_client.go: clients/repo_client.go
# Generating MockRepoClient
$(MOCKGEN) -source=clients/repo_client.go -destination clients/mockrepo/client.go -package mockrepo -copyright_file clients/mockrepo/license.txt
clients/mockrepo/repo.go: clients/repo.go
# Generating MockRepoClient
$(MOCKGEN) -source=clients/repo.go -destination clients/mockrepo/repo.go -package mockrepo -copyright_file clients/mockrepo/license.txt
$(MOCKGEN) -source=clients/repo_client.go -destination=clients/mockclients/repo_client.go -package=mockrepo -copyright_file=clients/mockclients/license.txt
clients/mockclients/repo.go: clients/repo.go
# Generating MockRepo
$(MOCKGEN) -source=clients/repo.go -destination=clients/mockclients/repo.go -package=mockrepo -copyright_file=clients/mockclients/license.txt
clients/mockclients/cii_client.go: clients/cii_client.go
# Generating MockCIIClient
$(MOCKGEN) -source=clients/cii_client.go -destination=clients/mockclients/cii_client.go -package=mockrepo -copyright_file=clients/mockclients/license.txt
generate-docs: ## Generates docs
generate-docs: validate-docs docs/checks.md

View File

@ -24,6 +24,7 @@ import (
type CheckRequest struct {
Ctx context.Context
RepoClient clients.RepoClient
CIIClient clients.CIIBestPracticesClient
Dlogger DetailLogger
Repo clients.Repo
}

View File

@ -21,7 +21,7 @@ import (
"github.com/ossf/scorecard/v3/checker"
"github.com/ossf/scorecard/v3/clients"
"github.com/ossf/scorecard/v3/clients/mockrepo"
mockrepo "github.com/ossf/scorecard/v3/clients/mockclients"
sce "github.com/ossf/scorecard/v3/errors"
scut "github.com/ossf/scorecard/v3/utests"
)

View File

@ -15,111 +15,47 @@
package checks
import (
"encoding/json"
"errors"
"fmt"
"io"
"math"
"net/http"
"strings"
"time"
"github.com/ossf/scorecard/v3/checker"
"github.com/ossf/scorecard/v3/clients"
sce "github.com/ossf/scorecard/v3/errors"
)
// CheckCIIBestPractices is the registered name for CIIBestPractices.
const CheckCIIBestPractices = "CII-Best-Practices"
var errTooManyRequests = errors.New("failed after exponential backoff")
const (
// CheckCIIBestPractices is the registered name for CIIBestPractices.
CheckCIIBestPractices = "CII-Best-Practices"
silverScore = 7
passingScore = 5
inProgressScore = 2
)
//nolint:gochecknoinits
func init() {
registerCheck(CheckCIIBestPractices, CIIBestPractices)
}
type expBackoffTransport struct {
numRetries uint8
}
func (transport *expBackoffTransport) RoundTrip(req *http.Request) (*http.Response, error) {
for i := 0; i < int(transport.numRetries); i++ {
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != http.StatusTooManyRequests {
// nolint: wrapcheck
return resp, err
}
time.Sleep(time.Duration(math.Pow(2, float64(i))) * time.Second)
}
return nil, errTooManyRequests
}
type response struct {
BadgeLevel string `json:"badge_level"`
}
// CIIBestPractices runs CII-Best-Practices check.
func CIIBestPractices(c *checker.CheckRequest) checker.CheckResult {
// TODO: not supported for local clients.
repoURI := fmt.Sprintf("https://%s", c.Repo.URI())
url := fmt.Sprintf("https://bestpractices.coreinfrastructure.org/projects.json?url=%s", repoURI)
req, err := http.NewRequestWithContext(c.Ctx, "GET", url, nil)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("http.NewRequestWithContext: %v", err))
return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e)
}
httpClient := http.Client{
Transport: &expBackoffTransport{
numRetries: 3,
},
}
resp, err := httpClient.Do(req)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("http.NewRequestWithContext: %v", err))
return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e)
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("io.ReadAll: %v", err))
return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e)
}
parsedResponse := []response{}
if err := json.Unmarshal(b, &parsedResponse); err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal,
fmt.Sprintf("json.Unmarshal on %s - %s: %v", resp.Status, parsedResponse, err))
return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e)
}
if len(parsedResponse) < 1 {
return checker.CreateMinScoreResult(CheckCIIBestPractices, "no badge found")
}
result := parsedResponse[0]
if result.BadgeLevel != "" {
// Three levels: passing, silver and gold,
// https://bestpractices.coreinfrastructure.org/en/criteria.
const silverScore = 7
const passingScore = 5
const inProgressScore = 2
switch {
default:
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("unsupported badge: %v", result.BadgeLevel))
return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e)
case strings.Contains(result.BadgeLevel, "in_progress"):
badgeLevel, err := c.CIIClient.GetBadgeLevel(c.Ctx, c.Repo.URI())
if err == nil {
switch badgeLevel {
case clients.NotFound:
return checker.CreateMinScoreResult(CheckCIIBestPractices, "no badge detected")
case clients.InProgress:
return checker.CreateResultWithScore(CheckCIIBestPractices, "badge detected: in_progress", inProgressScore)
case strings.Contains(result.BadgeLevel, "silver"):
return checker.CreateResultWithScore(CheckCIIBestPractices, "badge detected: silver", silverScore)
case strings.Contains(result.BadgeLevel, "gold"):
return checker.CreateMaxScoreResult(CheckCIIBestPractices, "badge detected: gold")
case strings.Contains(result.BadgeLevel, "passing"):
case clients.Passing:
return checker.CreateResultWithScore(CheckCIIBestPractices, "badge detected: passing", passingScore)
case clients.Silver:
return checker.CreateResultWithScore(CheckCIIBestPractices, "badge detected: silver", silverScore)
case clients.Gold:
return checker.CreateMaxScoreResult(CheckCIIBestPractices, "badge detected: gold")
case clients.Unknown:
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("unsupported badge: %v", badgeLevel))
return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e)
}
}
return checker.CreateMinScoreResult(CheckCIIBestPractices, "no badge detected")
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e)
}

View File

@ -0,0 +1,131 @@
// 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 checks
import (
"context"
"errors"
"testing"
"github.com/golang/mock/gomock"
"github.com/ossf/scorecard/v3/checker"
"github.com/ossf/scorecard/v3/clients"
mockrepo "github.com/ossf/scorecard/v3/clients/mockclients"
sce "github.com/ossf/scorecard/v3/errors"
scut "github.com/ossf/scorecard/v3/utests"
)
var errTest = errors.New("test error")
func TestCIIBestPractices(t *testing.T) {
t.Parallel()
tests := []struct {
err error
name string
uri string
expected scut.TestReturn
badgeLevel clients.BadgeLevel
}{
{
name: "CheckURIUsed",
uri: "github.com/owner/repo",
badgeLevel: clients.NotFound,
expected: scut.TestReturn{
Score: checker.MinResultScore,
},
},
{
name: "CheckErrorHandling",
err: errTest,
expected: scut.TestReturn{
Score: -1,
Error: sce.ErrScorecardInternal,
},
},
{
name: "NotFoundBadge",
badgeLevel: clients.NotFound,
expected: scut.TestReturn{
Score: checker.MinResultScore,
},
},
{
name: "InProgressBadge",
badgeLevel: clients.InProgress,
expected: scut.TestReturn{
Score: inProgressScore,
},
},
{
name: "PassingBadge",
badgeLevel: clients.Passing,
expected: scut.TestReturn{
Score: passingScore,
},
},
{
name: "SilverBadge",
badgeLevel: clients.Silver,
expected: scut.TestReturn{
Score: silverScore,
},
},
{
name: "GoldBadge",
badgeLevel: clients.Gold,
expected: scut.TestReturn{
Score: checker.MaxResultScore,
},
},
{
name: "UnknownBadge",
badgeLevel: clients.Unknown,
expected: scut.TestReturn{
Score: -1,
Error: sce.ErrScorecardInternal,
},
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
mockRepo := mockrepo.NewMockRepo(ctrl)
mockRepo.EXPECT().URI().Return(tt.uri).AnyTimes()
mockCIIClient := mockrepo.NewMockCIIBestPracticesClient(ctrl)
mockCIIClient.EXPECT().GetBadgeLevel(gomock.Any(), tt.uri).DoAndReturn(
func(context.Context, string) (clients.BadgeLevel, error) {
return tt.badgeLevel, tt.err
}).MinTimes(1)
req := checker.CheckRequest{
Repo: mockRepo,
CIIClient: mockCIIClient,
}
res := CIIBestPractices(&req)
dl := scut.TestDetailLogger{}
if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &res, &dl) {
t.Fail()
}
ctrl.Finish()
})
}
}

48
clients/cii_client.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2021 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 clients
import (
"context"
)
// BadgeLevel corresponds to CII-Best-Practices badge levels.
// https://bestpractices.coreinfrastructure.org/en
type BadgeLevel uint
const (
// Unknown or non-parsable CII Best Practices badge.
Unknown BadgeLevel = iota
// NotFound represents when CII Best Practices returns an empty response for a project.
NotFound
// InProgress state of CII Best Practices badge.
InProgress
// Passing level for CII Best Practices badge.
Passing
// Silver level for CII Best Practices badge.
Silver
// Gold level for CII Best Practices badge.
Gold
)
// CIIBestPracticesClient interface returns the BadgeLevel for a repo URL.
type CIIBestPracticesClient interface {
GetBadgeLevel(ctx context.Context, uri string) (BadgeLevel, error)
}
// DefaultCIIBestPracticesClient returns HTTPClientCIIBestPractices implementation of the interface.
func DefaultCIIBestPracticesClient() CIIBestPracticesClient {
return &HTTPClientCIIBestPractices{}
}

112
clients/cii_http_client.go Normal file
View File

@ -0,0 +1,112 @@
// Copyright 2021 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 clients
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"net/http"
"strings"
"time"
)
const (
inProgressResp = "in_progress"
passingResp = "passing"
silverResp = "silver"
goldResp = "gold"
)
var (
errTooManyRequests = errors.New("failed after exponential backoff")
errUnsupportedBadge = errors.New("unsupported badge")
)
// HTTPClientCIIBestPractices implements the CIIBestPracticesClient interface.
// A HTTP client with exponential backoff is used to communicate with the CII Best Practices servers.
type HTTPClientCIIBestPractices struct{}
type response struct {
BadgeLevel string `json:"badge_level"`
}
type expBackoffTransport struct {
numRetries uint8
}
func (transport *expBackoffTransport) RoundTrip(req *http.Request) (*http.Response, error) {
for i := 0; i < int(transport.numRetries); i++ {
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != http.StatusTooManyRequests {
// nolint: wrapcheck
return resp, err
}
time.Sleep(time.Duration(math.Pow(2, float64(i))) * time.Second)
}
return nil, errTooManyRequests
}
// GetBadgeLevel implements CIIBestPracticesClient.GetBadgeLevel.
func (client *HTTPClientCIIBestPractices) GetBadgeLevel(ctx context.Context, uri string) (BadgeLevel, error) {
repoURI := fmt.Sprintf("https://%s", uri)
url := fmt.Sprintf("https://bestpractices.coreinfrastructure.org/projects.json?url=%s", repoURI)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return Unknown, fmt.Errorf("error during http.NewRequestWithContext: %w", err)
}
httpClient := http.Client{
Transport: &expBackoffTransport{
numRetries: 3,
},
}
resp, err := httpClient.Do(req)
if err != nil {
return Unknown, fmt.Errorf("error during http.Do: %w", err)
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return Unknown, fmt.Errorf("error during io.ReadAll: %w", err)
}
parsedResponse := []response{}
if err := json.Unmarshal(b, &parsedResponse); err != nil {
return Unknown, fmt.Errorf("error during json.Unmarshal: %w", err)
}
if len(parsedResponse) < 1 {
return NotFound, nil
}
badgeLevel := parsedResponse[0].BadgeLevel
if strings.Contains(badgeLevel, inProgressResp) {
return InProgress, nil
}
if strings.Contains(badgeLevel, passingResp) {
return Passing, nil
}
if strings.Contains(badgeLevel, silverResp) {
return Silver, nil
}
if strings.Contains(badgeLevel, goldResp) {
return Gold, nil
}
return Unknown, fmt.Errorf("%w: %s", errUnsupportedBadge, badgeLevel)
}

View File

@ -0,0 +1,66 @@
// Copyright 2021 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.
//
// Code generated by MockGen. DO NOT EDIT.
// Source: clients/cii_client.go
// Package mockrepo is a generated GoMock package.
package mockrepo
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
clients "github.com/ossf/scorecard/v3/clients"
)
// MockCIIBestPracticesClient is a mock of CIIBestPracticesClient interface.
type MockCIIBestPracticesClient struct {
ctrl *gomock.Controller
recorder *MockCIIBestPracticesClientMockRecorder
}
// MockCIIBestPracticesClientMockRecorder is the mock recorder for MockCIIBestPracticesClient.
type MockCIIBestPracticesClientMockRecorder struct {
mock *MockCIIBestPracticesClient
}
// NewMockCIIBestPracticesClient creates a new mock instance.
func NewMockCIIBestPracticesClient(ctrl *gomock.Controller) *MockCIIBestPracticesClient {
mock := &MockCIIBestPracticesClient{ctrl: ctrl}
mock.recorder = &MockCIIBestPracticesClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCIIBestPracticesClient) EXPECT() *MockCIIBestPracticesClientMockRecorder {
return m.recorder
}
// GetBadgeLevel mocks base method.
func (m *MockCIIBestPracticesClient) GetBadgeLevel(ctx context.Context, uri string) (clients.BadgeLevel, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBadgeLevel", ctx, uri)
ret0, _ := ret[0].(clients.BadgeLevel)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetBadgeLevel indicates an expected call of GetBadgeLevel.
func (mr *MockCIIBestPracticesClientMockRecorder) GetBadgeLevel(ctx, uri interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBadgeLevel", reflect.TypeOf((*MockCIIBestPracticesClient)(nil).GetBadgeLevel), ctx, uri)
}

View File

@ -121,7 +121,7 @@ func (mr *MockRepoMockRecorder) String() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockRepo)(nil).String))
}
// URL mocks base method.
// URI mocks base method.
func (m *MockRepo) URI() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "URI")

View File

@ -305,6 +305,8 @@ var rootCmd = &cobra.Command{
}
defer repoClient.Close()
ciiClient := clients.DefaultCIIBestPracticesClient()
// Read docs.
checkDocs, err := docs.Read()
if err != nil {
@ -326,7 +328,7 @@ var rootCmd = &cobra.Command{
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
}
}
repoResult, err := pkg.RunScorecards(ctx, repoURI, enabledChecks, repoClient)
repoResult, err := pkg.RunScorecards(ctx, repoURI, enabledChecks, repoClient, ciiClient)
if err != nil {
log.Fatal(err)
}

View File

@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"
"github.com/ossf/scorecard/v3/checks"
"github.com/ossf/scorecard/v3/clients"
"github.com/ossf/scorecard/v3/clients/githubrepo"
"github.com/ossf/scorecard/v3/pkg"
)
@ -64,7 +65,8 @@ var serveCmd = &cobra.Command{
}
ctx := r.Context()
repoClient := githubrepo.CreateGithubRepoClient(ctx, logger)
repoResult, err := pkg.RunScorecards(ctx, repo, checks.AllChecks, repoClient)
ciiClient := clients.DefaultCIIBestPracticesClient()
repoResult, err := pkg.RunScorecards(ctx, repo, checks.AllChecks, repoClient, ciiClient)
if err != nil {
sugar.Error(err)
rw.WriteHeader(http.StatusInternalServerError)

View File

@ -51,7 +51,7 @@ var ignoreRuntimeErrors = flag.Bool("ignoreRuntimeErrors", false, "if set to tru
func processRequest(ctx context.Context,
batchRequest *data.ScorecardBatchRequest, checksToRun checker.CheckNameToFnMap,
bucketURL, bucketURL2 string, checkDocs docs.Doc,
repoClient clients.RepoClient, logger *zap.Logger) error {
repoClient clients.RepoClient, ciiClient clients.CIIBestPracticesClient, logger *zap.Logger) error {
filename := data.GetBlobFilename(
fmt.Sprintf("shard-%07d", batchRequest.GetShardNum()),
batchRequest.GetJobTime().AsTime())
@ -82,7 +82,7 @@ func processRequest(ctx context.Context,
continue
}
repo.AppendMetadata(repo.Metadata()...)
result, err := pkg.RunScorecards(ctx, repo, checksToRun, repoClient)
result, err := pkg.RunScorecards(ctx, repo, checksToRun, repoClient, ciiClient)
if errors.Is(err, sce.ErrRepoUnreachable) {
// Not accessible repo - continue.
continue
@ -177,6 +177,7 @@ func main() {
panic(err)
}
repoClient := githubrepo.CreateGithubRepoClient(ctx, logger)
ciiClient := clients.DefaultCIIBestPracticesClient()
exporter, err := startMetricsExporter()
if err != nil {
@ -207,7 +208,7 @@ func main() {
}
if err := processRequest(ctx, req, checksToRun,
bucketURL, bucketURL2, checkDocs,
repoClient, logger); err != nil {
repoClient, ciiClient, logger); err != nil {
logger.Warn(fmt.Sprintf("error processing request: %v", err))
// Nack the message so that another worker can retry.
subscriber.Nack()

View File

@ -22,6 +22,7 @@ import (
"github.com/ossf/scorecard/v3/checker"
"github.com/ossf/scorecard/v3/checks"
"github.com/ossf/scorecard/v3/clients"
"github.com/ossf/scorecard/v3/clients/githubrepo"
scut "github.com/ossf/scorecard/v3/utests"
)
@ -32,10 +33,12 @@ var _ = Describe("E2E TEST:CIIBestPractices", func() {
dl := scut.TestDetailLogger{}
repo, err := githubrepo.MakeGithubRepo("tensorflow/tensorflow")
Expect(err).Should(BeNil())
ciiClient := clients.DefaultCIIBestPracticesClient()
req := checker.CheckRequest{
Ctx: context.Background(),
RepoClient: nil,
CIIClient: ciiClient,
Repo: repo,
Dlogger: &dl,
}

View File

@ -28,11 +28,13 @@ import (
)
func runEnabledChecks(ctx context.Context,
repo clients.Repo, checksToRun checker.CheckNameToFnMap, repoClient clients.RepoClient,
repo clients.Repo, checksToRun checker.CheckNameToFnMap,
repoClient clients.RepoClient, ciiClient clients.CIIBestPracticesClient,
resultsCh chan checker.CheckResult) {
request := checker.CheckRequest{
Ctx: ctx,
RepoClient: repoClient,
CIIClient: ciiClient,
Repo: repo,
}
wg := sync.WaitGroup{}
@ -71,7 +73,7 @@ func getRepoCommitHash(r clients.RepoClient) (string, error) {
func RunScorecards(ctx context.Context,
repo clients.Repo,
checksToRun checker.CheckNameToFnMap,
repoClient clients.RepoClient) (ScorecardResult, error) {
repoClient clients.RepoClient, ciiClient clients.CIIBestPracticesClient) (ScorecardResult, error) {
if err := repoClient.InitRepo(repo); err != nil {
// No need to call sce.WithMessage() since InitRepo will do that for us.
//nolint:wrapcheck
@ -96,7 +98,7 @@ func RunScorecards(ctx context.Context,
Date: time.Now(),
}
resultsCh := make(chan checker.CheckResult)
go runEnabledChecks(ctx, repo, checksToRun, repoClient, resultsCh)
go runEnabledChecks(ctx, repo, checksToRun, repoClient, ciiClient, resultsCh)
for result := range resultsCh {
ret.Checks = append(ret.Checks, result)
}