From ce168f959502feab3ec815810fac7a3b5a1f470c Mon Sep 17 00:00:00 2001 From: Shubh Bapna <38372682+shubhbapna@users.noreply.github.com> Date: Fri, 3 Feb 2023 14:35:49 -0500 Subject: [PATCH] feat: allow overriding of `GITHUB_` env variables (#1582) * allow overriding of GITHUB_ env variables * bug fix for overriding env vars with empty string * revert step.go * refactor github_context to prevent lint failures. added more setters * added ability to override github env variables * handled base and head ref --- pkg/model/github_context.go | 71 ++++++++++++++++-- pkg/model/github_context_test.go | 119 ++++++++++++++++++++++++------- pkg/runner/run_context.go | 83 +++++++++++---------- 3 files changed, 199 insertions(+), 74 deletions(-) diff --git a/pkg/model/github_context.go b/pkg/model/github_context.go index 86172df..9ce8d08 100644 --- a/pkg/model/github_context.go +++ b/pkg/model/github_context.go @@ -3,6 +3,7 @@ package model import ( "context" "fmt" + "strings" "github.com/nektos/act/pkg/common" "github.com/nektos/act/pkg/common/git" @@ -89,26 +90,22 @@ func withDefaultBranch(ctx context.Context, b string, event map[string]interface var findGitRef = git.FindGitRef var findGitRevision = git.FindGitRevision -func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string, repoPath string) { +func (ghc *GithubContext) SetRef(ctx context.Context, defaultBranch string, repoPath string) { logger := common.Logger(ctx) + // 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 = fmt.Sprintf("refs/heads/%s", 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/%.0f/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: defaultBranch := asString(nestedMapLookup(ghc.Event, "repository", "default_branch")) if defaultBranch != "" { @@ -136,6 +133,23 @@ func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string ghc.Ref = fmt.Sprintf("refs/heads/%s", asString(nestedMapLookup(ghc.Event, "repository", "default_branch"))) } } +} + +func (ghc *GithubContext) SetSha(ctx context.Context, repoPath string) { + logger := common.Logger(ctx) + + // 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.Sha = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "sha")) + case "deployment", "deployment_status": + ghc.Sha = asString(nestedMapLookup(ghc.Event, "deployment", "sha")) + case "push", "create", "workflow_dispatch": + if deleted, ok := ghc.Event["deleted"].(bool); ok && !deleted { + ghc.Sha = asString(ghc.Event["after"]) + } + } if ghc.Sha == "" { _, sha, err := findGitRevision(ctx, repoPath) @@ -146,3 +160,48 @@ func (ghc *GithubContext) SetRefAndSha(ctx context.Context, defaultBranch string } } } + +func (ghc *GithubContext) SetRepositoryAndOwner(ctx context.Context, githubInstance string, remoteName string, repoPath string) { + if ghc.Repository == "" { + repo, err := git.FindGithubRepo(ctx, repoPath, githubInstance, remoteName) + if err != nil { + common.Logger(ctx).Warningf("unable to get git repo: %v", err) + return + } + ghc.Repository = repo + } + ghc.RepositoryOwner = strings.Split(ghc.Repository, "/")[0] +} + +func (ghc *GithubContext) SetRefTypeAndName() { + var refType, refName string + + // https://docs.github.com/en/actions/learn-github-actions/environment-variables + if strings.HasPrefix(ghc.Ref, "refs/tags/") { + refType = "tag" + refName = ghc.Ref[len("refs/tags/"):] + } else if strings.HasPrefix(ghc.Ref, "refs/heads/") { + refType = "branch" + refName = ghc.Ref[len("refs/heads/"):] + } + + if ghc.RefType == "" { + ghc.RefType = refType + } + + if ghc.RefName == "" { + ghc.RefName = refName + } +} + +func (ghc *GithubContext) SetBaseAndHeadRef() { + if ghc.EventName == "pull_request" || ghc.EventName == "pull_request_target" { + if ghc.BaseRef == "" { + ghc.BaseRef = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "ref")) + } + + if ghc.HeadRef == "" { + ghc.HeadRef = asString(nestedMapLookup(ghc.Event, "pull_request", "head", "ref")) + } + } +} diff --git a/pkg/model/github_context_test.go b/pkg/model/github_context_test.go index a290094..29bb546 100644 --- a/pkg/model/github_context_test.go +++ b/pkg/model/github_context_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSetRefAndSha(t *testing.T) { +func TestSetRef(t *testing.T) { log.SetLevel(log.DebugLevel) oldFindGitRef := findGitRef @@ -29,19 +29,11 @@ func TestSetRefAndSha(t *testing.T) { 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: "refs/heads/master", - sha: "pr-base-sha", + event: map[string]interface{}{}, + ref: "refs/heads/master", }, { eventName: "pull_request", @@ -49,18 +41,15 @@ func TestSetRefAndSha(t *testing.T) { "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", @@ -70,17 +59,13 @@ func TestSetRefAndSha(t *testing.T) { }, }, 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", }, ref: "refs/heads/somebranch", - sha: "push-sha", }, { eventName: "unknown", @@ -90,13 +75,11 @@ func TestSetRefAndSha(t *testing.T) { }, }, ref: "refs/heads/main", - sha: "1234fakesha", }, { eventName: "no-event", event: map[string]interface{}{}, ref: "refs/heads/master", - sha: "1234fakesha", }, } @@ -108,10 +91,9 @@ func TestSetRefAndSha(t *testing.T) { Event: table.event, } - ghc.SetRefAndSha(context.Background(), "main", "/some/dir") + ghc.SetRef(context.Background(), "main", "/some/dir") assert.Equal(t, table.ref, ghc.Ref) - assert.Equal(t, table.sha, ghc.Sha) }) } @@ -125,9 +107,96 @@ func TestSetRefAndSha(t *testing.T) { Event: map[string]interface{}{}, } - ghc.SetRefAndSha(context.Background(), "", "/some/dir") + ghc.SetRef(context.Background(), "", "/some/dir") assert.Equal(t, "refs/heads/master", ghc.Ref) - assert.Equal(t, "1234fakesha", ghc.Sha) }) } + +func TestSetSha(t *testing.T) { + log.SetLevel(log.DebugLevel) + + oldFindGitRef := findGitRef + oldFindGitRevision := findGitRevision + defer func() { findGitRef = oldFindGitRef }() + defer func() { findGitRevision = oldFindGitRevision }() + + findGitRef = func(ctx context.Context, file string) (string, error) { + return "refs/heads/master", nil + } + + findGitRevision = func(ctx context.Context, file string) (string, string, error) { + return "", "1234fakesha", nil + } + + tables := []struct { + eventName string + event map[string]interface{} + sha string + }{ + { + eventName: "pull_request_target", + event: map[string]interface{}{ + "pull_request": map[string]interface{}{ + "base": map[string]interface{}{ + "sha": "pr-base-sha", + }, + }, + }, + sha: "pr-base-sha", + }, + { + eventName: "pull_request", + event: map[string]interface{}{ + "number": 1234., + }, + sha: "1234fakesha", + }, + { + eventName: "deployment", + event: map[string]interface{}{ + "deployment": map[string]interface{}{ + "sha": "deployment-sha", + }, + }, + sha: "deployment-sha", + }, + { + eventName: "release", + event: map[string]interface{}{}, + sha: "1234fakesha", + }, + { + eventName: "push", + event: map[string]interface{}{ + "after": "push-sha", + "deleted": false, + }, + sha: "push-sha", + }, + { + eventName: "unknown", + event: map[string]interface{}{}, + sha: "1234fakesha", + }, + { + eventName: "no-event", + event: map[string]interface{}{}, + 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.SetSha(context.Background(), "/some/dir") + + assert.Equal(t, table.sha, ghc.Sha) + }) + } +} diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index fa2da05..db28328 100644 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -21,7 +21,6 @@ import ( log "github.com/sirupsen/logrus" "github.com/nektos/act/pkg/common" - "github.com/nektos/act/pkg/common/git" "github.com/nektos/act/pkg/container" "github.com/nektos/act/pkg/exprparser" "github.com/nektos/act/pkg/model" @@ -571,6 +570,14 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext RetentionDays: rc.Config.Env["GITHUB_RETENTION_DAYS"], RunnerPerflog: rc.Config.Env["RUNNER_PERFLOG"], RunnerTrackingID: rc.Config.Env["RUNNER_TRACKING_ID"], + Repository: rc.Config.Env["GITHUB_REPOSITORY"], + Ref: rc.Config.Env["GITHUB_REF"], + Sha: rc.Config.Env["SHA_REF"], + RefName: rc.Config.Env["GITHUB_REF_NAME"], + RefType: rc.Config.Env["GITHUB_REF_TYPE"], + BaseRef: rc.Config.Env["GITHUB_BASE_REF"], + HeadRef: rc.Config.Env["GITHUB_HEAD_REF"], + Workspace: rc.Config.Env["GITHUB_WORKSPACE"], } if rc.JobContainer != nil { ghc.EventPath = rc.JobContainer.GetActPath() + "/workflow/event.json" @@ -599,39 +606,24 @@ func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext ghc.Actor = "nektos/act" } - repoPath := rc.Config.Workdir - repo, err := git.FindGithubRepo(ctx, repoPath, rc.Config.GitHubInstance, rc.Config.RemoteName) - if err != nil { - logger.Warningf("unable to get git repo: %v", err) - } else { - ghc.Repository = repo - if ghc.RepositoryOwner == "" { - ghc.RepositoryOwner = strings.Split(repo, "/")[0] - } - } - if rc.EventJSON != "" { - err = json.Unmarshal([]byte(rc.EventJSON), &ghc.Event) + err := json.Unmarshal([]byte(rc.EventJSON), &ghc.Event) if err != nil { logger.Errorf("Unable to Unmarshal event '%s': %v", rc.EventJSON, err) } } - if ghc.EventName == "pull_request" || ghc.EventName == "pull_request_target" { - ghc.BaseRef = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "ref")) - ghc.HeadRef = asString(nestedMapLookup(ghc.Event, "pull_request", "head", "ref")) + ghc.SetBaseAndHeadRef() + repoPath := rc.Config.Workdir + ghc.SetRepositoryAndOwner(ctx, rc.Config.GitHubInstance, rc.Config.RemoteName, repoPath) + if ghc.Ref == "" { + ghc.SetRef(ctx, rc.Config.DefaultBranch, repoPath) + } + if ghc.Sha == "" { + ghc.SetSha(ctx, repoPath) } - ghc.SetRefAndSha(ctx, rc.Config.DefaultBranch, repoPath) - - // https://docs.github.com/en/actions/learn-github-actions/environment-variables - if strings.HasPrefix(ghc.Ref, "refs/tags/") { - ghc.RefType = "tag" - ghc.RefName = ghc.Ref[len("refs/tags/"):] - } else if strings.HasPrefix(ghc.Ref, "refs/heads/") { - ghc.RefType = "branch" - ghc.RefName = ghc.Ref[len("refs/heads/"):] - } + ghc.SetRefTypeAndName() return ghc } @@ -662,15 +654,6 @@ func isLocalCheckout(ghc *model.GithubContext, step *model.Step) bool { return true } -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 @@ -709,20 +692,34 @@ func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubCon env["GITHUB_REF_NAME"] = github.RefName env["GITHUB_REF_TYPE"] = github.RefType env["GITHUB_TOKEN"] = github.Token - env["GITHUB_SERVER_URL"] = "https://github.com" - env["GITHUB_API_URL"] = "https://api.github.com" - env["GITHUB_GRAPHQL_URL"] = "https://api.github.com/graphql" - env["GITHUB_BASE_REF"] = github.BaseRef - env["GITHUB_HEAD_REF"] = github.HeadRef env["GITHUB_JOB"] = rc.JobName env["GITHUB_REPOSITORY_OWNER"] = github.RepositoryOwner env["GITHUB_RETENTION_DAYS"] = github.RetentionDays env["RUNNER_PERFLOG"] = github.RunnerPerflog env["RUNNER_TRACKING_ID"] = github.RunnerTrackingID + env["GITHUB_BASE_REF"] = github.BaseRef + env["GITHUB_HEAD_REF"] = github.HeadRef + + defaultServerURL := "https://github.com" + defaultAPIURL := "https://api.github.com" + defaultGraphqlURL := "https://api.github.com/graphql" + if rc.Config.GitHubInstance != "github.com" { - env["GITHUB_SERVER_URL"] = fmt.Sprintf("https://%s", rc.Config.GitHubInstance) - env["GITHUB_API_URL"] = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance) - env["GITHUB_GRAPHQL_URL"] = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance) + defaultServerURL = fmt.Sprintf("https://%s", rc.Config.GitHubInstance) + defaultAPIURL = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance) + defaultGraphqlURL = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance) + } + + if env["GITHUB_SERVER_URL"] == "" { + env["GITHUB_SERVER_URL"] = defaultServerURL + } + + if env["GITHUB_API_URL"] == "" { + env["GITHUB_API_URL"] = defaultAPIURL + } + + if env["GITHUB_GRAPHQL_URL"] == "" { + env["GITHUB_GRAPHQL_URL"] = defaultGraphqlURL } if rc.Config.ArtifactServerPath != "" {