Skip to content

Commit edd0fb9

Browse files
ZauberNerdPhilipp Hinrichsen
and
Philipp Hinrichsen
authored
feat: try to read ref and sha from event payload if available (#889)
With this change `act` will try to populate the `githubContext.ref` and `githubContext.sha` with values read from the event payload. Caveats: - `page_build` should not have a ref - `status` should not have a ref - `registry_package` should set the ref to the branch/tag but the payload isn't documented - `workflow_call` should set ref to the same value as its caller but the payload isn't documented - most of the events should set the sha to the last commit on the ref but unfortunately the sha is not always included in the payload, therefore we use the sha from the local git checkout Co-Authored-By: Philipp Hinrichsen <[email protected]> Co-authored-by: Philipp Hinrichsen <[email protected]>
1 parent 4be9062 commit edd0fb9

File tree

3 files changed

+247
-51
lines changed

3 files changed

+247
-51
lines changed

pkg/model/github_context.go

+113
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package model
22

3+
import (
4+
"fmt"
5+
6+
"github.com/nektos/act/pkg/common"
7+
log "github.com/sirupsen/logrus"
8+
)
9+
310
type GithubContext struct {
411
Event map[string]interface{} `json:"event"`
512
EventPath string `json:"event_path"`
@@ -26,3 +33,109 @@ type GithubContext struct {
2633
RunnerPerflog string `json:"runner_perflog"`
2734
RunnerTrackingID string `json:"runner_tracking_id"`
2835
}
36+
37+
func asString(v interface{}) string {
38+
if v == nil {
39+
return ""
40+
} else if s, ok := v.(string); ok {
41+
return s
42+
}
43+
return ""
44+
}
45+
46+
func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{}) {
47+
var ok bool
48+
49+
if len(ks) == 0 { // degenerate input
50+
return nil
51+
}
52+
if rval, ok = m[ks[0]]; !ok {
53+
return nil
54+
} else if len(ks) == 1 { // we've reached the final key
55+
return rval
56+
} else if m, ok = rval.(map[string]interface{}); !ok {
57+
return nil
58+
} else { // 1+ more keys
59+
return nestedMapLookup(m, ks[1:]...)
60+
}
61+
}
62+
63+
func withDefaultBranch(b string, event map[string]interface{}) map[string]interface{} {
64+
repoI, ok := event["repository"]
65+
if !ok {
66+
repoI = make(map[string]interface{})
67+
}
68+
69+
repo, ok := repoI.(map[string]interface{})
70+
if !ok {
71+
log.Warnf("unable to set default branch to %v", b)
72+
return event
73+
}
74+
75+
// if the branch is already there return with no changes
76+
if _, ok = repo["default_branch"]; ok {
77+
return event
78+
}
79+
80+
repo["default_branch"] = b
81+
event["repository"] = repo
82+
83+
return event
84+
}
85+
86+
var findGitRef = common.FindGitRef
87+
var findGitRevision = common.FindGitRevision
88+
89+
func (ghc *GithubContext) SetRefAndSha(defaultBranch string, repoPath string) {
90+
// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
91+
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
92+
switch ghc.EventName {
93+
case "pull_request_target":
94+
ghc.Ref = ghc.BaseRef
95+
ghc.Sha = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "sha"))
96+
case "pull_request", "pull_request_review", "pull_request_review_comment":
97+
ghc.Ref = fmt.Sprintf("refs/pull/%s/merge", ghc.Event["number"])
98+
case "deployment", "deployment_status":
99+
ghc.Ref = asString(nestedMapLookup(ghc.Event, "deployment", "ref"))
100+
ghc.Sha = asString(nestedMapLookup(ghc.Event, "deployment", "sha"))
101+
case "release":
102+
ghc.Ref = asString(nestedMapLookup(ghc.Event, "release", "tag_name"))
103+
case "push", "create", "workflow_dispatch":
104+
ghc.Ref = asString(ghc.Event["ref"])
105+
if deleted, ok := ghc.Event["deleted"].(bool); ok && !deleted {
106+
ghc.Sha = asString(ghc.Event["after"])
107+
}
108+
default:
109+
ghc.Ref = asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))
110+
}
111+
112+
if ghc.Ref == "" {
113+
ref, err := findGitRef(repoPath)
114+
if err != nil {
115+
log.Warningf("unable to get git ref: %v", err)
116+
} else {
117+
log.Debugf("using github ref: %s", ref)
118+
ghc.Ref = ref
119+
}
120+
121+
// set the branch in the event data
122+
if defaultBranch != "" {
123+
ghc.Event = withDefaultBranch(defaultBranch, ghc.Event)
124+
} else {
125+
ghc.Event = withDefaultBranch("master", ghc.Event)
126+
}
127+
128+
if ghc.Ref == "" {
129+
ghc.Ref = asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))
130+
}
131+
}
132+
133+
if ghc.Sha == "" {
134+
_, sha, err := findGitRevision(repoPath)
135+
if err != nil {
136+
log.Warningf("unable to get git revision: %v", err)
137+
} else {
138+
ghc.Sha = sha
139+
}
140+
}
141+
}

pkg/model/github_context_test.go

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package model
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
log "github.com/sirupsen/logrus"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestSetRefAndSha(t *testing.T) {
12+
log.SetLevel(log.DebugLevel)
13+
14+
oldFindGitRef := findGitRef
15+
oldFindGitRevision := findGitRevision
16+
defer func() { findGitRef = oldFindGitRef }()
17+
defer func() { findGitRevision = oldFindGitRevision }()
18+
19+
findGitRef = func(file string) (string, error) {
20+
return "refs/heads/master", nil
21+
}
22+
23+
findGitRevision = func(file string) (string, string, error) {
24+
return "", "1234fakesha", nil
25+
}
26+
27+
tables := []struct {
28+
eventName string
29+
event map[string]interface{}
30+
ref string
31+
sha string
32+
}{
33+
{
34+
eventName: "pull_request_target",
35+
event: map[string]interface{}{
36+
"pull_request": map[string]interface{}{
37+
"base": map[string]interface{}{
38+
"sha": "pr-base-sha",
39+
},
40+
},
41+
},
42+
ref: "master",
43+
sha: "pr-base-sha",
44+
},
45+
{
46+
eventName: "pull_request",
47+
event: map[string]interface{}{
48+
"number": "1234",
49+
},
50+
ref: "refs/pull/1234/merge",
51+
sha: "1234fakesha",
52+
},
53+
{
54+
eventName: "deployment",
55+
event: map[string]interface{}{
56+
"deployment": map[string]interface{}{
57+
"ref": "refs/heads/somebranch",
58+
"sha": "deployment-sha",
59+
},
60+
},
61+
ref: "refs/heads/somebranch",
62+
sha: "deployment-sha",
63+
},
64+
{
65+
eventName: "release",
66+
event: map[string]interface{}{
67+
"release": map[string]interface{}{
68+
"tag_name": "v1.0.0",
69+
},
70+
},
71+
ref: "v1.0.0",
72+
sha: "1234fakesha",
73+
},
74+
{
75+
eventName: "push",
76+
event: map[string]interface{}{
77+
"ref": "refs/heads/somebranch",
78+
"after": "push-sha",
79+
"deleted": false,
80+
},
81+
ref: "refs/heads/somebranch",
82+
sha: "push-sha",
83+
},
84+
{
85+
eventName: "unknown",
86+
event: map[string]interface{}{
87+
"repository": map[string]interface{}{
88+
"default_branch": "main",
89+
},
90+
},
91+
ref: "main",
92+
sha: "1234fakesha",
93+
},
94+
{
95+
eventName: "no-event",
96+
event: map[string]interface{}{},
97+
ref: "refs/heads/master",
98+
sha: "1234fakesha",
99+
},
100+
}
101+
102+
for _, table := range tables {
103+
t.Run(table.eventName, func(t *testing.T) {
104+
ghc := &GithubContext{
105+
EventName: table.eventName,
106+
BaseRef: "master",
107+
Event: table.event,
108+
}
109+
110+
ghc.SetRefAndSha("main", "/some/dir")
111+
112+
assert.Equal(t, table.ref, ghc.Ref)
113+
assert.Equal(t, table.sha, ghc.Sha)
114+
})
115+
}
116+
117+
t.Run("no-default-branch", func(t *testing.T) {
118+
findGitRef = func(file string) (string, error) {
119+
return "", fmt.Errorf("no default branch")
120+
}
121+
122+
ghc := &GithubContext{
123+
EventName: "no-default-branch",
124+
Event: map[string]interface{}{},
125+
}
126+
127+
ghc.SetRefAndSha("", "/some/dir")
128+
129+
assert.Equal(t, "master", ghc.Ref)
130+
assert.Equal(t, "1234fakesha", ghc.Sha)
131+
})
132+
}

pkg/runner/run_context.go

+2-51
Original file line numberDiff line numberDiff line change
@@ -542,46 +542,20 @@ func (rc *RunContext) getGithubContext() *model.GithubContext {
542542
}
543543
}
544544

545-
_, sha, err := common.FindGitRevision(repoPath)
546-
if err != nil {
547-
log.Warningf("unable to get git revision: %v", err)
548-
} else {
549-
ghc.Sha = sha
550-
}
551-
552545
if rc.EventJSON != "" {
553546
err = json.Unmarshal([]byte(rc.EventJSON), &ghc.Event)
554547
if err != nil {
555548
log.Errorf("Unable to Unmarshal event '%s': %v", rc.EventJSON, err)
556549
}
557550
}
558551

559-
maybeRef := nestedMapLookup(ghc.Event, ghc.EventName, "ref")
560-
if maybeRef != nil {
561-
log.Debugf("using github ref from event: %s", maybeRef)
562-
ghc.Ref = maybeRef.(string)
563-
} else {
564-
ref, err := common.FindGitRef(repoPath)
565-
if err != nil {
566-
log.Warningf("unable to get git ref: %v", err)
567-
} else {
568-
log.Debugf("using github ref: %s", ref)
569-
ghc.Ref = ref
570-
}
571-
572-
// set the branch in the event data
573-
if rc.Config.DefaultBranch != "" {
574-
ghc.Event = withDefaultBranch(rc.Config.DefaultBranch, ghc.Event)
575-
} else {
576-
ghc.Event = withDefaultBranch("master", ghc.Event)
577-
}
578-
}
579-
580552
if ghc.EventName == "pull_request" {
581553
ghc.BaseRef = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "ref"))
582554
ghc.HeadRef = asString(nestedMapLookup(ghc.Event, "pull_request", "head", "ref"))
583555
}
584556

557+
ghc.SetRefAndSha(rc.Config.DefaultBranch, repoPath)
558+
585559
return ghc
586560
}
587561

@@ -637,29 +611,6 @@ func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{})
637611
}
638612
}
639613

640-
func withDefaultBranch(b string, event map[string]interface{}) map[string]interface{} {
641-
repoI, ok := event["repository"]
642-
if !ok {
643-
repoI = make(map[string]interface{})
644-
}
645-
646-
repo, ok := repoI.(map[string]interface{})
647-
if !ok {
648-
log.Warnf("unable to set default branch to %v", b)
649-
return event
650-
}
651-
652-
// if the branch is already there return with no changes
653-
if _, ok = repo["default_branch"]; ok {
654-
return event
655-
}
656-
657-
repo["default_branch"] = b
658-
event["repository"] = repo
659-
660-
return event
661-
}
662-
663614
func (rc *RunContext) withGithubEnv(env map[string]string) map[string]string {
664615
github := rc.getGithubContext()
665616
env["CI"] = "true"

0 commit comments

Comments
 (0)