Skip to content

Commit 2e062bc

Browse files
Get repo info from REST API if event file is unavailable (#576)
Co-authored-by: Azeem Shaikh <[email protected]>
1 parent 85bc05a commit 2e062bc

File tree

3 files changed

+104
-85
lines changed

3 files changed

+104
-85
lines changed

github/github.go

Lines changed: 64 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,18 @@
1515
package github
1616

1717
import (
18+
"bytes"
1819
"context"
1920
"encoding/json"
2021
"fmt"
2122
"io"
23+
"io/ioutil"
24+
"log"
2225
"net/http"
2326
"net/url"
24-
"os"
2527

2628
"github.com/ossf/scorecard/v4/clients/githubrepo/roundtripper"
27-
"github.com/ossf/scorecard/v4/log"
28-
)
29-
30-
const (
31-
baseRepoURL = "https://api.github.com/repos/"
29+
sclog "github.com/ossf/scorecard/v4/log"
3230
)
3331

3432
// RepoInfo is a struct for repository information.
@@ -43,9 +41,9 @@ type repo struct {
4341
4442
GITHUB_REPOSITORY_IS_FORK is true if the repository is a fork.
4543
*/
46-
DefaultBranch string `json:"default_branch"`
47-
Fork bool `json:"fork"`
48-
Private bool `json:"private"`
44+
DefaultBranch *string `json:"default_branch"`
45+
Fork *bool `json:"fork"`
46+
Private *bool `json:"private"`
4947
}
5048

5149
// Client holds a context and roundtripper for querying repo info from GitHub.
@@ -54,20 +52,6 @@ type Client struct {
5452
rt http.RoundTripper
5553
}
5654

57-
// NewClient returns a new Client for querying repo info from GitHub.
58-
func NewClient(ctx context.Context) *Client {
59-
c := &Client{}
60-
61-
defaultCtx := context.Background()
62-
if ctx == nil {
63-
ctx = defaultCtx
64-
}
65-
66-
c.SetContext(ctx)
67-
c.SetDefaultTransport()
68-
return c
69-
}
70-
7155
// SetContext sets a context for a GitHub client.
7256
func (c *Client) SetContext(ctx context.Context) {
7357
c.ctx = ctx
@@ -80,82 +64,96 @@ func (c *Client) SetTransport(rt http.RoundTripper) {
8064

8165
// SetDefaultTransport sets the scorecard roundtripper for a GitHub client.
8266
func (c *Client) SetDefaultTransport() {
83-
logger := log.NewLogger(log.DefaultLevel)
67+
logger := sclog.NewLogger(sclog.DefaultLevel)
8468
rt := roundtripper.NewTransport(c.ctx, logger)
8569
c.rt = rt
8670
}
8771

88-
// WriteRepoInfo queries GitHub for repo info and writes it to a file.
89-
func WriteRepoInfo(ctx context.Context, repoName, path string) error {
90-
c := NewClient(ctx)
91-
repoInfo, err := c.RepoInfo(repoName)
92-
if err != nil {
93-
return fmt.Errorf("getting repo info: %w", err)
94-
}
95-
96-
repoFile, err := os.Create(path)
97-
if err != nil {
98-
return fmt.Errorf("creating repo info file: %w", err)
99-
}
100-
defer repoFile.Close()
101-
102-
resp := repoInfo.respBytes
103-
_, writeErr := repoFile.Write(resp)
104-
if writeErr != nil {
105-
return fmt.Errorf("writing repo info: %w", writeErr)
106-
}
107-
108-
return nil
109-
}
110-
111-
// RepoInfo is a function to get the repository information.
72+
// ParseFromURL is a function to get the repository information.
11273
// It is decided to not use the golang GitHub library because of the
11374
// dependency on the github.com/google/go-github/github library
11475
// which will in turn require other dependencies.
115-
func (c *Client) RepoInfo(repoName string) (RepoInfo, error) {
116-
var r RepoInfo
117-
76+
func (c *Client) ParseFromURL(baseRepoURL, repoName string) (RepoInfo, error) {
77+
var ret RepoInfo
11878
baseURL, err := url.Parse(baseRepoURL)
11979
if err != nil {
120-
return r, fmt.Errorf("parsing base repo URL: %w", err)
80+
return ret, fmt.Errorf("parsing base repo URL: %w", err)
12181
}
12282

123-
repoURL, err := baseURL.Parse(repoName)
83+
repoURL, err := baseURL.Parse(fmt.Sprintf("repos/%s", repoName))
12484
if err != nil {
125-
return r, fmt.Errorf("parsing repo endpoint: %w", err)
85+
return ret, fmt.Errorf("parsing repo endpoint: %w", err)
12686
}
12787

128-
method := "GET"
88+
log.Printf("getting repo info from URL: %s", repoURL.String())
12989
req, err := http.NewRequestWithContext(
13090
c.ctx,
131-
method,
91+
http.MethodGet,
13292
repoURL.String(),
133-
nil,
134-
)
93+
nil /*body*/)
13594
if err != nil {
136-
return r, fmt.Errorf("error creating request: %w", err)
95+
return ret, fmt.Errorf("error creating request: %w", err)
13796
}
13897

13998
resp, err := http.DefaultClient.Do(req)
14099
if err != nil {
141-
return r, fmt.Errorf("error creating request: %w", err)
100+
return ret, fmt.Errorf("error creating request: %w", err)
142101
}
143102
defer resp.Body.Close()
144103
if err != nil {
145-
return r, fmt.Errorf("error reading response body: %w", err)
104+
return ret, fmt.Errorf("error reading response body: %w", err)
146105
}
147106

148107
respBytes, err := io.ReadAll(resp.Body)
149108
if err != nil {
150-
return r, fmt.Errorf("error reading response body: %w", err)
109+
return ret, fmt.Errorf("error reading response body: %w", err)
151110
}
152111

153-
r.respBytes = respBytes
112+
prettyPrintJSON(respBytes)
113+
ret.respBytes = respBytes
114+
if err := json.Unmarshal(respBytes, &ret.Repo); err != nil {
115+
return ret, fmt.Errorf("error decoding response body: %w", err)
116+
}
117+
return ret, nil
118+
}
119+
120+
// ParseFromFile is a function to get the repository information
121+
// from GitHub event file.
122+
func (c *Client) ParseFromFile(filepath string) (RepoInfo, error) {
123+
var ret RepoInfo
154124

155-
err = json.Unmarshal(respBytes, &r)
125+
log.Printf("getting repo info from file: %s", filepath)
126+
repoInfo, err := ioutil.ReadFile(filepath)
156127
if err != nil {
157-
return r, fmt.Errorf("error decoding response body: %w", err)
128+
return ret, fmt.Errorf("reading GitHub event path: %w", err)
158129
}
159130

160-
return r, nil
131+
prettyPrintJSON(repoInfo)
132+
if err := json.Unmarshal(repoInfo, &ret); err != nil {
133+
return ret, fmt.Errorf("unmarshalling repo info: %w", err)
134+
}
135+
136+
return ret, nil
137+
}
138+
139+
// NewClient returns a new Client for querying repo info from GitHub.
140+
func NewClient(ctx context.Context) *Client {
141+
c := &Client{
142+
ctx: ctx,
143+
}
144+
145+
if c.ctx == nil {
146+
c.SetContext(context.Background())
147+
}
148+
c.SetDefaultTransport()
149+
return c
150+
}
151+
152+
func prettyPrintJSON(jsonBytes []byte) {
153+
var buf bytes.Buffer
154+
if err := json.Indent(&buf, jsonBytes, "", ""); err != nil {
155+
log.Printf("%v", err)
156+
return
157+
}
158+
log.Println(buf.String())
161159
}

options/env.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
// Environment variables.
2323
// TODO(env): Remove once environment variables are not used for config.
24+
//
2425
//nolint:revive,nolintlint
2526
const (
2627
EnvEnableSarif = "ENABLE_SARIF"

options/options.go

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,14 @@
1515
package options
1616

1717
import (
18-
"encoding/json"
1918
"errors"
2019
"fmt"
21-
"io/ioutil"
2220
"os"
2321
"strconv"
2422
"strings"
2523

2624
"github.com/caarlos0/env/v6"
25+
"golang.org/x/net/context"
2726

2827
"github.com/ossf/scorecard-action/github"
2928
"github.com/ossf/scorecard/v4/checks"
@@ -44,6 +43,7 @@ var (
4443
// Errors.
4544
errGithubEventPathEmpty = errors.New("GitHub event path is empty")
4645
errResultsPathEmpty = errors.New("results path is empty")
46+
errGitHubRepoInfoUnavailable = errors.New("GitHub repo info inaccessible")
4747
errOnlyDefaultBranchSupported = errors.New("only default branch is supported")
4848
)
4949

@@ -67,6 +67,7 @@ type Options struct {
6767
GithubRef string `env:"GITHUB_REF"`
6868
GithubRepository string `env:"GITHUB_REPOSITORY"`
6969
GithubWorkspace string `env:"GITHUB_WORKSPACE"`
70+
GithubAPIURL string `env:"GITHUB_API_URL"`
7071

7172
DefaultBranch string `env:"SCORECARD_DEFAULT_BRANCH"`
7273
// TODO(options): This may be better as a bool
@@ -87,6 +88,13 @@ func New() (*Options, error) {
8788
if err := env.Parse(opts); err != nil {
8889
return opts, fmt.Errorf("parsing entrypoint env vars: %w", err)
8990
}
91+
// GITHUB_AUTH_TOKEN
92+
// Needs to be set *before* setRepoInfo() is invoked.
93+
// setRepoInfo() uses the GITHUB_AUTH_TOKEN env for querying the REST API.
94+
if _, tokenSet := os.LookupEnv(EnvGithubAuthToken); !tokenSet {
95+
inputToken := os.Getenv(EnvInputRepoToken)
96+
os.Setenv(EnvGithubAuthToken, inputToken)
97+
}
9098
if err := opts.setRepoInfo(); err != nil {
9199
return opts, fmt.Errorf("parsing repo info: %w", err)
92100
}
@@ -143,12 +151,6 @@ func (o *Options) Print() {
143151

144152
func (o *Options) setScorecardOpts() {
145153
o.ScorecardOpts = scopts.New()
146-
// GITHUB_AUTH_TOKEN
147-
_, tokenSet := os.LookupEnv(EnvGithubAuthToken)
148-
if !tokenSet {
149-
inputToken := os.Getenv(EnvInputRepoToken)
150-
os.Setenv(EnvGithubAuthToken, inputToken)
151-
}
152154

153155
// --repo= | --local
154156
// This section restores functionality that was removed in
@@ -194,6 +196,8 @@ func (o *Options) setScorecardOpts() {
194196
// setPublishResults sets whether results should be published based on a
195197
// repository's visibility.
196198
func (o *Options) setPublishResults() {
199+
inputVal := o.PublishResults
200+
o.PublishResults = false
197201
privateRepo, err := strconv.ParseBool(o.PrivateRepoStr)
198202
if err != nil {
199203
// TODO(options): Consider making this an error.
@@ -202,9 +206,10 @@ func (o *Options) setPublishResults() {
202206
o.PrivateRepoStr,
203207
err,
204208
)
209+
return
205210
}
206211

207-
o.PublishResults = o.PublishResults && !privateRepo
212+
o.PublishResults = inputVal && !privateRepo
208213
}
209214

210215
// setRepoInfo gets the path to the GitHub event and sets the
@@ -217,21 +222,36 @@ func (o *Options) setRepoInfo() error {
217222
return errGithubEventPathEmpty
218223
}
219224

220-
repoInfo, err := ioutil.ReadFile(eventPath)
221-
if err != nil {
222-
return fmt.Errorf("reading GitHub event path: %w", err)
225+
ghClient := github.NewClient(context.Background())
226+
if repoInfo, err := ghClient.ParseFromFile(eventPath); err == nil &&
227+
o.parseFromRepoInfo(repoInfo) {
228+
return nil
223229
}
224230

225-
var r github.RepoInfo
226-
if err := json.Unmarshal(repoInfo, &r); err != nil {
227-
return fmt.Errorf("unmarshalling repo info: %w", err)
231+
if repoInfo, err := ghClient.ParseFromURL(o.GithubAPIURL, o.GithubRepository); err == nil &&
232+
o.parseFromRepoInfo(repoInfo) {
233+
return nil
228234
}
229235

230-
o.PrivateRepoStr = strconv.FormatBool(r.Repo.Private)
231-
o.IsForkStr = strconv.FormatBool(r.Repo.Fork)
232-
o.DefaultBranch = r.Repo.DefaultBranch
236+
return errGitHubRepoInfoUnavailable
237+
}
233238

234-
return nil
239+
func (o *Options) parseFromRepoInfo(repoInfo github.RepoInfo) bool {
240+
if repoInfo.Repo.DefaultBranch == nil &&
241+
repoInfo.Repo.Fork == nil &&
242+
repoInfo.Repo.Private == nil {
243+
return false
244+
}
245+
if repoInfo.Repo.Private != nil {
246+
o.PrivateRepoStr = strconv.FormatBool(*repoInfo.Repo.Private)
247+
}
248+
if repoInfo.Repo.Fork != nil {
249+
o.IsForkStr = strconv.FormatBool(*repoInfo.Repo.Fork)
250+
}
251+
if repoInfo.Repo.DefaultBranch != nil {
252+
o.DefaultBranch = *repoInfo.Repo.DefaultBranch
253+
}
254+
return true
235255
}
236256

237257
func (o *Options) isPullRequestEvent() bool {

0 commit comments

Comments
 (0)