Skip to content

Commit b2e40de

Browse files
authored
Status API support for checks and fixes to pullvet note checks (#7)
* test7 * Change CreateCheckRun experiment * feat(checks): statuses * feat(checks): add description to status * feat(checks): add -status-context * add event dump to dev.yml * fix(pullvet): note detection * fix(pullvet): always get pull request body from api as it is `null` sometimes(maybe it is non-null only when `open` and `synchronize`? was null when pr reopen and label/unlabel)
1 parent 1290066 commit b2e40de

File tree

6 files changed

+205
-69
lines changed

6 files changed

+205
-69
lines changed

.github/workflows/dev.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ jobs:
3434
runs-on: ubuntu-latest
3535
if: startsWith(github.repository, 'variantdev/')
3636
steps:
37+
- run: |
38+
echo GITHUB_ACTION: $GITHUB_ACTION
39+
echo GITHUB_WORKFLOW: $GITHUB_WORKFLOW
40+
echo GITHUB_EVENT_NAME: $GITHUB_EVENT_NAME
41+
echo GITHUB_EVENT_PATH: $GITHUB_EVENT_PATH
42+
cat $GITHUB_EVENT_PATH
3743
- uses: actions/checkout@master
3844
- uses: actions/setup-go@v1
3945
with:
@@ -60,3 +66,33 @@ jobs:
6066
bin/actions checks -run dev2 -- bin/actions pullvet -require-any -label dev2 -label dev3
6167
env:
6268
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
69+
ensure-dev3:
70+
runs-on: ubuntu-latest
71+
if: startsWith(github.repository, 'variantdev/')
72+
steps:
73+
- uses: actions/checkout@master
74+
- uses: actions/setup-go@v1
75+
with:
76+
go-version: '1.12.7'
77+
- run: |
78+
make build
79+
- name: dev3
80+
run: |
81+
bin/actions checks -status-context checks/dev3 -- bin/actions pullvet -require-any -label dev3
82+
env:
83+
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
84+
ensure-changelog:
85+
runs-on: ubuntu-latest
86+
if: startsWith(github.repository, 'variantdev/')
87+
steps:
88+
- uses: actions/checkout@master
89+
- uses: actions/setup-go@v1
90+
with:
91+
go-version: '1.12.7'
92+
- run: |
93+
make build
94+
- name: changelog
95+
run: |
96+
bin/actions checks -status-context checks/changelog -- bin/actions pullvet -require-any -note changelog1 -note changelog2
97+
env:
98+
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

client.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package actions
2+
3+
import (
4+
"context"
5+
"os"
6+
7+
"github.com/google/go-github/v28/github"
8+
"golang.org/x/oauth2"
9+
)
10+
11+
// CreateInstallationTokenClient uses an installation token to authenticate to the Github API.
12+
func CreateInstallationTokenClient(instToken, baseURL, uploadURL string) (*github.Client, error) {
13+
// For installation tokens, Github uses a different token type ("token" instead of "bearer")
14+
tokenType := "token"
15+
if os.Getenv("GITHUB_TOKEN_TYPE") != "" {
16+
tokenType = os.Getenv("GITHUB_TOKEN_TYPE")
17+
}
18+
t := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: instToken, TokenType: tokenType})
19+
c := context.Background()
20+
tc := oauth2.NewClient(c, t)
21+
if baseURL != "" {
22+
return github.NewEnterpriseClient(baseURL, uploadURL, tc)
23+
}
24+
return github.NewClient(tc), nil
25+
}

event.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,28 +63,35 @@ func CheckSuiteEvent() (*github.CheckSuiteEvent, error) {
6363
return evt.(*github.CheckSuiteEvent), nil
6464
}
6565

66-
func PullRequest() (*github.PullRequest, error) {
66+
func PullRequest() (*github.PullRequest, string, string, error) {
6767
var pr *github.PullRequest
68+
var owner, repo string
6869
switch EventName() {
6970
case "pull_request":
7071
pull, err := PullRequestEvent()
7172
if err != nil {
72-
return nil, err
73+
return nil, "", "", err
7374
}
7475
pr = pull.PullRequest
76+
owner = pull.Repo.Owner.GetLogin()
77+
repo = pull.Repo.GetName()
7578
case "check_run":
7679
checkRun, err := CheckRunEvent()
7780
if err != nil {
78-
return nil, err
81+
return nil, "", "", err
7982
} else {
8083
pr = checkRun.CheckRun.PullRequests[0]
8184
}
85+
owner = checkRun.Repo.Owner.GetLogin()
86+
repo = checkRun.Repo.GetName()
8287
case "check_suite":
8388
checkSuite, err := CheckSuiteEvent()
8489
if err != nil {
85-
return nil, err
90+
return nil, "", "", err
8691
}
8792
pr = checkSuite.CheckSuite.PullRequests[0]
93+
owner = checkSuite.Repo.Owner.GetLogin()
94+
repo = checkSuite.Repo.GetName()
8895
}
89-
return pr, nil
96+
return pr, owner, repo, nil
9097
}

pkg/checks/checks.go

Lines changed: 83 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ import (
1616
"github.com/google/go-github/v28/github"
1717
"github.com/variantdev/go-actions"
1818
"github.com/variantdev/go-actions/pkg/cmd"
19-
"golang.org/x/oauth2"
2019
)
2120

2221
type Command struct {
2322
BaseURL, UploadURL string
2423
createRuns cmd.StringSlice
2524

2625
checkName string
27-
cmd string
28-
args []string
26+
27+
statusContext string
28+
29+
cmd string
30+
args []string
2931
}
3032

3133
func New() *Command {
@@ -40,6 +42,7 @@ func (c *Command) AddFlags(fs *flag.FlagSet) {
4042
fs.StringVar(&c.UploadURL, "github-upload-url", "", "")
4143
fs.Var(&c.createRuns, "create-run", "Name of CheckRun to be created on CheckSuite `(re)requested` event. Specify multiple times to create two or more runs")
4244
fs.StringVar(&c.checkName, "run", "", "CheckRun's name to be updated after the command in run")
45+
fs.StringVar(&c.statusContext, "status-context", "", "Commit status' context. If not empty, `checks` creates a status with this contecxt")
4346
}
4447

4548
func (c *Command) Run(args []string) error {
@@ -129,13 +132,13 @@ func (c *Command) createCheckRun(suite *github.CheckSuite, cr Run) (*github.Chec
129132
}
130133

131134
type CreateCheckRunOptions struct {
132-
Name string `json:"name"` // The name of the check (e.g., "code-coverage"). (Required.)
133-
HeadBranch string `json:"head_branch"` // The name of the branch to perform a check against. (Required.)
134-
HeadSHA string `json:"head_sha"` // The SHA of the commit. (Required.)
135-
DetailsURL *string `json:"details_url,omitempty"` // The URL of the integrator's site that has the full details of the check. (Optional.)
136-
ExternalID *string `json:"external_id,omitempty"` // A reference for the run on the integrator's system. (Optional.)
137-
Status *string `json:"status,omitempty"` // The current status. Can be one of "queued", "in_progress", or "completed". Default: "queued". (Optional.)
138-
Conclusion *string `json:"conclusion,omitempty"` // Can be one of "success", "failure", "neutral", "cancelled", "timed_out", or "action_required". (Optional. Required if you provide a status of "completed".)
135+
Name string `json:"name"` // The name of the check (e.g., "code-coverage"). (Required.)
136+
HeadBranch string `json:"head_branch"` // The name of the branch to perform a check against. (Required.)
137+
HeadSHA string `json:"head_sha"` // The SHA of the commit. (Required.)
138+
DetailsURL *string `json:"details_url,omitempty"` // The URL of the integrator's site that has the full details of the check. (Optional.)
139+
ExternalID *string `json:"external_id,omitempty"` // A reference for the run on the integrator's system. (Optional.)
140+
Status *string `json:"status,omitempty"` // The current status. Can be one of "queued", "in_progress", or "completed". Default: "queued". (Optional.)
141+
Conclusion *string `json:"conclusion,omitempty"` // Can be one of "success", "failure", "neutral", "cancelled", "timed_out", or "action_required". (Optional. Required if you provide a status of "completed".)
139142
// Does this really work?
140143
CheckSuiteID *int64 `json:"check_suite_id,omitempty`
141144
StartedAt *github.Timestamp `json:"started_at,omitempty"` // The time that the check run began. (Optional.)
@@ -145,7 +148,7 @@ type CreateCheckRunOptions struct {
145148
}
146149

147150
func (c *Command) CreateCheckRun(client *github.Client, ctx context.Context, owner, repo string, opt CreateCheckRunOptions) (*github.CheckRun, *github.Response, error) {
148-
u := fmt.Sprintf("repos/%v/%v/check-runs", owner, repo)
151+
u := fmt.Sprintf("repos/%v/%v/check-suites/%v/check-runs", owner, repo, *opt.CheckSuiteID)
149152
req, err := client.NewRequest("POST", u, opt)
150153
if err != nil {
151154
return nil, nil, err
@@ -168,47 +171,84 @@ func (c *Command) EnsureCheckRun(pre *github.PullRequestEvent) error {
168171
return err
169172
}
170173

171-
suite, err := c.EnsureCheckSuite(pre)
172-
if err != nil {
173-
return err
174-
}
174+
log.Printf("Running command: %q", c.cmd)
175175

176-
cr := Run{
177-
name: c.checkName,
178-
owner: suite.Repository.Owner.GetLogin(),
179-
repo: suite.Repository.GetName(),
180-
suiteId: suite.GetID(),
181-
}
176+
summary, text, runErr := c.runIt()
182177

183-
checkRunsList, _, err := client.Checks.ListCheckRunsCheckSuite(context.Background(), cr.owner, cr.repo, cr.suiteId, &github.ListCheckRunsOptions{
184-
CheckName: github.String(c.checkName),
185-
// TODO
186-
//ListOptions: github.ListOptions{},
187-
})
178+
owner := pre.Repo.Owner.GetLogin()
179+
repo := pre.Repo.GetName()
180+
181+
if c.checkName != "" {
182+
suite, err := c.EnsureCheckSuite(pre)
183+
if err != nil {
184+
return err
185+
}
188186

189-
var checkRun *github.CheckRun
190-
for _, existing := range checkRunsList.CheckRuns {
191-
if existing.GetName() == cr.name {
192-
checkRun = existing
187+
cr := Run{
188+
name: c.checkName,
189+
owner: owner,
190+
repo: repo,
191+
suiteId: suite.GetID(),
193192
}
194-
}
195193

196-
if checkRun == nil {
197-
log.Printf("Creating CheckRun %q", cr.name)
198-
created, err := c.createCheckRun(suite, cr)
199-
if err != nil {
194+
checkRunsList, _, err := client.Checks.ListCheckRunsCheckSuite(context.Background(), cr.owner, cr.repo, cr.suiteId, &github.ListCheckRunsOptions{
195+
CheckName: github.String(c.checkName),
196+
// TODO
197+
//ListOptions: github.ListOptions{},
198+
})
199+
200+
var checkRun *github.CheckRun
201+
for _, existing := range checkRunsList.CheckRuns {
202+
if existing.GetName() == cr.name {
203+
checkRun = existing
204+
}
205+
}
206+
207+
if checkRun == nil {
208+
log.Printf("Creating CheckRun %q", cr.name)
209+
created, err := c.createCheckRun(suite, cr)
210+
if err != nil {
211+
return err
212+
}
213+
checkRun = created
214+
}
215+
216+
c.logCheckRun(checkRun)
217+
218+
log.Printf("Updating CheckRun")
219+
if err := c.UpdateCheckRun(owner, repo, checkRun, summary, text, runErr); err != nil {
200220
return err
201221
}
202-
checkRun = created
203222
}
204223

205-
c.logCheckRun(checkRun)
206-
207-
log.Printf("Running the commmand for CheckRun %q", cr.name)
208-
summary, text, runErr := c.runIt()
224+
if c.statusContext != "" {
225+
sha := pre.PullRequest.Head.GetSHA()
226+
var state string
227+
if runErr != nil {
228+
state = "failure"
229+
} else {
230+
state = "success"
231+
}
232+
status := &github.RepoStatus{
233+
State: github.String(state),
234+
Context: github.String(c.statusContext),
235+
Description: github.String(text),
236+
}
237+
repoStatus, _, err := client.Repositories.CreateStatus(context.Background(), owner, repo, sha, status)
238+
if err != nil {
239+
log.Printf("Failed creating status: %v", err)
240+
} else {
241+
buf := bytes.Buffer{}
242+
enc := json.NewEncoder(&buf)
243+
enc.SetIndent("", " ")
244+
if err := enc.Encode(repoStatus); err != nil {
245+
return err
246+
}
247+
log.Printf("Created repo status:\n%s", buf.String())
248+
}
249+
}
209250

210-
log.Printf("Updating CheckRun")
211-
return c.UpdateCheckRun(cr.owner, cr.repo, checkRun, summary, text, runErr)
251+
return nil
212252
}
213253

214254
func (c *Command) logCheckRun(checkRun *github.CheckRun) {
@@ -421,21 +461,6 @@ func (c *Command) CreateCheckSuite(pre *github.PullRequestEvent) (*github.CheckS
421461
}
422462

423463
func (c *Command) instTokenClient() (*github.Client, error) {
424-
return instTokenClient(os.Getenv("GITHUB_TOKEN"), c.BaseURL, c.UploadURL)
464+
return actions.CreateInstallationTokenClient(os.Getenv("GITHUB_TOKEN"), c.BaseURL, c.UploadURL)
425465
}
426466

427-
// instTokenClient uses an installation token to authenticate to the Github API.
428-
func instTokenClient(instToken, baseURL, uploadURL string) (*github.Client, error) {
429-
// For installation tokens, Github uses a different token type ("token" instead of "bearer")
430-
tokenType := "token"
431-
if os.Getenv("GITHUB_TOKEN_TYPE") != "" {
432-
tokenType = os.Getenv("GITHUB_TOKEN_TYPE")
433-
}
434-
t := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: instToken, TokenType: tokenType})
435-
c := context.Background()
436-
tc := oauth2.NewClient(c, t)
437-
if baseURL != "" {
438-
return github.NewEnterpriseClient(baseURL, uploadURL, tc)
439-
}
440-
return github.NewClient(tc), nil
441-
}

pkg/pullvet/pullvet.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
package pullvet
22

33
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
47
"flag"
58
"fmt"
69
"github.com/google/go-github/v28/github"
710
"github.com/variantdev/go-actions"
811
"github.com/variantdev/go-actions/pkg/cmd"
12+
"log"
913
"os"
1014
"regexp"
1115
"strings"
1216
)
1317

1418
const defaultNoteRegex = "[\\*]*([^\\*:]+)[\\*]*:\\s```\n([^`]+)\n```"
1519

20+
var newlineRegex = regexp.MustCompile(`\r\n|\r|\n`)
21+
1622
type Command struct {
1723
labels cmd.StringSlice
1824
noteTitles cmd.StringSlice
@@ -26,6 +32,10 @@ type Command struct {
2632
requireAll bool
2733
}
2834

35+
func normalizeNewlines(str string) string {
36+
return newlineRegex.Copy().ReplaceAllString(str, "\n")
37+
}
38+
2939
func New() *Command {
3040
return &Command{
3141
}
@@ -42,14 +52,14 @@ func (c *Command) AddFlags(fs *flag.FlagSet) {
4252
}
4353

4454
func (c *Command) Run() error {
45-
pr, err := actions.PullRequest()
55+
pr, owner, repo, err := actions.PullRequest()
4656
if err != nil {
4757
return err
4858
}
49-
return c.HandlePullRequest(pr)
59+
return c.HandlePullRequest(owner, repo, pr)
5060
}
5161

52-
func (c *Command) HandlePullRequest(pullRequest *github.PullRequest) error {
62+
func (c *Command) HandlePullRequest(owner, repo string, pullRequest *github.PullRequest) error {
5363
var labels []string
5464
labelSet := map[string]struct{}{}
5565

@@ -99,11 +109,29 @@ func (c *Command) HandlePullRequest(pullRequest *github.PullRequest) error {
99109

100110
noteTitles := map[string]struct{}{}
101111

102-
body := pullRequest.GetBody()
112+
client, err := actions.CreateInstallationTokenClient(os.Getenv("GITHUB_TOKEN"), "", "")
113+
if err != nil {
114+
return err
115+
}
116+
117+
pr, _, err :=client.PullRequests.Get(context.Background(), owner, repo, pullRequest.GetNumber())
118+
if err != nil {
119+
return err
120+
}
121+
122+
buf := bytes.Buffer{}
123+
enc := json.NewEncoder(&buf)
124+
enc.SetIndent("", " ")
125+
if err := enc.Encode(pr); err != nil {
126+
return err
127+
}
128+
log.Printf("Pull request:\n%s", buf.String())
129+
130+
body := pr.GetBody()
103131

104132
regex := regexp.MustCompile(c.noteRegex)
105133

106-
allNoteMatches := regex.FindAllStringSubmatch(body, -1)
134+
allNoteMatches := regex.FindAllStringSubmatch(normalizeNewlines(body), -1)
107135
for _, m := range allNoteMatches {
108136
noteTitles[m[0]] = struct{}{}
109137
}

0 commit comments

Comments
 (0)