Skip to content

Commit

Permalink
feat: try to read ref and sha from event payload if available
Browse files Browse the repository at this point in the history
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 <philipp.hinrichsen@new-work.se>
  • Loading branch information
ZauberNerd and Poltergeist committed Jan 4, 2022
1 parent 4e6cddf commit 18c3c61
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 51 deletions.
113 changes: 113 additions & 0 deletions pkg/model/github_context.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package model

import (
"fmt"

"github.com/nektos/act/pkg/common"
log "github.com/sirupsen/logrus"
)

type GithubContext struct {
Event map[string]interface{} `json:"event"`
EventPath string `json:"event_path"`
Expand All @@ -26,3 +33,109 @@ type GithubContext struct {
RunnerPerflog string `json:"runner_perflog"`
RunnerTrackingID string `json:"runner_tracking_id"`
}

func asString(v interface{}) string {
if v == nil {
return ""
} else if s, ok := v.(string); ok {
return s
}
return ""
}

func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{}) {
var ok bool

if len(ks) == 0 { // degenerate input
return nil
}
if rval, ok = m[ks[0]]; !ok {
return nil
} else if len(ks) == 1 { // we've reached the final key
return rval
} else if m, ok = rval.(map[string]interface{}); !ok {
return nil
} else { // 1+ more keys
return nestedMapLookup(m, ks[1:]...)
}
}

func withDefaultBranch(b string, event map[string]interface{}) map[string]interface{} {
repoI, ok := event["repository"]
if !ok {
repoI = make(map[string]interface{})
}

repo, ok := repoI.(map[string]interface{})
if !ok {
log.Warnf("unable to set default branch to %v", b)
return event
}

// if the branch is already there return with no changes
if _, ok = repo["default_branch"]; ok {
return event
}

repo["default_branch"] = b
event["repository"] = repo

return event
}

var findGitRef = common.FindGitRef
var findGitRevision = common.FindGitRevision

func (ghc *GithubContext) SetRefAndSha(defaultBranch string, repoPath string) {
// https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows
// https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
switch ghc.EventName {
case "pull_request_target":
ghc.Ref = ghc.BaseRef
ghc.Sha = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "sha"))
case "pull_request", "pull_request_review", "pull_request_review_comment":
ghc.Ref = fmt.Sprintf("refs/pull/%s/merge", ghc.Event["number"])
case "deployment", "deployment_status":
ghc.Ref = asString(nestedMapLookup(ghc.Event, "deployment", "ref"))
ghc.Sha = asString(nestedMapLookup(ghc.Event, "deployment", "sha"))
case "release":
ghc.Ref = asString(nestedMapLookup(ghc.Event, "release", "tag_name"))
case "push", "create", "workflow_dispatch":
ghc.Ref = asString(ghc.Event["ref"])
if deleted, ok := ghc.Event["deleted"].(bool); ok && !deleted {
ghc.Sha = asString(ghc.Event["after"])
}
default:
ghc.Ref = asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))
}

if ghc.Ref == "" {
ref, err := findGitRef(repoPath)
if err != nil {
log.Warningf("unable to get git ref: %v", err)
} else {
log.Debugf("using github ref: %s", ref)
ghc.Ref = ref
}

// set the branch in the event data
if defaultBranch != "" {
ghc.Event = withDefaultBranch(defaultBranch, ghc.Event)
} else {
ghc.Event = withDefaultBranch("master", ghc.Event)
}

if ghc.Ref == "" {
ghc.Ref = asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))
}
}

if ghc.Sha == "" {
_, sha, err := findGitRevision(repoPath)
if err != nil {
log.Warningf("unable to get git revision: %v", err)
} else {
ghc.Sha = sha
}
}
}
132 changes: 132 additions & 0 deletions pkg/model/github_context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package model

import (
"fmt"
"testing"

log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestSetRefAndSha(t *testing.T) {
log.SetLevel(log.DebugLevel)

oldFindGitRef := findGitRef
oldFindGitRevision := findGitRevision
defer func() { findGitRef = oldFindGitRef }()
defer func() { findGitRevision = oldFindGitRevision }()

findGitRef = func(file string) (string, error) {
return "refs/heads/master", nil
}

findGitRevision = func(file string) (string, string, error) {
return "", "1234fakesha", nil
}

tables := []struct {
eventName string
event map[string]interface{}
ref string
sha string
}{
{
eventName: "pull_request_target",
event: map[string]interface{}{
"pull_request": map[string]interface{}{
"base": map[string]interface{}{
"sha": "pr-base-sha",
},
},
},
ref: "master",
sha: "pr-base-sha",
},
{
eventName: "pull_request",
event: map[string]interface{}{
"number": "1234",
},
ref: "refs/pull/1234/merge",
sha: "1234fakesha",
},
{
eventName: "deployment",
event: map[string]interface{}{
"deployment": map[string]interface{}{
"ref": "refs/heads/somebranch",
"sha": "deployment-sha",
},
},
ref: "refs/heads/somebranch",
sha: "deployment-sha",
},
{
eventName: "release",
event: map[string]interface{}{
"release": map[string]interface{}{
"tag_name": "v1.0.0",
},
},
ref: "v1.0.0",
sha: "1234fakesha",
},
{
eventName: "push",
event: map[string]interface{}{
"ref": "refs/heads/somebranch",
"after": "push-sha",
"deleted": false,
},
ref: "refs/heads/somebranch",
sha: "push-sha",
},
{
eventName: "unknown",
event: map[string]interface{}{
"repository": map[string]interface{}{
"default_branch": "main",
},
},
ref: "main",
sha: "1234fakesha",
},
{
eventName: "no-event",
event: map[string]interface{}{},
ref: "refs/heads/master",
sha: "1234fakesha",
},
}

for _, table := range tables {
t.Run(table.eventName, func(t *testing.T) {
ghc := &GithubContext{
EventName: table.eventName,
BaseRef: "master",
Event: table.event,
}

ghc.SetRefAndSha("main", "/some/dir")

assert.Equal(t, table.ref, ghc.Ref)
assert.Equal(t, table.sha, ghc.Sha)
})
}

t.Run("no-default-branch", func(t *testing.T) {
findGitRef = func(file string) (string, error) {
return "", fmt.Errorf("no default branch")
}

ghc := &GithubContext{
EventName: "no-default-branch",
Event: map[string]interface{}{},
}

ghc.SetRefAndSha("", "/some/dir")

assert.Equal(t, "master", ghc.Ref)
assert.Equal(t, "1234fakesha", ghc.Sha)
})
}
53 changes: 2 additions & 51 deletions pkg/runner/run_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,46 +564,20 @@ func (rc *RunContext) getGithubContext() *model.GithubContext {
}
}

_, sha, err := common.FindGitRevision(repoPath)
if err != nil {
log.Warningf("unable to get git revision: %v", err)
} else {
ghc.Sha = sha
}

if rc.EventJSON != "" {
err = json.Unmarshal([]byte(rc.EventJSON), &ghc.Event)
if err != nil {
log.Errorf("Unable to Unmarshal event '%s': %v", rc.EventJSON, err)
}
}

maybeRef := nestedMapLookup(ghc.Event, ghc.EventName, "ref")
if maybeRef != nil {
log.Debugf("using github ref from event: %s", maybeRef)
ghc.Ref = maybeRef.(string)
} else {
ref, err := common.FindGitRef(repoPath)
if err != nil {
log.Warningf("unable to get git ref: %v", err)
} else {
log.Debugf("using github ref: %s", ref)
ghc.Ref = ref
}

// set the branch in the event data
if rc.Config.DefaultBranch != "" {
ghc.Event = withDefaultBranch(rc.Config.DefaultBranch, ghc.Event)
} else {
ghc.Event = withDefaultBranch("master", ghc.Event)
}
}

if ghc.EventName == "pull_request" {
ghc.BaseRef = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "ref"))
ghc.HeadRef = asString(nestedMapLookup(ghc.Event, "pull_request", "head", "ref"))
}

ghc.SetRefAndSha(rc.Config.DefaultBranch, repoPath)

return ghc
}

Expand Down Expand Up @@ -659,29 +633,6 @@ func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{})
}
}

func withDefaultBranch(b string, event map[string]interface{}) map[string]interface{} {
repoI, ok := event["repository"]
if !ok {
repoI = make(map[string]interface{})
}

repo, ok := repoI.(map[string]interface{})
if !ok {
log.Warnf("unable to set default branch to %v", b)
return event
}

// if the branch is already there return with no changes
if _, ok = repo["default_branch"]; ok {
return event
}

repo["default_branch"] = b
event["repository"] = repo

return event
}

func (rc *RunContext) withGithubEnv(env map[string]string) map[string]string {
github := rc.getGithubContext()
env["CI"] = "true"
Expand Down

0 comments on commit 18c3c61

Please sign in to comment.