From 0cd4e5e1a6bf18ceee32b0a046371c2a56d61ffe Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 17 May 2022 17:37:53 -0400 Subject: [PATCH] Add support for getting the GitHub context (#44) * github context * switch to parse env via external dep * fix missing env * Add support for getting the GitHub context Co-authored-by: Bernardo Vale --- actions.go | 63 ++++++++++++++++++++++++++++ actions_root.go | 4 ++ actions_test.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 5 +++ go.sum | 6 +++ 5 files changed, 187 insertions(+) create mode 100644 go.sum diff --git a/actions.go b/actions.go index f6711b5..9bf4ebc 100644 --- a/actions.go +++ b/actions.go @@ -29,6 +29,8 @@ import ( "os" "strings" "time" + + "github.com/sethvargo/go-envconfig" ) var ( @@ -449,3 +451,64 @@ func (c *Action) Getenv(key string) string { // GetenvFunc is an abstraction to make tests feasible for commands that // interact with environment variables. type GetenvFunc func(key string) string + +// GitHubContext of current workflow. +// +// Replicated from https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts +type GitHubContext struct { + EventPath string `env:"GITHUB_EVENT_PATH"` + EventName string `env:"GITHUB_EVENT_NAME"` + SHA string `env:"GITHUB_SHA"` + Ref string `env:"GITHUB_REF"` + Workflow string `env:"GITHUB_WORKFLOW"` + Action string `env:"GITHUB_ACTION"` + Actor string `env:"GITHUB_ACTOR"` + Job string `env:"GITHUB_JOB"` + RunNumber int64 `env:"GITHUB_RUN_NUMBER"` + RunID int64 `env:"GITHUB_RUN_ID"` + APIURL string `env:"GITHUB_API_URL,default=https://api.github.com"` + ServerURL string `env:"GITHUB_SERVER_URL,default=https://github.com"` + GraphqlURL string `env:"GITHUB_GRAPHQL_URL,default=https://api.github.com/graphql"` + + // Event is populated by parsing the file at EventPath, if it exists. + Event map[string]any +} + +// Context returns the context of current action with the payload object +// that triggered the workflow +func (c *Action) Context() (*GitHubContext, error) { + ctx := context.Background() + lookuper := &wrappedLookuper{f: c.getenv} + + var githubContext GitHubContext + if err := envconfig.ProcessWith(ctx, &githubContext, lookuper); err != nil { + return nil, fmt.Errorf("could not process github context variables: %w", err) + } + + if githubContext.EventPath != "" { + eventData, err := os.ReadFile(githubContext.EventPath) + if err != nil && !os.IsNotExist(err) { + return nil, fmt.Errorf("could not read event file: %w", err) + } + if eventData != nil { + if err := json.Unmarshal(eventData, &githubContext.Event); err != nil { + return nil, fmt.Errorf("failed to unmarshal event payload: %w", err) + } + } + } + + return &githubContext, nil +} + +// wrappedLookuper creates a lookuper that wraps a given getenv func. +type wrappedLookuper struct { + f GetenvFunc +} + +// Lookup implements a custom lookuper. +func (w *wrappedLookuper) Lookup(key string) (string, bool) { + if v := w.f(key); v != "" { + return v, true + } + return "", false +} diff --git a/actions_root.go b/actions_root.go index a252e1e..f6c81be 100644 --- a/actions_root.go +++ b/actions_root.go @@ -144,3 +144,7 @@ func WithFieldsMap(m map[string]string) *Action { func GetIDToken(ctx context.Context, audience string) (string, error) { return defaultAction.GetIDToken(ctx, audience) } + +func Context() (*GitHubContext, error) { + return defaultAction.Context() +} diff --git a/actions_test.go b/actions_test.go index cb0ca24..1cc56a7 100644 --- a/actions_test.go +++ b/actions_test.go @@ -25,6 +25,8 @@ import ( "reflect" "strings" "testing" + + "github.com/google/go-cmp/cmp" ) func TestNew(t *testing.T) { @@ -591,6 +593,113 @@ func TestAction_GetIDToken(t *testing.T) { } } +func TestAction_Context(t *testing.T) { + t.Parallel() + + f, err := os.CreateTemp("", "") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + os.Remove(f.Name()) + }) + + if _, err := f.Write([]byte(`{"foo": "bar"}`)); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + + eventPayloadPath := f.Name() + + cases := []struct { + name string + env map[string]string + exp *GitHubContext + }{ + { + name: "empty", + env: nil, + exp: &GitHubContext{ + // Defaults + APIURL: "https://api.github.com", + ServerURL: "https://github.com", + GraphqlURL: "https://api.github.com/graphql", + }, + }, + { + name: "no_payload", + env: map[string]string{ + "GITHUB_EVENT_NAME": "event_name", + "GITHUB_SHA": "abcd1234", + "GITHUB_REF": "main", + "GITHUB_WORKFLOW": "test", + "GITHUB_ACTION": "foo/bar@v0", + "GITHUB_ACTOR": "sethvargo", + "GITHUB_JOB": "12", + "GITHUB_RUN_NUMBER": "34", + "GITHUB_RUN_ID": "56", + "GITHUB_API_URL": "https://foo.com", + "GITHUB_SERVER_URL": "https://bar.com", + "GITHUB_GRAPHQL_URL": "https://baz.com", + }, + exp: &GitHubContext{ + EventName: "event_name", + SHA: "abcd1234", + Ref: "main", + Workflow: "test", + Action: "foo/bar@v0", + Actor: "sethvargo", + Job: "12", + RunNumber: 34, + RunID: 56, + APIURL: "https://foo.com", + ServerURL: "https://bar.com", + GraphqlURL: "https://baz.com", + }, + }, + { + name: "payload", + env: map[string]string{ + "GITHUB_EVENT_PATH": eventPayloadPath, + }, + exp: &GitHubContext{ + EventPath: eventPayloadPath, + + // Defaults + APIURL: "https://api.github.com", + ServerURL: "https://github.com", + GraphqlURL: "https://api.github.com/graphql", + + Event: map[string]any{ + "foo": "bar", + }, + }, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + a := New(WithGetenv(func(k string) string { + return tc.env[k] + })) + got, err := a.Context() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.exp, got); diff != "" { + t.Fatalf("mismatch (-want, +got):\n%s", diff) + } + }) + } +} + // newFakeGetenvFunc returns a new GetenvFunc that is expected to be called with // the provided key. It returns the provided value if the call matches the // provided key. It reports an error on test t otherwise. diff --git a/go.mod b/go.mod index 77a201f..ef95dbc 100644 --- a/go.mod +++ b/go.mod @@ -15,3 +15,8 @@ module github.com/sethvargo/go-githubactions go 1.18 + +require ( + github.com/google/go-cmp v0.4.1 + github.com/sethvargo/go-envconfig v0.6.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..11ac198 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/sethvargo/go-envconfig v0.6.0 h1:GxxdoeiNpWgGiVEphNFNObgMYRN/ZvI2dN7rBwadyss= +github.com/sethvargo/go-envconfig v0.6.0/go.mod h1:00S1FAhRUuTNJazWBWcJGvEHOM+NO6DhoRMAOX7FY5o= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=