Skip to content

Commit

Permalink
✨ cmd: Allow new scorecard to be instantiated with options (#1703)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
justaugustus authored Mar 3, 2022
1 parent d192c8e commit 3070b3c
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 61 deletions.
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

0 comments on commit 3070b3c

Please sign in to comment.