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

* cmd: Allow new scorecard commands to be instantiated with options
* options: Default flags to struct field values
* options: Use constants for flag names
* options: Simplify SARIF check

Signed-off-by: Stephen Augustus <foo@auggie.dev>
This commit is contained in:
Stephen Augustus (he/him) 2022-03-02 20:38:34 -05:00 committed by GitHub
parent d192c8e3ac
commit 3070b3ca1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 61 deletions

View File

@ -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,...]
@ -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)
}
@ -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)
}
@ -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)
}
@ -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,
@ -139,14 +137,14 @@ 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)
}
@ -154,7 +152,7 @@ func rootCmd(opts *options.Options) {
}
resultsErr := pkg.FormatResults(
opts,
o,
&repoResult,
checkDocs,
pol,

View File

@ -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 {
@ -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)

View File

@ -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)
}
}

View File

@ -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.
@ -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",
)
@ -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, ", "),
),
)
}

View File

@ -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
}