mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-11 00:45:36 +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"
|
"github.com/ossf/scorecard/v4/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
var opts = options.New()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
scorecardLong = "A program that shows security scorecard for an open source software."
|
scorecardLong = "A program that shows security scorecard for an open source software."
|
||||||
scorecardUse = `./scorecard [--repo=<repo_url>] [--local=folder] [--checks=check1,...]
|
scorecardUse = `./scorecard [--repo=<repo_url>] [--local=folder] [--checks=check1,...]
|
||||||
@ -46,13 +44,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// New creates a new instance of the scorecard command.
|
// New creates a new instance of the scorecard command.
|
||||||
func New() *cobra.Command {
|
func New(o *options.Options) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: scorecardUse,
|
Use: scorecardUse,
|
||||||
Short: scorecardShort,
|
Short: scorecardShort,
|
||||||
Long: scorecardLong,
|
Long: scorecardLong,
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := opts.Validate()
|
err := o.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validating options: %w", err)
|
return fmt.Errorf("validating options: %w", err)
|
||||||
}
|
}
|
||||||
@ -61,38 +59,38 @@ func New() *cobra.Command {
|
|||||||
},
|
},
|
||||||
// TODO(cmd): Consider using RunE here
|
// TODO(cmd): Consider using RunE here
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
rootCmd(opts)
|
rootCmd(o)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.AddFlags(cmd)
|
o.AddFlags(cmd)
|
||||||
|
|
||||||
// Add sub-commands.
|
// Add sub-commands.
|
||||||
cmd.AddCommand(serveCmd())
|
cmd.AddCommand(serveCmd(o))
|
||||||
cmd.AddCommand(version.Version())
|
cmd.AddCommand(version.Version())
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// rootCmd runs scorecard checks given a set of arguments.
|
// rootCmd runs scorecard checks given a set of arguments.
|
||||||
func rootCmd(opts *options.Options) {
|
func rootCmd(o *options.Options) {
|
||||||
// Set `repo` from package managers.
|
// 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 {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
if pkgResp.exists {
|
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 {
|
if err != nil {
|
||||||
log.Panicf("readPolicy: %v", err)
|
log.Panicf("readPolicy: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
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(
|
repoURI, repoClient, ossFuzzRepoClient, ciiClient, vulnsClient, err := checker.GetClients(
|
||||||
ctx, opts.Repo, opts.Local, logger)
|
ctx, o.Repo, o.Local, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
@ -108,18 +106,18 @@ func rootCmd(opts *options.Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var requiredRequestTypes []checker.RequestType
|
var requiredRequestTypes []checker.RequestType
|
||||||
if opts.Local != "" {
|
if o.Local != "" {
|
||||||
requiredRequestTypes = append(requiredRequestTypes, checker.FileBased)
|
requiredRequestTypes = append(requiredRequestTypes, checker.FileBased)
|
||||||
}
|
}
|
||||||
if !strings.EqualFold(opts.Commit, clients.HeadSHA) {
|
if !strings.EqualFold(o.Commit, clients.HeadSHA) {
|
||||||
requiredRequestTypes = append(requiredRequestTypes, checker.CommitBased)
|
requiredRequestTypes = append(requiredRequestTypes, checker.CommitBased)
|
||||||
}
|
}
|
||||||
enabledChecks, err := policy.GetEnabled(pol, opts.ChecksToRun, requiredRequestTypes)
|
enabledChecks, err := policy.GetEnabled(pol, o.ChecksToRun, requiredRequestTypes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Format == options.FormatDefault {
|
if o.Format == options.FormatDefault {
|
||||||
for checkName := range enabledChecks {
|
for checkName := range enabledChecks {
|
||||||
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
|
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
|
||||||
}
|
}
|
||||||
@ -128,8 +126,8 @@ func rootCmd(opts *options.Options) {
|
|||||||
repoResult, err := pkg.RunScorecards(
|
repoResult, err := pkg.RunScorecards(
|
||||||
ctx,
|
ctx,
|
||||||
repoURI,
|
repoURI,
|
||||||
opts.Commit,
|
o.Commit,
|
||||||
opts.Format == options.FormatRaw,
|
o.Format == options.FormatRaw,
|
||||||
enabledChecks,
|
enabledChecks,
|
||||||
repoClient,
|
repoClient,
|
||||||
ossFuzzRepoClient,
|
ossFuzzRepoClient,
|
||||||
@ -139,14 +137,14 @@ func rootCmd(opts *options.Options) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
repoResult.Metadata = append(repoResult.Metadata, opts.Metadata...)
|
repoResult.Metadata = append(repoResult.Metadata, o.Metadata...)
|
||||||
|
|
||||||
// Sort them by name
|
// Sort them by name
|
||||||
sort.Slice(repoResult.Checks, func(i, j int) bool {
|
sort.Slice(repoResult.Checks, func(i, j int) bool {
|
||||||
return repoResult.Checks[i].Name < repoResult.Checks[j].Name
|
return repoResult.Checks[i].Name < repoResult.Checks[j].Name
|
||||||
})
|
})
|
||||||
|
|
||||||
if opts.Format == options.FormatDefault {
|
if o.Format == options.FormatDefault {
|
||||||
for checkName := range enabledChecks {
|
for checkName := range enabledChecks {
|
||||||
fmt.Fprintf(os.Stderr, "Finished [%s]\n", checkName)
|
fmt.Fprintf(os.Stderr, "Finished [%s]\n", checkName)
|
||||||
}
|
}
|
||||||
@ -154,7 +152,7 @@ func rootCmd(opts *options.Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resultsErr := pkg.FormatResults(
|
resultsErr := pkg.FormatResults(
|
||||||
opts,
|
o,
|
||||||
&repoResult,
|
&repoResult,
|
||||||
checkDocs,
|
checkDocs,
|
||||||
pol,
|
pol,
|
||||||
|
@ -27,17 +27,18 @@ import (
|
|||||||
"github.com/ossf/scorecard/v4/clients"
|
"github.com/ossf/scorecard/v4/clients"
|
||||||
"github.com/ossf/scorecard/v4/clients/githubrepo"
|
"github.com/ossf/scorecard/v4/clients/githubrepo"
|
||||||
"github.com/ossf/scorecard/v4/log"
|
"github.com/ossf/scorecard/v4/log"
|
||||||
|
"github.com/ossf/scorecard/v4/options"
|
||||||
"github.com/ossf/scorecard/v4/pkg"
|
"github.com/ossf/scorecard/v4/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(cmd): Determine if this should be exported.
|
// TODO(cmd): Determine if this should be exported.
|
||||||
func serveCmd() *cobra.Command {
|
func serveCmd(o *options.Options) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "serve",
|
Use: "serve",
|
||||||
Short: "Serve the scorecard program over http",
|
Short: "Serve the scorecard program over http",
|
||||||
Long: ``,
|
Long: ``,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
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)
|
t, err := template.New("webpage").Parse(tpl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,7 +77,7 @@ func serveCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.Header.Get("Content-Type") == "application/json" {
|
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
|
// TODO(log): Improve error message
|
||||||
logger.Error(err, "")
|
logger.Error(err, "")
|
||||||
rw.WriteHeader(http.StatusInternalServerError)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
4
main.go
4
main.go
@ -19,10 +19,12 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/ossf/scorecard/v4/cmd"
|
"github.com/ossf/scorecard/v4/cmd"
|
||||||
|
"github.com/ossf/scorecard/v4/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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)
|
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"
|
"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.
|
// Command is an interface for handling options for command-line utilities.
|
||||||
type Command interface {
|
type Command interface {
|
||||||
// AddFlags adds this options' flags to the cobra command.
|
// AddFlags adds this options' flags to the cobra command.
|
||||||
@ -33,65 +71,65 @@ type Command interface {
|
|||||||
func (o *Options) AddFlags(cmd *cobra.Command) {
|
func (o *Options) AddFlags(cmd *cobra.Command) {
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&o.Repo,
|
&o.Repo,
|
||||||
"repo",
|
FlagRepo,
|
||||||
"",
|
o.Repo,
|
||||||
"repository to check",
|
"repository to check",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&o.Local,
|
&o.Local,
|
||||||
"local",
|
FlagLocal,
|
||||||
"",
|
o.Local,
|
||||||
"local folder to check",
|
"local folder to check",
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(v5): Should this be behind a feature flag?
|
// TODO(v5): Should this be behind a feature flag?
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&o.Commit,
|
&o.Commit,
|
||||||
"commit",
|
FlagCommit,
|
||||||
DefaultCommit,
|
o.Commit,
|
||||||
"commit to analyze",
|
"commit to analyze",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&o.LogLevel,
|
&o.LogLevel,
|
||||||
"verbosity",
|
FlagLogLevel,
|
||||||
DefaultLogLevel,
|
o.LogLevel,
|
||||||
"set the log level",
|
"set the log level",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&o.NPM,
|
&o.NPM,
|
||||||
"npm",
|
FlagNPM,
|
||||||
"",
|
o.NPM,
|
||||||
"npm package to check, given that the npm package has a GitHub repository",
|
"npm package to check, given that the npm package has a GitHub repository",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&o.PyPI,
|
&o.PyPI,
|
||||||
"pypi",
|
FlagPyPI,
|
||||||
"",
|
o.PyPI,
|
||||||
"pypi package to check, given that the pypi package has a GitHub repository",
|
"pypi package to check, given that the pypi package has a GitHub repository",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&o.RubyGems,
|
&o.RubyGems,
|
||||||
"rubygems",
|
FlagRubyGems,
|
||||||
"",
|
o.RubyGems,
|
||||||
"rubygems package to check, given that the rubygems package has a GitHub repository",
|
"rubygems package to check, given that the rubygems package has a GitHub repository",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Flags().StringSliceVar(
|
cmd.Flags().StringSliceVar(
|
||||||
&o.Metadata,
|
&o.Metadata,
|
||||||
"metadata",
|
FlagMetadata,
|
||||||
[]string{},
|
o.Metadata,
|
||||||
"metadata for the project. It can be multiple separated by commas",
|
"metadata for the project. It can be multiple separated by commas",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Flags().BoolVar(
|
cmd.Flags().BoolVar(
|
||||||
&o.ShowDetails,
|
&o.ShowDetails,
|
||||||
"show-details",
|
FlagShowDetails,
|
||||||
false,
|
o.ShowDetails,
|
||||||
"show extra details about each check",
|
"show extra details about each check",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -101,32 +139,35 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
|
|||||||
}
|
}
|
||||||
cmd.Flags().StringSliceVar(
|
cmd.Flags().StringSliceVar(
|
||||||
&o.ChecksToRun,
|
&o.ChecksToRun,
|
||||||
"checks",
|
FlagChecks,
|
||||||
[]string{},
|
o.ChecksToRun,
|
||||||
fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ",")),
|
fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ",")),
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(options): Extract logic
|
// TODO(options): Extract logic
|
||||||
|
allowedFormats := []string{
|
||||||
|
FormatDefault,
|
||||||
|
FormatJSON,
|
||||||
|
}
|
||||||
|
|
||||||
if o.isSarifEnabled() {
|
if o.isSarifEnabled() {
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&o.PolicyFile,
|
&o.PolicyFile,
|
||||||
"policy",
|
FlagPolicyFile,
|
||||||
"",
|
o.PolicyFile,
|
||||||
"policy to enforce",
|
"policy to enforce",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.Flags().StringVar(
|
allowedFormats = append(allowedFormats, FormatSarif)
|
||||||
&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]",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
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
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user