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

✨ cmd: Allow new scorecard to be instantiated with options #1703

Merged
merged 4 commits into from
Mar 3, 2022
Merged
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
42 changes: 20 additions & 22 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ import (
"github.com/ossf/scorecard/v4/policy"
)

var opts = options.New()

const (
scorecardLong = "A program that shows security scorecard for an open source software."
scorecardUse = `./scorecard [--repo=<repo_url>] [--local=folder] [--checks=check1,...]
Expand All @@ -46,13 +44,13 @@ const (
)

// New creates a new instance of the scorecard command.
func New() *cobra.Command {
func New(o *options.Options) *cobra.Command {
cmd := &cobra.Command{
Use: scorecardUse,
Short: scorecardShort,
Long: scorecardLong,
PreRunE: func(cmd *cobra.Command, args []string) error {
err := opts.Validate()
err := o.Validate()
if err != nil {
return fmt.Errorf("validating options: %w", err)
}
Expand All @@ -61,38 +59,38 @@ func New() *cobra.Command {
},
// TODO(cmd): Consider using RunE here
Run: func(cmd *cobra.Command, args []string) {
rootCmd(opts)
rootCmd(o)
},
}

opts.AddFlags(cmd)
o.AddFlags(cmd)

// Add sub-commands.
cmd.AddCommand(serveCmd())
cmd.AddCommand(serveCmd(o))
cmd.AddCommand(version.Version())
return cmd
}

// rootCmd runs scorecard checks given a set of arguments.
func rootCmd(opts *options.Options) {
func rootCmd(o *options.Options) {
// Set `repo` from package managers.
pkgResp, err := fetchGitRepositoryFromPackageManagers(opts.NPM, opts.PyPI, opts.RubyGems)
pkgResp, err := fetchGitRepositoryFromPackageManagers(o.NPM, o.PyPI, o.RubyGems)
if err != nil {
log.Panic(err)
}
if pkgResp.exists {
opts.Repo = pkgResp.associatedRepo
o.Repo = pkgResp.associatedRepo
}

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

ctx := context.Background()
logger := sclog.NewLogger(sclog.ParseLevel(opts.LogLevel))
logger := sclog.NewLogger(sclog.ParseLevel(o.LogLevel))
repoURI, repoClient, ossFuzzRepoClient, ciiClient, vulnsClient, err := checker.GetClients(
ctx, opts.Repo, opts.Local, logger)
ctx, o.Repo, o.Local, logger)
if err != nil {
log.Panic(err)
}
Expand All @@ -108,18 +106,18 @@ func rootCmd(opts *options.Options) {
}

var requiredRequestTypes []checker.RequestType
if opts.Local != "" {
if o.Local != "" {
requiredRequestTypes = append(requiredRequestTypes, checker.FileBased)
}
if !strings.EqualFold(opts.Commit, clients.HeadSHA) {
if !strings.EqualFold(o.Commit, clients.HeadSHA) {
requiredRequestTypes = append(requiredRequestTypes, checker.CommitBased)
}
enabledChecks, err := policy.GetEnabled(pol, opts.ChecksToRun, requiredRequestTypes)
enabledChecks, err := policy.GetEnabled(pol, o.ChecksToRun, requiredRequestTypes)
if err != nil {
log.Panic(err)
}

if opts.Format == options.FormatDefault {
if o.Format == options.FormatDefault {
for checkName := range enabledChecks {
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
}
Expand All @@ -128,8 +126,8 @@ func rootCmd(opts *options.Options) {
repoResult, err := pkg.RunScorecards(
ctx,
repoURI,
opts.Commit,
opts.Format == options.FormatRaw,
o.Commit,
o.Format == options.FormatRaw,
enabledChecks,
repoClient,
ossFuzzRepoClient,
Expand All @@ -139,22 +137,22 @@ func rootCmd(opts *options.Options) {
if err != nil {
log.Panic(err)
}
repoResult.Metadata = append(repoResult.Metadata, opts.Metadata...)
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 opts.Format == options.FormatDefault {
if o.Format == options.FormatDefault {
for checkName := range enabledChecks {
fmt.Fprintf(os.Stderr, "Finished [%s]\n", checkName)
}
fmt.Println("\nRESULTS\n-------")
}

resultsErr := pkg.FormatResults(
opts,
o,
&repoResult,
checkDocs,
pol,
Expand Down
7 changes: 4 additions & 3 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@ import (
"github.com/ossf/scorecard/v4/clients"
"github.com/ossf/scorecard/v4/clients/githubrepo"
"github.com/ossf/scorecard/v4/log"
"github.com/ossf/scorecard/v4/options"
"github.com/ossf/scorecard/v4/pkg"
)

// TODO(cmd): Determine if this should be exported.
func serveCmd() *cobra.Command {
func serveCmd(o *options.Options) *cobra.Command {
return &cobra.Command{
Use: "serve",
Short: "Serve the scorecard program over http",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
logger := log.NewLogger(log.ParseLevel(opts.LogLevel))
logger := log.NewLogger(log.ParseLevel(o.LogLevel))

t, err := template.New("webpage").Parse(tpl)
if err != nil {
Expand Down Expand Up @@ -76,7 +77,7 @@ func serveCmd() *cobra.Command {
}

if r.Header.Get("Content-Type") == "application/json" {
if err := repoResult.AsJSON(opts.ShowDetails, log.ParseLevel(opts.LogLevel), rw); err != nil {
if err := repoResult.AsJSON(o.ShowDetails, log.ParseLevel(o.LogLevel), rw); err != nil {
// TODO(log): Improve error message
logger.Error(err, "")
rw.WriteHeader(http.StatusInternalServerError)
Expand Down
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import (
"log"

"github.com/ossf/scorecard/v4/cmd"
"github.com/ossf/scorecard/v4/options"
)

func main() {
if err := cmd.New().Execute(); err != nil {
opts := options.New()
if err := cmd.New(opts).Execute(); err != nil {
log.Fatalf("error during command execution: %v", err)
}
}
111 changes: 76 additions & 35 deletions options/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,44 @@ import (
"github.com/ossf/scorecard/v4/checks"
)

const (
// FlagRepo is the flag name for specifying a repository.
FlagRepo = "repo"

// FlagLocal is the flag name for specifying a local run.
FlagLocal = "local"

// FlagCommit is the flag name for specifying a commit.
FlagCommit = "commit"

// FlagLogLevel is the flag name for specifying the log level.
FlagLogLevel = "verbosity"

// FlagNPM is the flag name for specifying a NPM repository.
FlagNPM = "npm"

// FlagPyPI is the flag name for specifying a PyPI repository.
FlagPyPI = "pypi"

// FlagRubyGems is the flag name for specifying a RubyGems repository.
FlagRubyGems = "rubygems"

// FlagMetadata is the flag name for specifying metadata for the project.
FlagMetadata = "metadata"

// FlagShowDetails is the flag name for outputting additional check info.
FlagShowDetails = "show-details"

// FlagChecks is the flag name for specifying which checks to run.
FlagChecks = "checks"

// FlagPolicyFile is the flag name for specifying a policy file.
FlagPolicyFile = "policy"

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

// Command is an interface for handling options for command-line utilities.
type Command interface {
// AddFlags adds this options' flags to the cobra command.
Expand All @@ -33,65 +71,65 @@ type Command interface {
func (o *Options) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(
&o.Repo,
"repo",
"",
FlagRepo,
o.Repo,
"repository to check",
)

cmd.Flags().StringVar(
&o.Local,
"local",
"",
FlagLocal,
o.Local,
"local folder to check",
)

// TODO(v5): Should this be behind a feature flag?
cmd.Flags().StringVar(
&o.Commit,
"commit",
DefaultCommit,
FlagCommit,
o.Commit,
"commit to analyze",
)

cmd.Flags().StringVar(
&o.LogLevel,
"verbosity",
DefaultLogLevel,
FlagLogLevel,
o.LogLevel,
"set the log level",
)

cmd.Flags().StringVar(
&o.NPM,
"npm",
"",
FlagNPM,
o.NPM,
"npm package to check, given that the npm package has a GitHub repository",
)

cmd.Flags().StringVar(
&o.PyPI,
"pypi",
"",
FlagPyPI,
o.PyPI,
"pypi package to check, given that the pypi package has a GitHub repository",
)

cmd.Flags().StringVar(
&o.RubyGems,
"rubygems",
"",
FlagRubyGems,
o.RubyGems,
"rubygems package to check, given that the rubygems package has a GitHub repository",
)

cmd.Flags().StringSliceVar(
&o.Metadata,
"metadata",
[]string{},
FlagMetadata,
o.Metadata,
"metadata for the project. It can be multiple separated by commas",
)

cmd.Flags().BoolVar(
&o.ShowDetails,
"show-details",
false,
FlagShowDetails,
o.ShowDetails,
"show extra details about each check",
)

Expand All @@ -101,32 +139,35 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
}
cmd.Flags().StringSliceVar(
&o.ChecksToRun,
"checks",
[]string{},
FlagChecks,
o.ChecksToRun,
fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ",")),
)

// TODO(options): Extract logic
allowedFormats := []string{
FormatDefault,
FormatJSON,
}

if o.isSarifEnabled() {
cmd.Flags().StringVar(
&o.PolicyFile,
"policy",
"",
FlagPolicyFile,
o.PolicyFile,
"policy to enforce",
)

cmd.Flags().StringVar(
&o.Format,
"format",
FormatDefault,
"output format allowed values are [default, sarif, json]",
)
} else {
cmd.Flags().StringVar(
&o.Format,
"format",
FormatDefault,
"output format allowed values are [default, json]",
)
allowedFormats = append(allowedFormats, FormatSarif)
}

cmd.Flags().StringVar(
&o.Format,
FlagFormat,
o.Format,
fmt.Sprintf(
"output format. Possible values are: %s",
strings.Join(allowedFormats, ", "),
),
)
}
12 changes: 12 additions & 0 deletions options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ func New() *Options {
fmt.Printf("could not parse env vars, using default options: %v", err)
}

// Defaulting.
// TODO(options): Consider moving this to a separate function/method.
if opts.Commit == "" {
opts.Commit = DefaultCommit
}
if opts.Format == "" {
opts.Format = FormatDefault
}
if opts.LogLevel == "" {
opts.LogLevel = DefaultLogLevel
}

return opts
}

Expand Down