diff --git a/pkg/model/github_context.go b/pkg/model/github_context.go index 199a9c6..c68aaa1 100644 --- a/pkg/model/github_context.go +++ b/pkg/model/github_context.go @@ -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"` @@ -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 + } + } +} diff --git a/pkg/model/github_context_test.go b/pkg/model/github_context_test.go new file mode 100644 index 0000000..c3c8b56 --- /dev/null +++ b/pkg/model/github_context_test.go @@ -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) + }) +} diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index 5949dae..58cb2ac 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -542,13 +542,6 @@ 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 { @@ -556,32 +549,13 @@ func (rc *RunContext) getGithubContext() *model.GithubContext { } } - 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 } @@ -637,29 +611,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"