diff --git a/entrypoint/entrypoint.go b/entrypoint/entrypoint.go index a4477bdc..67f183ca 100755 --- a/entrypoint/entrypoint.go +++ b/entrypoint/entrypoint.go @@ -17,12 +17,13 @@ package entrypoint import ( "errors" "fmt" + "io" "os" "github.com/spf13/cobra" "github.com/ossf/scorecard-action/options" - "github.com/ossf/scorecard/v4/cmd" + sccmd "github.com/ossf/scorecard/v4/cmd" scopts "github.com/ossf/scorecard/v4/options" ) @@ -33,22 +34,20 @@ func New() (*cobra.Command, error) { if err != nil { return nil, fmt.Errorf("creating new options: %w", err) } - - if err := opts.Initialize(); err != nil { - return nil, fmt.Errorf("initializing options: %w", err) + if err := opts.Validate(); err != nil { + return nil, fmt.Errorf("validating options: %w", err) } + opts.Print() + // Adapt Scorecard CMD. scOpts := opts.ScorecardOpts - - actionCmd := cmd.New(scOpts) - + actionCmd := sccmd.New(scOpts) actionCmd.Flags().StringVar( &scOpts.ResultsFile, "output-file", scOpts.ResultsFile, "path to output results to", ) - actionCmd.Flags().BoolVar( &opts.PublishResults, "publish", @@ -60,11 +59,6 @@ func New() (*cobra.Command, error) { // TODO(scorecard): Move this into scorecard var out, stdout *os.File actionCmd.PreRunE = func(cmd *cobra.Command, args []string) error { - err := scOpts.Validate() - if err != nil { - return fmt.Errorf("validating options: %w", err) - } - // TODO: the results file should be completed and validated by the time we get it. if scOpts.ResultsFile != "" { var err error @@ -81,12 +75,15 @@ func New() (*cobra.Command, error) { os.Stdout = out actionCmd.SetOut(out) } - return nil } actionCmd.PersistentPostRun = func(cmd *cobra.Command, args []string) { if out != nil { + if _, err = out.Seek(0, io.SeekStart); err == nil { + // nolint:errcheck + _, _ = io.Copy(stdout, out) + } _ = out.Close() } os.Stdout = stdout diff --git a/options/options.go b/options/options.go index 81a0e0de..ab19218a 100644 --- a/options/options.go +++ b/options/options.go @@ -26,16 +26,25 @@ import ( "github.com/caarlos0/env/v6" "github.com/ossf/scorecard-action/github" + "github.com/ossf/scorecard/v4/checks" scopts "github.com/ossf/scorecard/v4/options" ) +const ( + defaultScorecardPolicyFile = "/policy.yml" + trueStr = "true" + formatSarif = scopts.FormatSarif + + pullRequestEvent = "pull_request" + pushEvent = "push" + branchProtectionEvent = "branch_protection_rule" +) + var ( // Errors. errGithubEventPathEmpty = errors.New("GitHub event path is empty") errResultsPathEmpty = errors.New("results path is empty") errOnlyDefaultBranchSupported = errors.New("only default branch is supported") - - trueStr = "true" ) // Options are options for running scorecard via GitHub Actions. @@ -72,99 +81,20 @@ type Options struct { PublishResults bool } -const ( - defaultScorecardPolicyFile = "/policy.yml" - formatSarif = scopts.FormatSarif -) - // New creates a new options set for running scorecard via GitHub Actions. func New() (*Options, error) { - // Enable scorecard command to use SARIF format. - os.Setenv(scopts.EnvVarEnableSarif, trueStr) - - opts := &Options{ - ScorecardOpts: scopts.New(), - } + opts := &Options{} if err := env.Parse(opts); err != nil { return opts, fmt.Errorf("parsing entrypoint env vars: %w", err) } - - opts.ScorecardOpts.ShowDetails = true - // This section restores functionality that was removed in - // https://github.com/ossf/scorecard/pull/1898. - // TODO(options): Consider moving this to its own function. - opts.ScorecardOpts.Repo = opts.GithubRepository - - if err := opts.Initialize(); err != nil { - return opts, fmt.Errorf( - "initializing scorecard-action options: %w", - err, - ) - } - - // TODO(options): Move this set-or-default logic to its own function. - opts.ScorecardOpts.Format = formatSarif - opts.ScorecardOpts.EnableSarif = true - if opts.InputResultsFormat != "" { - opts.ScorecardOpts.Format = opts.InputResultsFormat - } - - if opts.ScorecardOpts.Format == formatSarif { - if opts.ScorecardOpts.PolicyFile == "" { - // TODO(policy): Should we default or error here? - opts.ScorecardOpts.PolicyFile = defaultScorecardPolicyFile - } - } - - // TODO(scorecard): Reset commit options. Fix this in scorecard. - opts.ScorecardOpts.Commit = scopts.DefaultCommit - - if err := opts.ScorecardOpts.Validate(); err != nil { - return opts, fmt.Errorf("validating scorecard options: %w", err) - } - - opts.SetPublishResults() - - if opts.ScorecardOpts.ResultsFile == "" { - opts.ScorecardOpts.ResultsFile = opts.InputResultsFile - } - - if opts.ScorecardOpts.ResultsFile == "" { - // TODO(test): Reassess test case for this code path - return opts, errResultsPathEmpty + if err := opts.setRepoInfo(); err != nil { + return opts, fmt.Errorf("parsing repo info: %w", err) } - - if err := opts.Validate(); err != nil { - return opts, fmt.Errorf("validating scorecard-action options: %w", err) - } - + opts.setScorecardOpts() + opts.setPublishResults() return opts, nil } -// Initialize initializes the environment variables required for the action. -func (o *Options) Initialize() error { - /* - https://docs.github.com/en/actions/learn-github-actions/environment-variables - GITHUB_EVENT_PATH contains the json file for the event. - GITHUB_SHA contains the commit hash. - GITHUB_WORKSPACE contains the repo folder. - GITHUB_EVENT_NAME contains the event name. - GITHUB_ACTIONS is true in GitHub env. - */ - - // TODO(checks): Do we actually expect to use these? - // o.EnableLicense = "1" - // o.EnableDangerousWorkflow = "1" - - _, tokenSet := os.LookupEnv(EnvGithubAuthToken) - if !tokenSet { - inputToken := os.Getenv(EnvInputRepoToken) - os.Setenv(EnvGithubAuthToken, inputToken) - } - - return o.SetRepoInfo() -} - // Validate validates the scorecard configuration. func (o *Options) Validate() error { if os.Getenv(EnvGithubAuthToken) == "" { @@ -180,14 +110,20 @@ func (o *Options) Validate() error { return errEmptyGitHubAuthToken } - if strings.Contains(o.GithubEventName, "pull_request") && - o.GithubRef != o.DefaultBranch { + if !o.isPullRequestEvent() && + !o.isDefaultBranch() { fmt.Printf("%s not supported with %s event.\n", o.GithubRef, o.GithubEventName) fmt.Printf("Only the default branch %s is supported.\n", o.DefaultBranch) return errOnlyDefaultBranchSupported } - + if err := o.ScorecardOpts.Validate(); err != nil { + return fmt.Errorf("validating scorecard options: %w", err) + } + if o.ScorecardOpts.ResultsFile == "" { + // TODO(test): Reassess test case for this code path + return errResultsPathEmpty + } return nil } @@ -205,9 +141,59 @@ func (o *Options) Print() { fmt.Printf("Default branch: %s\n", o.DefaultBranch) } -// SetPublishResults sets whether results should be published based on a +func (o *Options) setScorecardOpts() { + o.ScorecardOpts = scopts.New() + // GITHUB_AUTH_TOKEN + _, tokenSet := os.LookupEnv(EnvGithubAuthToken) + if !tokenSet { + inputToken := os.Getenv(EnvInputRepoToken) + os.Setenv(EnvGithubAuthToken, inputToken) + } + + // --repo= | --local + // This section restores functionality that was removed in + // https://github.com/ossf/scorecard/pull/1898. + // TODO(options): Consider moving this to its own function. + if !o.isPullRequestEvent() { + o.ScorecardOpts.Repo = o.GithubRepository + } else { + o.ScorecardOpts.Local = "." + } + + // --format= + // Enable scorecard command to use SARIF format (default format). + os.Setenv(scopts.EnvVarEnableSarif, trueStr) + o.ScorecardOpts.EnableSarif = true + o.ScorecardOpts.Format = formatSarif + if o.InputResultsFormat != "" { + o.ScorecardOpts.Format = o.InputResultsFormat + } + if o.ScorecardOpts.Format == formatSarif && o.ScorecardOpts.PolicyFile == "" { + // TODO(policy): Should we default or error here? + o.ScorecardOpts.PolicyFile = defaultScorecardPolicyFile + } + + // --checks= + if o.GithubEventName == branchProtectionEvent { + o.ScorecardOpts.ChecksToRun = []string{checks.CheckBranchProtection} + } + + // --show-details + o.ScorecardOpts.ShowDetails = true + + // --commit= + // TODO(scorecard): Reset commit options. Fix this in scorecard. + o.ScorecardOpts.Commit = scopts.DefaultCommit + + // --out-file= + if o.ScorecardOpts.ResultsFile == "" { + o.ScorecardOpts.ResultsFile = o.InputResultsFile + } +} + +// setPublishResults sets whether results should be published based on a // repository's visibility. -func (o *Options) SetPublishResults() { +func (o *Options) setPublishResults() { privateRepo, err := strconv.ParseBool(o.PrivateRepoStr) if err != nil { // TODO(options): Consider making this an error. @@ -221,11 +207,11 @@ func (o *Options) SetPublishResults() { o.PublishResults = o.PublishResults && !privateRepo } -// SetRepoInfo gets the path to the GitHub event and sets the +// setRepoInfo gets the path to the GitHub event and sets the // SCORECARD_IS_FORK environment variable. // TODO(options): Check if this actually needs to be exported. // TODO(options): Choose a more accurate name for what this does. -func (o *Options) SetRepoInfo() error { +func (o *Options) setRepoInfo() error { eventPath := o.GithubEventPath if eventPath == "" { return errGithubEventPathEmpty @@ -247,3 +233,11 @@ func (o *Options) SetRepoInfo() error { return nil } + +func (o *Options) isPullRequestEvent() bool { + return strings.HasPrefix(o.GithubEventName, pullRequestEvent) +} + +func (o *Options) isDefaultBranch() bool { + return o.GithubRef == fmt.Sprintf("refs/heads/%s", o.DefaultBranch) +} diff --git a/options/options_test.go b/options/options_test.go index 6b3067f0..8b4fe802 100644 --- a/options/options_test.go +++ b/options/options_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/ossf/scorecard/v4/checks" "github.com/ossf/scorecard/v4/options" ) @@ -33,6 +34,7 @@ const ( githubEventPathIncorrect = "testdata/incorrect.json" githubEventPathBadPath = "testdata/bad-path.json" githubEventPathBadData = "testdata/bad-data.json" + githubEventPathPublic = "testdata/public.json" ) func TestNew(t *testing.T) { @@ -43,6 +45,10 @@ func TestNew(t *testing.T) { ResultsFile string Commit string LogLevel string + Repo string + Local string + ChecksToRun []string + ShowDetails bool } tests := []struct { name string @@ -61,8 +67,8 @@ func TestNew(t *testing.T) { { name: "SuccessFormatSARIF", githubEventPath: githubEventPathNonFork, - githubEventName: "pull_request", - githubRef: "main", + githubEventName: pushEvent, + githubRef: "refs/heads/main", repo: testRepo, resultsFormat: "sarif", resultsFile: testResultsFile, @@ -73,14 +79,16 @@ func TestNew(t *testing.T) { ResultsFile: testResultsFile, Commit: options.DefaultCommit, LogLevel: options.DefaultLogLevel, + Repo: testRepo, + ShowDetails: true, }, wantErr: false, }, { name: "SuccessFormatJSON", githubEventPath: githubEventPathNonFork, - githubEventName: "pull_request", - githubRef: "main", + githubEventName: pushEvent, + githubRef: "refs/heads/main", repo: testRepo, resultsFormat: "json", resultsFile: testResultsFile, @@ -90,14 +98,55 @@ func TestNew(t *testing.T) { ResultsFile: testResultsFile, Commit: options.DefaultCommit, LogLevel: options.DefaultLogLevel, + Repo: testRepo, + ShowDetails: true, + }, + wantErr: false, + }, + { + name: "SuccessPullRequest", + githubEventPath: githubEventPathNonFork, + githubEventName: pullRequestEvent, + githubRef: "refs/heads/pr-branch", + repo: testRepo, + resultsFormat: "json", + resultsFile: testResultsFile, + want: fields{ + EnableSarif: true, + Format: options.FormatJSON, + ResultsFile: testResultsFile, + Commit: options.DefaultCommit, + LogLevel: options.DefaultLogLevel, + Local: ".", + ShowDetails: true, + }, + wantErr: false, + }, + { + name: "SuccessBranchProtectionEvent", + githubEventPath: githubEventPathNonFork, + githubEventName: branchProtectionEvent, + githubRef: "refs/heads/main", + repo: testRepo, + resultsFormat: "json", + resultsFile: testResultsFile, + want: fields{ + EnableSarif: true, + Format: options.FormatJSON, + ResultsFile: testResultsFile, + Commit: options.DefaultCommit, + LogLevel: options.DefaultLogLevel, + Repo: testRepo, + ChecksToRun: []string{checks.CheckBranchProtection}, + ShowDetails: true, }, wantErr: false, }, { name: "FailureTokenIsNotSet", githubEventPath: githubEventPathNonFork, - githubEventName: "pull_request", - githubRef: "main", + githubEventName: pushEvent, + githubRef: "refs/heads/main", repo: testRepo, resultsFormat: "sarif", resultsFile: testResultsFile, @@ -108,6 +157,8 @@ func TestNew(t *testing.T) { ResultsFile: testResultsFile, Commit: options.DefaultCommit, LogLevel: options.DefaultLogLevel, + Repo: testRepo, + ShowDetails: true, }, unsetToken: true, wantErr: true, @@ -115,14 +166,15 @@ func TestNew(t *testing.T) { { name: "FailureResultsPathNotSet", githubEventPath: githubEventPathNonFork, - githubEventName: "pull_request", - githubRef: "main", + githubEventName: pushEvent, + githubRef: "refs/heads/main", want: fields{ EnableSarif: true, Format: formatSarif, PolicyFile: defaultScorecardPolicyFile, Commit: options.DefaultCommit, LogLevel: options.DefaultLogLevel, + ShowDetails: true, }, unsetResultsPath: true, wantErr: true, @@ -130,8 +182,8 @@ func TestNew(t *testing.T) { { name: "FailureResultsPathEmpty", githubEventPath: githubEventPathNonFork, - githubEventName: "pull_request", - githubRef: "main", + githubEventName: pushEvent, + githubRef: "refs/heads/main", resultsFile: "", want: fields{ EnableSarif: true, @@ -140,14 +192,15 @@ func TestNew(t *testing.T) { ResultsFile: "", Commit: options.DefaultCommit, LogLevel: options.DefaultLogLevel, + ShowDetails: true, }, wantErr: true, }, { name: "FailureBranchIsntMain", githubEventPath: githubEventPathNonFork, - githubEventName: "pull_request", - githubRef: "other-branch", + githubEventName: pushEvent, + githubRef: "refs/heads/other-branch", repo: testRepo, resultsFormat: "sarif", resultsFile: testResultsFile, @@ -158,6 +211,8 @@ func TestNew(t *testing.T) { ResultsFile: testResultsFile, Commit: options.DefaultCommit, LogLevel: options.DefaultLogLevel, + Repo: testRepo, + ShowDetails: true, }, wantErr: true, }, @@ -202,24 +257,31 @@ func TestNew(t *testing.T) { ResultsFile: scOpts.ResultsFile, Commit: scOpts.Commit, LogLevel: scOpts.LogLevel, + Repo: scOpts.Repo, + Local: scOpts.Local, + ChecksToRun: scOpts.ChecksToRun, + ShowDetails: scOpts.ShowDetails, } - if (err != nil) != tt.wantErr { + if err != nil { + t.Fatalf("New(): %v", err) + } + if !cmp.Equal(tt.want, got) { + t.Errorf("New(): -want, +got:\n%s", cmp.Diff(tt.want, got)) + } + + if err := opts.Validate(); (err != nil) != tt.wantErr { for _, e := range os.Environ() { t.Logf(e) } - t.Errorf("New() error = %+v, wantErr %+v", err, tt.wantErr) + t.Errorf("Validate() error = %+v, wantErr %+v", err, tt.wantErr) return } - - if !cmp.Equal(tt.want, got) { - t.Errorf("New(): -want, +got:\n%s", cmp.Diff(tt.want, got)) - } }) } } -func TestInitialize(t *testing.T) { +func TestSetRepoInfo(t *testing.T) { type fields struct { ScorecardOpts *options.Options EnabledChecks string @@ -281,8 +343,8 @@ func TestInitialize(t *testing.T) { IsForkStr: tt.fields.IsForkStr, PrivateRepoStr: tt.fields.PrivateRepoStr, } - if err := o.Initialize(); (err != nil) != tt.wantErr { - t.Errorf("Options.Initialize() error = %v, wantErr %v", err, tt.wantErr) + if err := o.setRepoInfo(); (err != nil) != tt.wantErr { + t.Errorf("Options.setRepoInfo() error = %v, wantErr %v", err, tt.wantErr) } }) } @@ -365,7 +427,7 @@ func TestSetPublishResults(t *testing.T) { } opts.PrivateRepoStr = tt.privateRepo - opts.SetPublishResults() + opts.setPublishResults() got := opts.PublishResults if !cmp.Equal(tt.want, got) { diff --git a/options/testdata/public.json b/options/testdata/public.json new file mode 100644 index 00000000..e0884058 --- /dev/null +++ b/options/testdata/public.json @@ -0,0 +1,173 @@ +{ + "after": "aa0496aa6ed5102642f352a5c4ad3cf090017c76", + "base_ref": null, + "before": "3d471fd36d7f1147843c69d68de35d321d36fe43", + "commits": [ + { + "author": { + "email": "64505099+laurentsimon@users.noreply.github.com", + "name": "laurentsimon", + "username": "laurentsimon" + }, + "committer": { + "email": "noreply@github.com", + "name": "GitHub", + "username": "web-flow" + }, + "distinct": true, + "id": "aa0496aa6ed5102642f352a5c4ad3cf090017c76", + "message": "Update dummy", + "timestamp": "2022-01-10T14:24:44-08:00", + "tree_id": "91673de3f7984d11277da340d24b0187523bd283", + "url": "https://github.com/laurentsimon/scorecard-action-test-2/commit/aa0496aa6ed5102642f352a5c4ad3cf090017c76" + } + ], + "compare": "https://github.com/laurentsimon/scorecard-action-test-2/compare/3d471fd36d7f...aa0496aa6ed5", + "created": false, + "deleted": false, + "forced": false, + "head_commit": { + "author": { + "email": "64505099+laurentsimon@users.noreply.github.com", + "name": "laurentsimon", + "username": "laurentsimon" + }, + "committer": { + "email": "noreply@github.com", + "name": "GitHub", + "username": "web-flow" + }, + "distinct": true, + "id": "aa0496aa6ed5102642f352a5c4ad3cf090017c76", + "message": "Update dummy", + "timestamp": "2022-01-10T14:24:44-08:00", + "tree_id": "91673de3f7984d11277da340d24b0187523bd283", + "url": "https://github.com/laurentsimon/scorecard-action-test-2/commit/aa0496aa6ed5102642f352a5c4ad3cf090017c76" + }, + "pusher": { + "email": "64505099+laurentsimon@users.noreply.github.com", + "name": "laurentsimon" + }, + "ref": "refs/heads/main", + "repository": { + "allow_forking": true, + "archive_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/{archive_format}{/ref}", + "archived": false, + "assignees_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/assignees{/user}", + "blobs_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/branches{/branch}", + "clone_url": "https://github.com/laurentsimon/scorecard-action-test-2.git", + "collaborators_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/comments{/number}", + "commits_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/commits{/sha}", + "compare_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/contents/{+path}", + "contributors_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/contributors", + "created_at": 1636137447, + "default_branch": "main", + "deployments_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/deployments", + "description": null, + "disabled": false, + "downloads_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/downloads", + "events_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/events", + "forks": 0, + "forks_count": 0, + "forks_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/forks", + "full_name": "laurentsimon/scorecard-action-test-2", + "git_commits_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/git/tags{/sha}", + "git_url": "git://github.com/laurentsimon/scorecard-action-test-2.git", + "has_downloads": true, + "has_issues": true, + "has_pages": false, + "has_projects": true, + "has_wiki": true, + "homepage": null, + "hooks_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/hooks", + "html_url": "https://github.com/laurentsimon/scorecard-action-test-2", + "id": 425049966, + "is_template": false, + "issue_comment_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/issues/events{/number}", + "issues_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/issues{/number}", + "keys_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/keys{/key_id}", + "labels_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/labels{/name}", + "language": null, + "languages_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/languages", + "license": null, + "master_branch": "main", + "merges_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/merges", + "milestones_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/milestones{/number}", + "mirror_url": null, + "name": "scorecard-action-test-2", + "node_id": "R_kgDOGVW_bg", + "notifications_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/notifications{?since,all,participating}", + "open_issues": 0, + "open_issues_count": 0, + "owner": { + "avatar_url": "https://avatars.githubusercontent.com/u/64505099?v=4", + "email": "64505099+laurentsimon@users.noreply.github.com", + "events_url": "https://api.github.com/users/laurentsimon/events{/privacy}", + "followers_url": "https://api.github.com/users/laurentsimon/followers", + "following_url": "https://api.github.com/users/laurentsimon/following{/other_user}", + "gists_url": "https://api.github.com/users/laurentsimon/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/laurentsimon", + "id": 64505099, + "login": "laurentsimon", + "name": "laurentsimon", + "node_id": "MDQ6VXNlcjY0NTA1MDk5", + "organizations_url": "https://api.github.com/users/laurentsimon/orgs", + "received_events_url": "https://api.github.com/users/laurentsimon/received_events", + "repos_url": "https://api.github.com/users/laurentsimon/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/laurentsimon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/laurentsimon/subscriptions", + "type": "User", + "url": "https://api.github.com/users/laurentsimon" + }, + "private": false, + "pulls_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/pulls{/number}", + "pushed_at": 1641853484, + "releases_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/releases{/id}", + "size": 315, + "ssh_url": "git@github.com:laurentsimon/scorecard-action-test-2.git", + "stargazers": 0, + "stargazers_count": 0, + "stargazers_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/stargazers", + "statuses_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/subscribers", + "subscription_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/subscription", + "svn_url": "https://github.com/laurentsimon/scorecard-action-test-2", + "tags_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/tags", + "teams_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/teams", + "topics": [], + "trees_url": "https://api.github.com/repos/laurentsimon/scorecard-action-test-2/git/trees{/sha}", + "updated_at": "2022-01-10T22:16:01Z", + "url": "https://github.com/laurentsimon/scorecard-action-test-2", + "visibility": "private", + "watchers": 0, + "watchers_count": 0 + }, + "sender": { + "avatar_url": "https://avatars.githubusercontent.com/u/64505099?v=4", + "events_url": "https://api.github.com/users/laurentsimon/events{/privacy}", + "followers_url": "https://api.github.com/users/laurentsimon/followers", + "following_url": "https://api.github.com/users/laurentsimon/following{/other_user}", + "gists_url": "https://api.github.com/users/laurentsimon/gists{/gist_id}", + "gravatar_id": "", + "html_url": "https://github.com/laurentsimon", + "id": 64505099, + "login": "laurentsimon", + "node_id": "MDQ6VXNlcjY0NTA1MDk5", + "organizations_url": "https://api.github.com/users/laurentsimon/orgs", + "received_events_url": "https://api.github.com/users/laurentsimon/received_events", + "repos_url": "https://api.github.com/users/laurentsimon/repos", + "site_admin": false, + "starred_url": "https://api.github.com/users/laurentsimon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/laurentsimon/subscriptions", + "type": "User", + "url": "https://api.github.com/users/laurentsimon" + } +}