nektos / act

Run your GitHub Actions locally 🚀
https://nektosact.com
MIT License
54.06k stars 1.35k forks source link

Issue: Event type is not parsed from event file #671

Open catthehacker opened 3 years ago

catthehacker commented 3 years ago

Act version

https://github.com/nektos/act/commit/8153dc92e5c34c51d02777b8274f05c399d3befc

Expected behaviour

event type should be parsed from event file

Actual behaviour

it defaults to push

Workflow and/or repository

n/a

Steps to reproduce

go run main.go -e pkg/runner/testdata/pull-request/event.json -W pkg/runner/testdata/pull-request/main.yaml -v

act output

Log ```sh   ~/go/src/github.com/nektos/act cat/fix/event-json *4 ?1 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────  3s cat@alpine-vm 11:53:1 ❯ act -e pkg/runner/testdata/pull-request/event.json -W pkg/runner/testdata/pull-request/main.yaml -v DEBU[0000] Loading environment from /home/cat/go/src/github.com/nektos/act/.env DEBU[0000] Loading secrets from /home/cat/go/src/github.com/nektos/act/.secrets DEBU[0000] Loading workflow '/home/cat/go/src/github.com/nektos/act/pkg/runner/testdata/pull-request/main.yaml' DEBU[0000] Reading workflow '/home/cat/go/src/github.com/nektos/act/pkg/runner/testdata/pull-request/main.yaml' DEBU[0000] Planning event: push DEBU[0000] Reading event.json from /home/cat/go/src/github.com/nektos/act/pkg/runner/testdata/pull-request/event.json   ~/go/src/github.com/nektos/act cat/fix/event-json *4 ?1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── cat@alpine-vm 12:02:5 ❯ ```
catthehacker commented 3 years ago

This is some quick ugly fix that needs to be improved still. @cplee would you mind looking at this and give some opinion how (bad) that looks or what could be done better 😸

diff --git a/cmd/root.go b/cmd/root.go
index 13867b9..8d1a3a9 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -3,9 +3,13 @@ package cmd
 import (
        "bufio"
        "context"
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
        "os"
        "path/filepath"
        "regexp"
+       "sort"
        "strings"

        "github.com/nektos/act/pkg/common"
@@ -176,8 +180,9 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
                if input.autodetectEvent && len(events) > 0 {
                        // set default event type to first event
                        // this way user dont have to specify the event.
-                       log.Debugf("Using detected workflow event: %s", events[0])
                        eventName = events[0]
+               } else if input.eventPath != "" {
+                       eventName = parseEventFile(input.eventPath)
                } else {
                        if len(args) > 0 {
                                eventName = args[0]
@@ -185,6 +190,10 @@ func newRunCommand(ctx context.Context, input *Input) func(*cobra.Command, []str
                                eventName = "push"
                        }
                }
+               if res := sort.SearchStrings(model.Events, eventName); res == 0 {
+                       return fmt.Errorf("event type '%s' not found in a list of acceptable events", eventName)
+               }
+               log.Debugf("Using detected workflow event: %s", eventName)

                // build the plan for this run
                var plan *model.Plan
@@ -359,3 +368,144 @@ func watchAndRun(ctx context.Context, fn common.Executor) error {
        folderWatcher.Stop()
        return err
 }
+
+// parseEventFile returns proper event name based on content of event json file
+// nolint:gocyclo
+func parseEventFile(p string) string {
+       e := make(map[string]interface{})
+       eventJSONBytes, err := ioutil.ReadFile(p)
+       if err != nil {
+               log.Errorf("%v", err)
+               return ""
+       }
+
+       err = json.Unmarshal(eventJSONBytes, &e)
+       if err != nil {
+               log.Errorf("%v", err)
+               return ""
+       }
+
+       keys := make([]string, 0, len(e))
+       for k := range e {
+               keys = append(keys, k)
+       }
+       sort.Strings(keys)
+
+       // log.Debugf("json: %v", e)
+
+       if e["action"] == "revoked" {
+               return "github_app_authorization"
+       }
+       for _, k := range keys {
+               switch k {
+               case "check_run", "check_suite", "content_reference", "label", "marketplace_purchase", "milestone", "package",
+                       "project_card", "project_column", "project", "release", "security_advisory":
+                       return k
+               case "alert":
+                       if e["ref"] != nil {
+                               return "code_scanning_alert"
+                       }
+                       switch e["action"] {
+                       case "created", "resolved", "reopened":
+                               return "secret_scanning_alert"
+                       }
+                       return "repository_vulnerability_alert"
+               case "blocked_user":
+                       return "org_block"
+               case "comment":
+                       if e["pull_request"] == nil {
+                               if e["issue"] != nil {
+                                       return "issue_comment"
+                               }
+                               if e["discussion"] != nil {
+                                       return "discussion_comment"
+                               }
+                               return "commit_comment"
+                       }
+               case "client_payload":
+                       return "repository_dispatch"
+               case "deployment":
+                       if e["deployment_status"] != nil {
+                               return "deployment_status"
+                       }
+                       return "deployment"
+               case "member":
+                       if e["team"] != nil {
+                               return "membership"
+                       }
+                       return "member"
+               case "membership":
+                       return "organization"
+               case "ref":
+                       if e["ref_type"] != nil {
+                               if e["master_branch"] == nil {
+                                       return "delete"
+                               }
+                               return "create"
+                       }
+                       if e["workflow"] != nil {
+                               return "workflow_dispatch"
+                       }
+                       return "push"
+               case "key":
+                       return "deploy_key"
+               case "discussion":
+                       return k
+               case "forkee":
+                       return "fork"
+               case "pull_request":
+                       if e["comment"] != nil {
+                               return "pull_request_review_comment"
+                       }
+                       if e["review"] != nil {
+                               return "pull_request_review"
+                       }
+                       return k
+               case "pages":
+                       return "gollum"
+               case "hook_id":
+                       if e["zen"] != nil {
+                               return "ping"
+                       }
+                       return "meta"
+               case "issue":
+                       return "issues"
+               case "installation":
+                       if e["repository_selection"] != nil {
+                               return "installation_repositories"
+                       }
+                       return "installation"
+               case "build":
+                       return "page_build"
+               case "repository":
+                       if e["starred_at"] != nil {
+                               return "star"
+                       }
+                       if e["team"] != nil {
+                               if e["action"] != nil {
+                                       return "team"
+                               }
+                               return "team_add"
+                       }
+                       if e["state"] != nil {
+                               return "status"
+                       }
+                       if e["action"] != nil {
+                               if e["action"] == "started" {
+                                       return "watch"
+                               }
+                               if e["action"] == "requested" || e["action"] == "completed" {
+                                       return "workflow_run"
+                               }
+                               return "repository"
+                       }
+                       if e["status"] != nil {
+                               return "repository_import"
+                       }
+                       return "public"
+               case "sponsorship":
+                       return "sponsorship"
+               }
+       }
+       return ""
+}
diff --git a/cmd/root_test.go b/cmd/root_test.go
new file mode 100644
index 0000000..356dbfb
--- /dev/null
+++ b/cmd/root_test.go
@@ -0,0 +1,35 @@
+package cmd
+
+import (
+       "errors"
+       "io/ioutil"
+       "os"
+       "path/filepath"
+       "strings"
+       "testing"
+
+       "github.com/nektos/act/pkg/model"
+       log "github.com/sirupsen/logrus"
+       "gotest.tools/v3/assert"
+)
+
+func TestParseEventFile(t *testing.T) {
+       log.SetLevel(log.DebugLevel)
+       for _, event := range model.Events {
+               p := filepath.Join("..", "pkg", "runner", "testdata", "event-types", strings.ReplaceAll(event, `_`, `-`))
+               log.Debugf("Path: %s", p)
+
+               _, err := os.Lstat(p)
+               if errors.Is(err, os.ErrNotExist) {
+                       err = os.MkdirAll(p, 0766)
+                       assert.NilError(t, err, "")
+
+                       err = ioutil.WriteFile(filepath.Join(p, "event.json"), []byte{}, 0600)
+                       assert.NilError(t, err, "")
+               } else {
+                       eventName := parseEventFile(filepath.Join(p, "event.json"))
+                       log.Debugf("event: %s, eventName: %s", event, eventName)
+                       assert.Equal(t, eventName, event)
+               }
+       }
+}
diff --git a/pkg/model/planner.go b/pkg/model/planner.go
index 3c2a660..42b5f57 100644
--- a/pkg/model/planner.go
+++ b/pkg/model/planner.go
@@ -14,6 +14,17 @@ import (
        log "github.com/sirupsen/logrus"
 )

+// Events contains all allowed event names
+var Events = []string{
+       "check_run", "check_suite", "code_scanning_alert", "commit_comment", "content_reference", "create", "delete", "deploy_key",
+       "deployment", "deployment_status", "discussion", "discussion_comment", "fork", "github_app_authorization", "gollum", "installation",
+       "installation_repositories", "issue_comment", "issues", "label", "marketplace_purchase", "member", "membership", "meta", "milestone",
+       "organization", "org_block", "package", "page_build", "ping", "project_card", "project_column", "project", "public", "pull_request",
+       "pull_request_review", "pull_request_review_comment", "push", "release", "repository_dispatch", "repository", "repository_import",
+       "repository_vulnerability_alert", "secret_scanning_alert", "security_advisory", "sponsorship", "star", "status", "team", "team_add",
+       "watch", "workflow_dispatch", "workflow_run",
+}
+
 // WorkflowPlanner contains methods for creating plans
 type WorkflowPlanner interface {
        PlanEvent(eventName string) *Plan
diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go
index 612f473..bfa2e6c 100755
--- a/pkg/runner/run_context.go
+++ b/pkg/runner/run_context.go
@@ -524,32 +524,33 @@ func (rc *RunContext) getGithubContext() *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)
+       switch ghc.EventName {
+       case "pull_request":
+               ghc.BaseRef = asString(nestedMapLookup(ghc.Event, "pull_request", "base", "ref"))
+               ghc.HeadRef = asString(nestedMapLookup(ghc.Event, "pull_request", "head", "ref"))
+       default:
+               maybeRef := nestedMapLookup(ghc.Event, ghc.EventName, "ref")
+               if maybeRef != nil {
+                       log.Debugf("using github ref from event: %s", maybeRef)
+                       ghc.Ref = maybeRef.(string)
                } else {
-                       log.Debugf("using github ref: %s", ref)
-                       ghc.Ref = ref
-               }
+                       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)
+                       // 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"))
-       }
-
        return ghc
 }

diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go
index 9262b96..7a49031 100644
--- a/pkg/runner/runner.go
+++ b/pkg/runner/runner.go
@@ -100,6 +100,7 @@ func New(runnerConfig *Config) (Runner, error) {
                        return nil, err
                }
                runner.eventJSON = string(eventJSONBytes)
+               log.Debugf("Event JSON: '%s'", runner.eventJSON)
        }
        return runner, nil
 }
diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go
index 89e1061..304bafd 100644
--- a/pkg/runner/runner_test.go
+++ b/pkg/runner/runner_test.go
@@ -173,32 +173,34 @@ func TestRunEventPullRequest(t *testing.T) {
        ctx := context.Background()

        platforms := map[string]string{
-               "ubuntu-latest": "node:12.20.1-buster-slim",
+               "ubuntu-latest": "node:12.20-buster-slim",
        }

-       workflowPath := "pull-request"
-       eventName := "pull_request"
+       for _, event := range model.Events {
+               workflowPath := strings.ReplaceAll(event, `_`, `-`)
+               eventName := event

-       workdir, err := filepath.Abs("testdata")
-       assert.NilError(t, err, workflowPath)
+               workdir, err := filepath.Abs("testdata")
+               assert.NilError(t, err, workflowPath)

-       runnerConfig := &Config{
-               Workdir:         workdir,
-               EventName:       eventName,
-               EventPath:       filepath.Join(workdir, workflowPath, "event.json"),
-               Platforms:       platforms,
-               ReuseContainers: false,
-       }
-       runner, err := New(runnerConfig)
-       assert.NilError(t, err, workflowPath)
+               runnerConfig := &Config{
+                       Workdir:         workdir,
+                       EventName:       eventName,
+                       EventPath:       filepath.Join(workdir, workflowPath, "event.json"),
+                       Platforms:       platforms,
+                       ReuseContainers: false,
+               }
+               runner, err := New(runnerConfig)
+               assert.NilError(t, err, workflowPath)

-       planner, err := model.NewWorkflowPlanner(fmt.Sprintf("testdata/%s", workflowPath), true)
-       assert.NilError(t, err, workflowPath)
+               planner, err := model.NewWorkflowPlanner(filepath.Join("testdata", "event-types", workflowPath), true)
+               assert.NilError(t, err, workflowPath)

-       plan := planner.PlanEvent(eventName)
+               plan := planner.PlanEvent(eventName)

-       err = runner.NewPlanExecutor(plan)(ctx)
-       assert.NilError(t, err, workflowPath)
+               err = runner.NewPlanExecutor(plan)(ctx)
+               assert.NilError(t, err, workflowPath)
+       }
 }

 func TestContainerPath(t *testing.T) {

branch: https://github.com/catthehacker/act-fork/tree/cat/fix%2Fevent-json

github-actions[bot] commented 3 years ago

Issue is stale and will be closed in 14 days unless there is new activity

dimaqq commented 1 year ago

Might this also be fixed by #1428?

Dr-Electron commented 1 month ago

Not sure it is.

I have this simple workflow-test.yml:

on:  
  pull_request:
    types: [ready_for_review]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Test
        run: echo test

and the following event.json:

{
    "action": "opened",
    "pull_request": {
        "head": {
            "ref": "sample-head-ref"
        },
        "base": {
            "ref": "main"
        }
    }
}

And if I run:

act pull_request -W ./.github/workflows/workflow-testyml -e event.json

it runs, although it shouldn't as the type is opened not ready_for_review