mirror of
https://github.com/ossf/scorecard.git
synced 2024-08-15 03:10:33 +03:00
✨ 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:
parent
d192c8e3ac
commit
3070b3ca1b
42
cmd/root.go
42
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=<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,
|
||||
|
@ -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)
|
||||
|
4
main.go
4
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)
|
||||
}
|
||||
}
|
||||
|
111
options/flags.go
111
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, ", "),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user