Skip to content

Commit

Permalink
✨ cmd: Refactor to make importable (#1696)
Browse files Browse the repository at this point in the history
* cmd: Refactor to make importable
* options: Add support for parsing via environment variables
* options: Support setting feature flags via option
* cmd: Replace `version` with sigs.k8s.io/release-utils/version
* cmd: Move option validation into pre-run function

Signed-off-by: Stephen Augustus <[email protected]>
  • Loading branch information
justaugustus authored Mar 2, 2022
1 parent 738b246 commit 84cdc8c
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 209 deletions.
104 changes: 31 additions & 73 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import (
"strings"

"github.com/spf13/cobra"
"sigs.k8s.io/release-utils/version"

"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/checks"
"github.com/ossf/scorecard/v4/clients"
docs "github.com/ossf/scorecard/v4/docs/checks"
sclog "github.com/ossf/scorecard/v4/log"
Expand All @@ -35,6 +35,8 @@ 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 @@ -43,80 +45,36 @@ const (
scorecardShort = "Security Scorecards"
)

var rootCmd = &cobra.Command{
Use: scorecardUse,
Short: scorecardShort,
Long: scorecardLong,
Run: scorecardCmd,
}

var opts = options.New()

//nolint:gochecknoinits
func init() {
rootCmd.Flags().StringVar(&opts.Repo, "repo", "", "repository to check")
rootCmd.Flags().StringVar(&opts.Local, "local", "", "local folder to check")
rootCmd.Flags().StringVar(&opts.Commit, "commit", options.DefaultCommit, "commit to analyze")
rootCmd.Flags().StringVar(
&opts.LogLevel,
"verbosity",
options.DefaultLogLevel,
"set the log level",
)
rootCmd.Flags().StringVar(
&opts.NPM, "npm", "",
"npm package to check, given that the npm package has a GitHub repository")
rootCmd.Flags().StringVar(
&opts.PyPI, "pypi", "",
"pypi package to check, given that the pypi package has a GitHub repository")
rootCmd.Flags().StringVar(
&opts.RubyGems, "rubygems", "",
"rubygems package to check, given that the rubygems package has a GitHub repository")
rootCmd.Flags().StringSliceVar(
&opts.Metadata, "metadata", []string{}, "metadata for the project. It can be multiple separated by commas")
rootCmd.Flags().BoolVar(&opts.ShowDetails, "show-details", false, "show extra details about each check")
checkNames := []string{}
for checkName := range checks.GetAll() {
checkNames = append(checkNames, checkName)
}
rootCmd.Flags().StringSliceVar(&opts.ChecksToRun, "checks", []string{},
fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ",")))

// TODO(cmd): Extract logic
if options.IsSarifEnabled() {
rootCmd.Flags().StringVar(&opts.PolicyFile, "policy", "", "policy to enforce")
rootCmd.Flags().StringVar(&opts.Format, "format", options.FormatDefault,
"output format allowed values are [default, sarif, json]")
} else {
rootCmd.Flags().StringVar(&opts.Format, "format", options.FormatDefault,
"output format allowed values are [default, json]")
}
}

// Execute runs the Scorecard commandline.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func scorecardCmd(cmd *cobra.Command, args []string) {
RunScorecard(args)
// New creates a new instance of the scorecard command.
func New() *cobra.Command {
cmd := &cobra.Command{
Use: scorecardUse,
Short: scorecardShort,
Long: scorecardLong,
PreRunE: func(cmd *cobra.Command, args []string) error {
err := opts.Validate()
if err != nil {
return fmt.Errorf("validating options: %w", err)
}

return nil
},
// TODO(cmd): Consider using RunE here
Run: func(cmd *cobra.Command, args []string) {
rootCmd(opts)
},
}

opts.AddFlags(cmd)

// Add sub-commands.
cmd.AddCommand(serveCmd())
cmd.AddCommand(version.Version())
return cmd
}

// RunScorecard runs scorecard checks given a set of arguments.
// TODO(cmd): Is `args` required?
func RunScorecard(args []string) {
// TODO(cmd): Catch validation errors
valErrs := opts.Validate()
if len(valErrs) != 0 {
log.Panicf(
"the following validation errors occurred: %+v",
valErrs,
)
}

// rootCmd runs scorecard checks given a set of arguments.
func rootCmd(opts *options.Options) {
// Set `repo` from package managers.
pkgResp, err := fetchGitRepositoryFromPackageManagers(opts.NPM, opts.PyPI, opts.RubyGems)
if err != nil {
Expand Down
126 changes: 62 additions & 64 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,79 +30,77 @@ import (
"github.com/ossf/scorecard/v4/pkg"
)

//nolint:gochecknoinits
func init() {
rootCmd.AddCommand(serveCmd)
}

var serveCmd = &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))

t, err := template.New("webpage").Parse(tpl)
if err != nil {
// TODO(log): Should this actually panic?
logger.Error(err, "parsing webpage template")
panic(err)
}
// TODO(cmd): Determine if this should be exported.
func serveCmd() *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))

http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
repoParam := r.URL.Query().Get("repo")
const length = 3
s := strings.SplitN(repoParam, "/", length)
if len(s) != length {
rw.WriteHeader(http.StatusBadRequest)
}
repo, err := githubrepo.MakeGithubRepo(repoParam)
t, err := template.New("webpage").Parse(tpl)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
}
ctx := r.Context()
repoClient := githubrepo.CreateGithubRepoClient(ctx, logger)
ossFuzzRepoClient, err := githubrepo.CreateOssFuzzRepoClient(ctx, logger)
vulnsClient := clients.DefaultVulnerabilitiesClient()
if err != nil {
logger.Error(err, "initializing clients")
rw.WriteHeader(http.StatusInternalServerError)
}
defer ossFuzzRepoClient.Close()
ciiClient := clients.DefaultCIIBestPracticesClient()
repoResult, err := pkg.RunScorecards(
ctx, repo, clients.HeadSHA /*commitSHA*/, false /*raw*/, checks.AllChecks, repoClient,
ossFuzzRepoClient, ciiClient, vulnsClient)
if err != nil {
logger.Error(err, "running enabled scorecard checks on repo")
rw.WriteHeader(http.StatusInternalServerError)
// TODO(log): Should this actually panic?
logger.Error(err, "parsing webpage template")
panic(err)
}

if r.Header.Get("Content-Type") == "application/json" {
if err := repoResult.AsJSON(opts.ShowDetails, log.ParseLevel(opts.LogLevel), rw); err != nil {
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
repoParam := r.URL.Query().Get("repo")
const length = 3
s := strings.SplitN(repoParam, "/", length)
if len(s) != length {
rw.WriteHeader(http.StatusBadRequest)
}
repo, err := githubrepo.MakeGithubRepo(repoParam)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
}
ctx := r.Context()
repoClient := githubrepo.CreateGithubRepoClient(ctx, logger)
ossFuzzRepoClient, err := githubrepo.CreateOssFuzzRepoClient(ctx, logger)
vulnsClient := clients.DefaultVulnerabilitiesClient()
if err != nil {
logger.Error(err, "initializing clients")
rw.WriteHeader(http.StatusInternalServerError)
}
defer ossFuzzRepoClient.Close()
ciiClient := clients.DefaultCIIBestPracticesClient()
repoResult, err := pkg.RunScorecards(
ctx, repo, clients.HeadSHA /*commitSHA*/, false /*raw*/, checks.AllChecks, repoClient,
ossFuzzRepoClient, ciiClient, vulnsClient)
if err != nil {
logger.Error(err, "running enabled scorecard checks on repo")
rw.WriteHeader(http.StatusInternalServerError)
}

if r.Header.Get("Content-Type") == "application/json" {
if err := repoResult.AsJSON(opts.ShowDetails, log.ParseLevel(opts.LogLevel), rw); err != nil {
// TODO(log): Improve error message
logger.Error(err, "")
rw.WriteHeader(http.StatusInternalServerError)
}
return
}
if err := t.Execute(rw, repoResult); err != nil {
// TODO(log): Improve error message
logger.Error(err, "")
rw.WriteHeader(http.StatusInternalServerError)
}
return
})
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
if err := t.Execute(rw, repoResult); err != nil {
// TODO(log): Improve error message
logger.Error(err, "")
fmt.Printf("Listening on localhost:%s\n", port)
err = http.ListenAndServe(fmt.Sprintf("0.0.0.0:%s", port), nil)
if err != nil {
// TODO(log): Should this actually panic?
logger.Error(err, "listening and serving")
panic(err)
}
})
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
fmt.Printf("Listening on localhost:%s\n", port)
err = http.ListenAndServe(fmt.Sprintf("0.0.0.0:%s", port), nil)
if err != nil {
// TODO(log): Should this actually panic?
logger.Error(err, "listening and serving")
panic(err)
}
},
},
}
}

const tpl = `
Expand Down
44 changes: 0 additions & 44 deletions cmd/version.go

This file was deleted.

7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ require (
mvdan.cc/sh/v3 v3.4.3
)

require github.com/onsi/ginkgo/v2 v2.1.3
require (
github.com/caarlos0/env/v6 v6.9.1
github.com/onsi/ginkgo/v2 v2.1.3
sigs.k8s.io/release-utils v0.5.0
)

require (
cloud.google.com/go v0.100.2 // indirect
Expand All @@ -56,6 +60,7 @@ require (
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/aws/aws-sdk-go v1.40.34 // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/stargz-snapshotter/estargz v0.10.1 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/docker/cli v20.10.12+incompatible // indirect
Expand Down
Loading

0 comments on commit 84cdc8c

Please sign in to comment.