diff --git a/cmd/root.go b/cmd/root.go index 6c811981ad9..e9f4d3e53e4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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=] [--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, diff --git a/cmd/serve.go b/cmd/serve.go index f9c770f72e6..a57765fd79c 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -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) diff --git a/main.go b/main.go index 85612083d2c..838fe937595 100644 --- a/main.go +++ b/main.go @@ -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) } } diff --git a/options/flags.go b/options/flags.go index f0a204104b5..7b17ce06618 100644 --- a/options/flags.go +++ b/options/flags.go @@ -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, ", "), + ), + ) } diff --git a/options/options.go b/options/options.go index 488d251dda7..c8adf3eb4ef 100644 --- a/options/options.go +++ b/options/options.go @@ -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 }