Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Feature [experimental]: The Scorecard Dependencydiff CLI (Version 0 Part 1) #2077

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7235754
save
aidenwang9867 Jul 19, 2022
fd5934c
Merge branch 'ossf:main' into depdiff_cli
aidenwang9867 Jul 19, 2022
4c0a3b1
tempsave CLI 0718
aidenwang9867 Jul 19, 2022
aee106e
temp
aidenwang9867 Jul 19, 2022
41b42a9
save
aidenwang9867 Jul 19, 2022
9d3668a
save
aidenwang9867 Jul 20, 2022
403b17c
save
aidenwang9867 Jul 20, 2022
4fe1faa
save
aidenwang9867 Jul 20, 2022
fdd821c
save
aidenwang9867 Jul 21, 2022
46fb33a
Merge branch 'main' into depdiff_cli
aidenwang9867 Jul 21, 2022
be6c8d2
save
aidenwang9867 Jul 21, 2022
3e2400b
save
aidenwang9867 Jul 21, 2022
d1048f6
Merge branch 'main' into depdiff_cli
aidenwang9867 Jul 22, 2022
5dccf56
Merge branch 'main' into depdiff_cli
aidenwang9867 Jul 25, 2022
f981c39
save
aidenwang9867 Jul 25, 2022
1343876
save
aidenwang9867 Jul 25, 2022
3e25f93
Merge branch 'main' into depdiff_cli
aidenwang9867 Jul 25, 2022
a362177
Merge branch 'main' into depdiff_cli
aidenwang9867 Jul 25, 2022
32f1307
save
aidenwang9867 Jul 26, 2022
e399ef9
Merge branch 'main' into depdiff_cli
aidenwang9867 Jul 28, 2022
8b2e725
save
aidenwang9867 Jul 28, 2022
bd501bb
save
aidenwang9867 Jul 28, 2022
091221e
Merge branch 'main' into depdiff_cli
azeemshaikh38 Jul 28, 2022
ddf5b80
save
aidenwang9867 Jul 29, 2022
d7fe030
save
aidenwang9867 Jul 29, 2022
8d799e9
Merge branch 'depdiff_cli' of https://github.com/aidenwang9867/scorec…
aidenwang9867 Jul 29, 2022
38e7916
save
aidenwang9867 Jul 29, 2022
39d8aba
save
aidenwang9867 Jul 29, 2022
ebeeed4
save
aidenwang9867 Jul 29, 2022
5fc9ea0
Merge branch 'main' into depdiff_cli
aidenwang9867 Jul 29, 2022
e170001
save
aidenwang9867 Jul 30, 2022
f43e2f9
Merge branch 'depdiff_cli' of https://github.com/aidenwang9867/scorec…
aidenwang9867 Jul 30, 2022
d6ef3e5
save
aidenwang9867 Jul 31, 2022
c50d993
Merge branch 'main' into depdiff_cli
aidenwang9867 Aug 1, 2022
1902e0d
save
aidenwang9867 Aug 1, 2022
33269f6
Merge branch 'main' into depdiff_cli
aidenwang9867 Aug 1, 2022
a7d7b8e
save
aidenwang9867 Aug 1, 2022
14d770b
Merge branch 'depdiff_cli' of https://github.com/aidenwang9867/scorec…
aidenwang9867 Aug 1, 2022
bba5e83
save
aidenwang9867 Aug 1, 2022
6739263
save
aidenwang9867 Aug 3, 2022
72cc949
Merge branch 'main' into depdiff_cli
aidenwang9867 Aug 3, 2022
2a7e09e
Merge branch 'main' into depdiff_cli
aidenwang9867 Aug 3, 2022
c3f2503
Merge branch 'main' into depdiff_cli
aidenwang9867 Aug 10, 2022
c9d82c0
Merge branch 'main' into depdiff_cli
aidenwang9867 Aug 16, 2022
5c9fed1
Merge branch 'main' into depdiff_cli
aidenwang9867 Aug 27, 2022
cb142ce
save
aidenwang9867 Aug 30, 2022
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
73 changes: 73 additions & 0 deletions cmd/dependency-diff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 cmd implements Scorecard commandline.
package cmd

import (
"context"
"fmt"
"log"

"github.com/spf13/cobra"

"github.com/ossf/scorecard/v4/dependencydiff"
docs "github.com/ossf/scorecard/v4/docs/checks"
"github.com/ossf/scorecard/v4/options"
"github.com/ossf/scorecard/v4/pkg"
)

const (
dependencydiffUse = `dependency-diff --base=<base> --head=<head> --repo=<repo> [--checks=check1,...]`
dependencydiffShort = `Surface Scorecard checking results for dependency-diffs
between commits or branches of a code repository.`
)

func dependencydiffCmd(o *options.Options) *cobra.Command {
depdiffCmd := &cobra.Command{
Use: dependencydiffUse,
Short: dependencydiffShort,
Long: ``,
PreRunE: func(cmd *cobra.Command, args []string) error {
err := o.ValidateDepdiff()
if err != nil {
return fmt.Errorf("validating options: %w", err)
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
checkDocs, err := docs.Read()
if err != nil {
log.Panicf("cannot read yaml file: %v", err)
}
doDependencydiff(ctx, o, checkDocs)
},
}
o.AddDepdiffFlags(depdiffCmd)
return depdiffCmd
}

func doDependencydiff(ctx context.Context, o *options.Options, checkDocs docs.Doc) {
base, head := o.Base, o.Head
depdiffResults, err := dependencydiff.GetDependencyDiffResults(
ctx, o.Repo, base, head, o.ChecksToRun, nil)
if err != nil {
log.Panicf("error getting dependencydiff results: %v", err)
}
depdiffErr := pkg.FormatDependencydiffResults(o, depdiffResults, checkDocs)
if depdiffErr != nil {
log.Panicf("Failed to format dependencydiff results: %v", depdiffErr)
}
}
32 changes: 15 additions & 17 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,10 @@ func New(o *options.Options) *cobra.Command {
Short: scorecardShort,
Long: scorecardLong,
PreRunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate()
err := o.ValidateRoot()
if err != nil {
return fmt.Errorf("validating options: %w", err)
}

return nil
},
// TODO(cmd): Consider using RunE here
Expand All @@ -62,9 +61,10 @@ func New(o *options.Options) *cobra.Command {
},
}

o.AddFlags(cmd)
o.AddRootFlags(cmd)

// Add sub-commands.
cmd.AddCommand(dependencydiffCmd(o))
cmd.AddCommand(serveCmd(o))
cmd.AddCommand(version.Version())
return cmd
Expand All @@ -81,14 +81,24 @@ func rootCmd(o *options.Options) {
if pkgResp.exists {
o.Repo = pkgResp.associatedRepo
}

pol, err := policy.ParseFromFile(o.PolicyFile)
if err != nil {
log.Panicf("readPolicy: %v", err)
}

ctx := context.Background()
logger := sclog.NewLogger(sclog.ParseLevel(o.LogLevel))
// Read docs.
checkDocs, err := docs.Read()
if err != nil {
log.Panicf("cannot read yaml file: %v", err)
}
// Run the scorecard checks on the repo.
doScorecardChecks(ctx, o, logger, checkDocs, pol)
}

func doScorecardChecks(ctx context.Context, o *options.Options,
logger *sclog.Logger, checkDocs docs.Doc, pol *policy.ScorecardPolicy,
) {
repoURI, repoClient, ossFuzzRepoClient, ciiClient, vulnsClient, err := checker.GetClients(
ctx, o.Repo, o.Local, logger)
if err != nil {
Expand All @@ -98,13 +108,6 @@ func rootCmd(o *options.Options) {
if ossFuzzRepoClient != nil {
defer ossFuzzRepoClient.Close()
}

// Read docs.
checkDocs, err := docs.Read()
if err != nil {
log.Panicf("cannot read yaml file: %v", err)
}

var requiredRequestTypes []checker.RequestType
if o.Local != "" {
requiredRequestTypes = append(requiredRequestTypes, checker.FileBased)
Expand All @@ -116,13 +119,11 @@ func rootCmd(o *options.Options) {
if err != nil {
log.Panic(err)
}

if o.Format == options.FormatDefault {
for checkName := range enabledChecks {
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
}
}

repoResult, err := pkg.RunScorecards(
aidenwang9867 marked this conversation as resolved.
Show resolved Hide resolved
ctx,
repoURI,
Expand All @@ -137,19 +138,16 @@ func rootCmd(o *options.Options) {
log.Panic(err)
}
repoResult.Metadata = append(repoResult.Metadata, o.Metadata...)

// Sort them by name
sort.Slice(repoResult.Checks, func(i, j int) bool {
return repoResult.Checks[i].Name < repoResult.Checks[j].Name
})

if o.Format == options.FormatDefault {
for checkName := range enabledChecks {
fmt.Fprintf(os.Stderr, "Finished [%s]\n", checkName)
}
fmt.Println("\nRESULTS\n-------")
}

resultsErr := pkg.FormatResults(
o,
&repoResult,
Expand Down
2 changes: 1 addition & 1 deletion dependencydiff/dependencydiff.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func GetDependencyDiffResults(
if err != nil {
return nil, fmt.Errorf("error in fetchRawDependencyDiffData: %w", err)
}
// Map the ecosystem naming convention from GitHub to OSV.
// Map the ecosystem naming convention from GitHub to OSV.
err = mapDependencyEcosystemNaming(dCtx.dependencydiffs)
if err != nil {
return nil, fmt.Errorf("error in mapDependencyEcosystemNaming: %w", err)
Expand Down
1 change: 1 addition & 0 deletions e2e/dependencydiff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
head = "1989568f93e484f6a86f8b276b170e3d6962ce12"
)

// TODO (#2087): More e2e tests and a potnetial refactoring needed for the func getScorecardCheckResults.
var _ = Describe("E2E TEST:"+dependencydiff.Depdiff, func() {
Context("E2E TEST:Validating use of the dependency-diff API", func() {
It("Should return a slice of dependency-diff checking results", func() {
Expand Down
32 changes: 28 additions & 4 deletions options/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ const (

// FlagFormat is the flag name for specifying output format.
FlagFormat = "format"

// FlagBase is the flag name for specifying a dependency-diff base.
FlagBase = "base"
aidenwang9867 marked this conversation as resolved.
Show resolved Hide resolved

// FlagHead is the flag name for specifying a dependency-diff head.
FlagHead = "head"
)

// Command is an interface for handling options for command-line utilities.
Expand All @@ -67,9 +73,9 @@ type Command interface {
AddFlags(cmd *cobra.Command)
}

// AddFlags adds this options' flags to the cobra command.
func (o *Options) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(
// AddRootFlags adds this options' flags to the cobra command.
func (o *Options) AddRootFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(
&o.Repo,
FlagRepo,
o.Repo,
Expand Down Expand Up @@ -137,7 +143,7 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
for checkName := range checks.GetAll() {
checkNames = append(checkNames, checkName)
}
cmd.Flags().StringSliceVar(
cmd.PersistentFlags().StringSliceVar(
&o.ChecksToRun,
FlagChecks,
o.ChecksToRun,
Expand Down Expand Up @@ -171,3 +177,21 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
),
)
}

// AddDepdiffFlags adds flags to the dependency-diff cobra command.
func (o *Options) AddDepdiffFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(
&o.Base,
FlagBase,
o.Base,
`the base branch name or the base commitSHA to check. valid input examples:
"main" (branch name), "SHA_VALUE" (commitSHA)`,
)
cmd.Flags().StringVar(
&o.Head,
FlagHead,
o.Head,
`the head branch name or the head commitSHA to check. valid input examples:
"dev" (branch name), "SHA_VALUE" (commitSHA)`,
)
}
39 changes: 32 additions & 7 deletions options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,17 @@ type Options struct {
PolicyFile string
// TODO(action): Add logic for writing results to file
ResultsFile string
Base string
Head string
aidenwang9867 marked this conversation as resolved.
Show resolved Hide resolved
ChecksToRun []string
Metadata []string
ShowDetails bool

// Feature flags.
EnableSarif bool `env:"ENABLE_SARIF"`
EnableScorecardV5 bool `env:"SCORECARD_V5"`
EnableScorecardV6 bool `env:"SCORECARD_V6"`
EnableSarif bool `env:"ENABLE_SARIF"`
EnableScorecardV5 bool `env:"SCORECARD_V5"`
EnableScorecardV6 bool `env:"SCORECARD_V6"`
EnableScorecardExperimental bool `env:"SCORECARD_EXPERIMENTAL"`
}

// New creates a new instance of `Options`.
Expand Down Expand Up @@ -97,6 +100,9 @@ const (
// EnvVarScorecardV6 is the environment variable which enables scorecard v6
// options.
EnvVarScorecardV6 = "SCORECARD_V6"
// EnvVarScorecardExperimental is the environment variable which enables
// scorecard experimental features.
EnvVarScorecardExperimental = "SCORECARD_EXPERIMENTAL"
)

var (
Expand All @@ -111,13 +117,14 @@ var (
errRepoOptionMustBeSet = errors.New(
"exactly one of `repo`, `npm`, `pypi`, `rubygems` or `local` must be set",
)
errSARIFNotSupported = errors.New("SARIF format is not supported yet")
errValidate = errors.New("some options could not be validated")
errSARIFNotSupported = errors.New("SARIF format is not supported yet")
errValidate = errors.New("some options could not be validated")
errExperimentalDisabled = errors.New("scorecard experimental features are disabled")
)

// Validate validates scorecard configuration options.
// ValidateRoot validates scorecard configuration options.
// TODO(options): Cleanup error messages.
func (o *Options) Validate() error {
func (o *Options) ValidateRoot() error {
var errs []error

// Validate exactly one of `--repo`, `--npm`, `--pypi`, `--rubygems`, `--local` is enabled.
Expand Down Expand Up @@ -238,3 +245,21 @@ func validateFormat(format string) bool {
return false
}
}

// isExperimentalEnabled returns true if SCORECARD_EXPERIMENTAL was specified in options or via
// environment variable.
func (o *Options) isExperimentalEnabled() bool {
_, enabled := os.LookupEnv(EnvVarScorecardExperimental)
return o.EnableScorecardExperimental || enabled
}

// ValidateDepdiff validates dependencydiff configuration options.
func (o *Options) ValidateDepdiff() error {
if !o.isExperimentalEnabled() {
return fmt.Errorf(
"cannot use this feature: %w",
errExperimentalDisabled,
)
}
return nil
}
2 changes: 1 addition & 1 deletion options/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func TestOptions_Validate(t *testing.T) {
defer os.Unsetenv(EnvVarEnableSarif)
}

if err := o.Validate(); (err != nil) != tt.wantErr {
if err := o.ValidateRoot(); (err != nil) != tt.wantErr {
t.Errorf("Options.Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
Expand Down
20 changes: 13 additions & 7 deletions pkg/dependencydiff_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
package pkg

import (
"encoding/json"
"fmt"
"io"
"os"

sce "github.com/ossf/scorecard/v4/errors"
"github.com/ossf/scorecard/v4/docs/checks"
"github.com/ossf/scorecard/v4/log"
"github.com/ossf/scorecard/v4/options"
)

// ChangeType is the change type (added, updated, removed) of a dependency.
Expand Down Expand Up @@ -81,10 +82,15 @@ type DependencyCheckResult struct {
Name string
}

// AsJSON for DependencyCheckResult exports the DependencyCheckResult as a JSON object.
func (dr *DependencyCheckResult) AsJSON(writer io.Writer) error {
if err := json.NewEncoder(writer).Encode(*dr); err != nil {
return sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("encoder.Encode: %v", err))
// FormatDependencydiffResults formats dependencydiff results.
func FormatDependencydiffResults(
opts *options.Options,
depdiffResults []DependencyCheckResult,
doc checks.Doc,
) error {
err := DependencydiffResultsAsJSON(depdiffResults, log.ParseLevel(opts.LogLevel), doc, os.Stdout)
if err != nil {
return fmt.Errorf("failed to output dependencydiff results: %w", err)
}
return nil
}
Loading