-
Notifications
You must be signed in to change notification settings - Fork 511
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Feature DependencyDiff (Version 0 Part 2) (#2046)
* temp * Update dependencies.go * Update errors.go * Update scorecard_results.go * Update vulnerabilities.go * save * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp * temp0713-1 * temp0713-2 * temp0713-3 * temp0713-4 * temp0713-4 * temp0713-5 * temp0713-6 * temp0713-7 * temp0713-8 * temp0713-9 * temp0713-10 * temp0713-11 * temp0713-12 * 1 * temp * temp * temp * temp * temp * temp * temp * temp * save * save * save * final_commit_before_merge
- Loading branch information
1 parent
dd8fbc0
commit 10681da
Showing
5 changed files
with
525 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// Copyright 2022 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 dependencydiff | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"path" | ||
|
||
"github.com/ossf/scorecard/v4/checker" | ||
"github.com/ossf/scorecard/v4/checks" | ||
"github.com/ossf/scorecard/v4/clients" | ||
"github.com/ossf/scorecard/v4/clients/githubrepo" | ||
sce "github.com/ossf/scorecard/v4/errors" | ||
"github.com/ossf/scorecard/v4/log" | ||
"github.com/ossf/scorecard/v4/pkg" | ||
"github.com/ossf/scorecard/v4/policy" | ||
) | ||
|
||
// Depdiff is the exported name for dependency-diff. | ||
const Depdiff = "Dependency-diff" | ||
|
||
type dependencydiffContext struct { | ||
logger *log.Logger | ||
ownerName, repoName, baseSHA, headSHA string | ||
ctx context.Context | ||
ghRepo clients.Repo | ||
ghRepoClient clients.RepoClient | ||
ossFuzzClient clients.RepoClient | ||
vulnsClient clients.VulnerabilitiesClient | ||
ciiClient clients.CIIBestPracticesClient | ||
changeTypesToCheck map[pkg.ChangeType]bool | ||
checkNamesToRun []string | ||
dependencydiffs []dependency | ||
results []pkg.DependencyCheckResult | ||
} | ||
|
||
// GetDependencyDiffResults gets dependency changes between two given code commits BASE and HEAD | ||
// along with the Scorecard check results of the dependencies, and returns a slice of DependencyCheckResult. | ||
// TO use this API, an access token must be set following https://github.com/ossf/scorecard#authentication. | ||
func GetDependencyDiffResults( | ||
ctx context.Context, ownerName, repoName, baseSHA, headSHA string, scorecardChecksNames []string, | ||
changeTypesToCheck map[pkg.ChangeType]bool) ([]pkg.DependencyCheckResult, error) { | ||
// Fetch the raw dependency diffs. | ||
dCtx := dependencydiffContext{ | ||
logger: log.NewLogger(log.InfoLevel), | ||
ownerName: ownerName, | ||
repoName: repoName, | ||
baseSHA: baseSHA, | ||
headSHA: headSHA, | ||
ctx: ctx, | ||
changeTypesToCheck: changeTypesToCheck, | ||
checkNamesToRun: scorecardChecksNames, | ||
} | ||
err := fetchRawDependencyDiffData(&dCtx) | ||
if err != nil { | ||
return nil, fmt.Errorf("error in fetchRawDependencyDiffData: %w", err) | ||
} | ||
|
||
// Initialize the repo and client(s) corresponding to the checks to run. | ||
err = initRepoAndClientByChecks(&dCtx) | ||
if err != nil { | ||
return nil, fmt.Errorf("error in initRepoAndClientByChecks: %w", err) | ||
} | ||
err = getScorecardCheckResults(&dCtx) | ||
if err != nil { | ||
return nil, fmt.Errorf("error getting scorecard check results: %w", err) | ||
} | ||
return dCtx.results, nil | ||
} | ||
|
||
func initRepoAndClientByChecks(dCtx *dependencydiffContext) error { | ||
repo, repoClient, ossFuzzClient, ciiClient, vulnsClient, err := checker.GetClients( | ||
dCtx.ctx, path.Join(dCtx.ownerName, dCtx.repoName), "", dCtx.logger, | ||
) | ||
if err != nil { | ||
return fmt.Errorf("error creating the github repo: %w", err) | ||
} | ||
// If the caller doesn't specify the checks to run, run all checks and return all the clients. | ||
if dCtx.checkNamesToRun == nil || len(dCtx.checkNamesToRun) == 0 { | ||
dCtx.ghRepo, dCtx.ghRepoClient, dCtx.ossFuzzClient, dCtx.ciiClient, dCtx.vulnsClient = | ||
repo, repoClient, ossFuzzClient, ciiClient, vulnsClient | ||
return nil | ||
} | ||
dCtx.ghRepo = repo | ||
dCtx.ghRepoClient = githubrepo.CreateGithubRepoClient(dCtx.ctx, dCtx.logger) | ||
for _, cn := range dCtx.checkNamesToRun { | ||
switch cn { | ||
case checks.CheckFuzzing: | ||
dCtx.ossFuzzClient = ossFuzzClient | ||
case checks.CheckCIIBestPractices: | ||
dCtx.ciiClient = ciiClient | ||
case checks.CheckVulnerabilities: | ||
dCtx.vulnsClient = vulnsClient | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func getScorecardCheckResults(dCtx *dependencydiffContext) error { | ||
// Initialize the checks to run from the caller's input. | ||
checksToRun, err := policy.GetEnabled(nil, dCtx.checkNamesToRun, nil) | ||
if err != nil { | ||
return fmt.Errorf("error init scorecard checks: %w", err) | ||
} | ||
for _, d := range dCtx.dependencydiffs { | ||
depCheckResult := pkg.DependencyCheckResult{ | ||
PackageURL: d.PackageURL, | ||
SourceRepository: d.SourceRepository, | ||
ChangeType: d.ChangeType, | ||
ManifestPath: d.ManifestPath, | ||
Ecosystem: d.Ecosystem, | ||
Version: d.Version, | ||
Name: d.Name, | ||
} | ||
// For now we skip those without source repo urls. | ||
// TODO (#2063): use the BigQuery dataset to supplement null source repo URLs to fetch the Scorecard results for them. | ||
if d.SourceRepository != nil && *d.SourceRepository != "" { | ||
if d.ChangeType != nil && (dCtx.changeTypesToCheck[*d.ChangeType] || dCtx.changeTypesToCheck == nil) { | ||
// Run scorecard on those types of dependencies that the caller would like to check. | ||
// If the input map changeTypesToCheck is empty, by default, we run checks for all valid types. | ||
// TODO (#2064): use the Scorecare REST API to retrieve the Scorecard result statelessly. | ||
scorecardResult, err := pkg.RunScorecards( | ||
dCtx.ctx, | ||
dCtx.ghRepo, | ||
// TODO (#2065): In future versions, ideally, this should be | ||
// the commitSHA corresponding to d.Version instead of HEAD. | ||
clients.HeadSHA, | ||
checksToRun, | ||
dCtx.ghRepoClient, | ||
dCtx.ossFuzzClient, | ||
dCtx.ciiClient, | ||
dCtx.vulnsClient, | ||
) | ||
// If the run fails, we leave the current dependency scorecard result empty and record the error | ||
// rather than letting the entire API return nil since we still expect results for other dependencies. | ||
if err != nil { | ||
depCheckResult.ScorecardResultsWithError.Error = sce.WithMessage(sce.ErrScorecardInternal, | ||
fmt.Sprintf("error running the scorecard checks: %v", err)) | ||
} else { // Otherwise, we record the scorecard check results for this dependency. | ||
depCheckResult.ScorecardResultsWithError.ScorecardResults = &scorecardResult | ||
} | ||
} | ||
} | ||
dCtx.results = append(dCtx.results, depCheckResult) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
// Copyright 2022 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 dependencydiff | ||
|
||
import ( | ||
"context" | ||
"path" | ||
"testing" | ||
|
||
"github.com/ossf/scorecard/v4/clients" | ||
"github.com/ossf/scorecard/v4/log" | ||
) | ||
|
||
// Test_fetchRawDependencyDiffData is a test function for fetchRawDependencyDiffData. | ||
func Test_fetchRawDependencyDiffData(t *testing.T) { | ||
t.Parallel() | ||
//nolint | ||
tests := []struct { | ||
name string | ||
dCtx dependencydiffContext | ||
wantEmpty bool | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "error response", | ||
dCtx: dependencydiffContext{ | ||
logger: log.NewLogger(log.InfoLevel), | ||
ctx: context.Background(), | ||
ownerName: "no_such_owner", | ||
repoName: "repo_not_exist", | ||
baseSHA: "base", | ||
headSHA: clients.HeadSHA, | ||
}, | ||
wantEmpty: true, | ||
wantErr: true, | ||
}, | ||
// Considering of the token usage, normal responses are tested in the e2e test. | ||
} | ||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.name, func(t *testing.T) { | ||
t.Parallel() | ||
err := fetchRawDependencyDiffData(&tt.dCtx) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("fetchRawDependencyDiffData() error = {%v}, want error: %v", err, tt.wantErr) | ||
return | ||
} | ||
lenResults := len(tt.dCtx.dependencydiffs) | ||
if (lenResults == 0) != tt.wantEmpty { | ||
t.Errorf("want empty results: %v, got len of results:%d", tt.wantEmpty, lenResults) | ||
return | ||
} | ||
|
||
}) | ||
} | ||
} | ||
|
||
func Test_initRepoAndClientByChecks(t *testing.T) { | ||
t.Parallel() | ||
//nolint | ||
tests := []struct { | ||
name string | ||
dCtx dependencydiffContext | ||
wantGhRepo, wantRepoClient, wantFuzzClient bool | ||
wantVulnClient, wantCIIClient bool | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "error creating repo", | ||
dCtx: dependencydiffContext{ | ||
logger: log.NewLogger(log.InfoLevel), | ||
ctx: context.Background(), | ||
ownerName: path.Join("host_not_exist.com", "owner_not_exist"), | ||
repoName: "repo_not_exist", | ||
checkNamesToRun: []string{}, | ||
}, | ||
wantGhRepo: false, | ||
wantRepoClient: false, | ||
wantFuzzClient: false, | ||
wantVulnClient: false, | ||
wantCIIClient: false, | ||
wantErr: true, | ||
}, | ||
// Same as the above, putting the normal responses to the e2e test. | ||
} | ||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.name, func(t *testing.T) { | ||
t.Parallel() | ||
err := initRepoAndClientByChecks(&tt.dCtx) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("initRepoAndClientByChecks() error = {%v}, want error: %v", err, tt.wantErr) | ||
return | ||
} | ||
if (tt.dCtx.ghRepo != nil) != tt.wantGhRepo { | ||
t.Errorf("init repo error, wantGhRepo: %v, got %v", tt.wantGhRepo, tt.dCtx.ghRepo) | ||
return | ||
} | ||
if (tt.dCtx.ghRepoClient != nil) != tt.wantRepoClient { | ||
t.Errorf("init repo error, wantRepoClient: %v, got %v", tt.wantRepoClient, tt.dCtx.ghRepoClient) | ||
return | ||
} | ||
if (tt.dCtx.ossFuzzClient != nil) != tt.wantFuzzClient { | ||
t.Errorf("init repo error, wantFuzzClient: %v, got %v", tt.wantFuzzClient, tt.dCtx.ossFuzzClient) | ||
return | ||
} | ||
if (tt.dCtx.vulnsClient != nil) != tt.wantVulnClient { | ||
t.Errorf("init repo error, wantVulnClient: %v, got %v", tt.wantVulnClient, tt.dCtx.vulnsClient) | ||
return | ||
} | ||
if (tt.dCtx.ciiClient != nil) != tt.wantCIIClient { | ||
t.Errorf("init repo error, wantCIIClient: %v, got %v", tt.wantCIIClient, tt.dCtx.ciiClient) | ||
return | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Test_getScorecardCheckResults(t *testing.T) { | ||
t.Parallel() | ||
//nolint | ||
tests := []struct { | ||
name string | ||
dCtx dependencydiffContext | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "empty response", | ||
dCtx: dependencydiffContext{ | ||
ctx: context.Background(), | ||
logger: log.NewLogger(log.InfoLevel), | ||
ownerName: "owner_not_exist", | ||
repoName: "repo_not_exist", | ||
}, | ||
wantErr: false, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.name, func(t *testing.T) { | ||
t.Parallel() | ||
err := initRepoAndClientByChecks(&tt.dCtx) | ||
if err != nil { | ||
t.Errorf("init repo and client error") | ||
return | ||
} | ||
err = getScorecardCheckResults(&tt.dCtx) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("getScorecardCheckResults() error = {%v}, want error: %v", err, tt.wantErr) | ||
return | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.