feat: add initial helper methods
This commit is contained in:
parent
81d80fcced
commit
db684e73f8
7 changed files with 1174 additions and 0 deletions
261
action.go
Normal file
261
action.go
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
addMaskCmd = "add-mask"
|
||||||
|
|
||||||
|
envCmd = "env"
|
||||||
|
outputCmd = "output"
|
||||||
|
pathCmd = "path"
|
||||||
|
stateCmd = "state"
|
||||||
|
|
||||||
|
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
||||||
|
multiLineFileDelim = "234baa68-d26f-4bf9-996d-45ec3520cb95"
|
||||||
|
multilineFileCmd = "%s<<" + multiLineFileDelim + "\n%s\n" + multiLineFileDelim // ${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}
|
||||||
|
|
||||||
|
addMatcherCmd = "add-matcher"
|
||||||
|
removeMatcherCmd = "remove-matcher"
|
||||||
|
|
||||||
|
groupCmd = "group"
|
||||||
|
endGroupCmd = "endgroup"
|
||||||
|
|
||||||
|
stepSummaryCmd = "step-summary"
|
||||||
|
|
||||||
|
debugCmd = "debug"
|
||||||
|
|
||||||
|
noticeCmd = "notice"
|
||||||
|
warningCmd = "warning"
|
||||||
|
errorCmd = "error"
|
||||||
|
|
||||||
|
errFileCmdFmt = "unable to write command to the environment file: %s"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Action struct {
|
||||||
|
w io.Writer
|
||||||
|
env func(string) string
|
||||||
|
fields CommandProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Action {
|
||||||
|
return &Action{w: os.Stdout, env: os.Getenv}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFieldsSlice includes the provided fields in log output. "f" must be a
|
||||||
|
// slice of k=v pairs. The given slice will be sorted. It panics if any of the
|
||||||
|
// string in the given slice does not construct a valid 'key=value' pair.
|
||||||
|
func (c *Action) WithFieldsSlice(f ...string) *Action {
|
||||||
|
m := make(CommandProperties)
|
||||||
|
for _, s := range f {
|
||||||
|
pair := strings.SplitN(s, "=", 2)
|
||||||
|
if len(pair) < 2 {
|
||||||
|
panic(fmt.Sprintf("%q is not a proper k=v pair!", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
m[pair[0]] = pair[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.WithFieldsMap(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFieldsMap includes the provided fields in log output. The fields in "m"
|
||||||
|
// are automatically converted to k=v pairs and sorted.
|
||||||
|
func (c *Action) WithFieldsMap(m map[string]string) *Action {
|
||||||
|
return &Action{
|
||||||
|
w: c.w,
|
||||||
|
fields: m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInput gets the input by the given name. It returns the empty string if the
|
||||||
|
// input is not defined.
|
||||||
|
func (c *Action) GetInput(i string) string {
|
||||||
|
e := strings.ReplaceAll(i, " ", "_")
|
||||||
|
e = strings.ToUpper(e)
|
||||||
|
e = "INPUT_" + e
|
||||||
|
return strings.TrimSpace(c.env(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueCommand issues a new GitHub actions Command.
|
||||||
|
// It panics if it cannot write to the output stream.
|
||||||
|
func (c *Action) IssueCommand(cmd *Command) {
|
||||||
|
if _, err := fmt.Fprintln(c.w, cmd.String()); err != nil {
|
||||||
|
panic(fmt.Errorf("failed to issue command: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueFileCommand issues a new GitHub actions Command using environment files.
|
||||||
|
// It panics if writing to the file fails.
|
||||||
|
func (c *Action) IssueFileCommand(cmd *Command) {
|
||||||
|
e := strings.ReplaceAll(cmd.Name, "-", "_")
|
||||||
|
e = strings.ToUpper(e)
|
||||||
|
e = "GITHUB_" + e
|
||||||
|
|
||||||
|
filepath := c.env(e)
|
||||||
|
msg := []byte(cmd.Message)
|
||||||
|
f, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf(errFileCmdFmt, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := f.Write(msg); err != nil {
|
||||||
|
panic(fmt.Errorf(errFileCmdFmt, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMask adds a new field mask for the given string "p". After called, future
|
||||||
|
// attempts to log "p" will be replaced with "***" in log output. It panics if
|
||||||
|
// it cannot write to the output stream.
|
||||||
|
func (c *Action) AddMask(p string) {
|
||||||
|
// ::add-mask::<p>
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: addMaskCmd,
|
||||||
|
Message: p,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMatcher adds a new matcher with the given file path. It panics if it
|
||||||
|
// cannot write to the output stream.
|
||||||
|
func (c *Action) AddMatcher(p string) {
|
||||||
|
// ::add-matcher::<p>
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: addMatcherCmd,
|
||||||
|
Message: p,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveMatcher removes a matcher with the given owner name. It panics if it
|
||||||
|
// cannot write to the output stream.
|
||||||
|
func (c *Action) RemoveMatcher(o string) {
|
||||||
|
// ::remove-matcher owner=<o>::
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: removeMatcherCmd,
|
||||||
|
Properties: CommandProperties{
|
||||||
|
"owner": o,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group starts a new collapsable region up to the next ungroup invocation. It
|
||||||
|
// panics if it cannot write to the output stream.
|
||||||
|
func (c *Action) Group(t string) {
|
||||||
|
// ::group::<t>
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: groupCmd,
|
||||||
|
Message: t,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndGroup ends the current group. It panics if it cannot write to the output
|
||||||
|
// stream.
|
||||||
|
func (c *Action) EndGroup() {
|
||||||
|
// ::endgroup::
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: endGroupCmd,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf prints a debug-level message. It follows the standard fmt.Printf
|
||||||
|
// arguments, appending an OS-specific line break to the end of the message.
|
||||||
|
// It panics if it cannot write to the output stream.
|
||||||
|
func (c *Action) Debugf(msg string, args ...any) {
|
||||||
|
// ::debug <c.fields>::<msg, args>
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: debugCmd,
|
||||||
|
Message: fmt.Sprintf(msg, args...),
|
||||||
|
Properties: c.fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Noticef prints a notice-level message. It follows the standard fmt.Printf
|
||||||
|
// arguments, appending an OS-specific line break to the end of the message.
|
||||||
|
// It panics if it cannot write to the output stream.
|
||||||
|
func (c *Action) Noticef(msg string, args ...any) {
|
||||||
|
// ::notice <c.fields>::<msg, args>
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: noticeCmd,
|
||||||
|
Message: fmt.Sprintf(msg, args...),
|
||||||
|
Properties: c.fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf prints a warning-level message. It follows the standard fmt.Printf
|
||||||
|
// arguments, appending an OS-specific line break to the end of the message.
|
||||||
|
// It panics if it cannot write to the output stream.
|
||||||
|
func (c *Action) Warningf(msg string, args ...any) {
|
||||||
|
// ::warning <c.fields>::<msg, args>
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: warningCmd,
|
||||||
|
Message: fmt.Sprintf(msg, args...),
|
||||||
|
Properties: c.fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf prints a error-level message. It follows the standard fmt.Printf
|
||||||
|
// arguments, appending an OS-specific line break to the end of the message.
|
||||||
|
// It panics if it cannot write to the output stream.
|
||||||
|
func (c *Action) Errorf(msg string, args ...any) {
|
||||||
|
// ::error <c.fields>::<msg, args>
|
||||||
|
c.IssueCommand(&Command{
|
||||||
|
Name: errorCmd,
|
||||||
|
Message: fmt.Sprintf(msg, args...),
|
||||||
|
Properties: c.fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPath adds the string "p" to the path for the invocation.
|
||||||
|
// It panics if it cannot write to the output file.
|
||||||
|
func (c *Action) AddPath(p string) {
|
||||||
|
c.IssueFileCommand(&Command{
|
||||||
|
Name: pathCmd,
|
||||||
|
Message: p,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveState saves state to be used in the "finally" post job entry point.
|
||||||
|
// It panics if it cannot write to the output stream.
|
||||||
|
func (c *Action) SaveState(k, v string) {
|
||||||
|
c.IssueFileCommand(&Command{
|
||||||
|
Name: stateCmd,
|
||||||
|
Message: fmt.Sprintf(multilineFileCmd, k, v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStepSummary writes the given markdown to the job summary. If a job summary
|
||||||
|
// already exists, this value is appended.
|
||||||
|
// It panics if it cannot write to the output file.
|
||||||
|
func (c *Action) AddStepSummary(markdown string) {
|
||||||
|
c.IssueFileCommand(&Command{
|
||||||
|
Name: stepSummaryCmd,
|
||||||
|
Message: markdown,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnv sets an environment variable.
|
||||||
|
// It panics if it cannot write to the output file.
|
||||||
|
func (c *Action) SetEnv(k, v string) {
|
||||||
|
c.IssueFileCommand(&Command{
|
||||||
|
Name: envCmd,
|
||||||
|
Message: fmt.Sprintf(multilineFileCmd, k, v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets an output parameter.
|
||||||
|
// It panics if it cannot write to the output file.
|
||||||
|
func (c *Action) SetOutput(k, v string) {
|
||||||
|
c.IssueFileCommand(&Command{
|
||||||
|
Name: outputCmd,
|
||||||
|
Message: fmt.Sprintf(multilineFileCmd, k, v),
|
||||||
|
})
|
||||||
|
}
|
443
action_test.go
Normal file
443
action_test.go
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newFakeGetenvFunc(t *testing.T, wantKey, v string) func(string) string {
|
||||||
|
return func(gotKey string) string {
|
||||||
|
if gotKey != wantKey {
|
||||||
|
t.Errorf("expected call GetenvFunc(%q) to be GetenvFunc(%q)", gotKey, wantKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_WithFieldsSlice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a = a.WithFieldsSlice("line=100", "file=app.js")
|
||||||
|
a.Debugf("fail: %s", "thing")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::debug file=app.js,line=100::fail: thing"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_WithFieldsSlice_Panic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
want := `"no-equals" is not a proper k=v pair!`
|
||||||
|
if got := recover(); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a = a.WithFieldsSlice("no-equals")
|
||||||
|
a.Debugf("fail: %s", "thing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_WithFieldsMap(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a = a.WithFieldsMap(map[string]string{"line": "100", "file": "app.js"})
|
||||||
|
a.Debugf("fail: %s", "thing")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::debug file=app.js,line=100::fail: thing"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_GetInput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
a.env = newFakeGetenvFunc(t, "INPUT_FOO", "bar")
|
||||||
|
|
||||||
|
if got, want := a.GetInput("foo"), "bar"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_IssueCommand(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.IssueCommand(&Command{
|
||||||
|
Name: "foo",
|
||||||
|
Message: "bar",
|
||||||
|
})
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::foo::bar"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_IssueFileCommand(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
file, err := os.CreateTemp("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create a temp env file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
a.env = newFakeGetenvFunc(t, "GITHUB_FOO", file.Name())
|
||||||
|
|
||||||
|
a.IssueFileCommand(&Command{
|
||||||
|
Name: "foo",
|
||||||
|
Message: "bar",
|
||||||
|
})
|
||||||
|
|
||||||
|
// expect an empty stdout buffer
|
||||||
|
if got, want := b.String(), ""; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect the message to be written to the env file
|
||||||
|
data, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to read temp env file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := string(data), "bar"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_AddMask(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.AddMask("foobar")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::add-mask::foobar"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_AddMatcher(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.AddMatcher("foobar.json")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::add-matcher::foobar.json"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_RemoveMatcher(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.RemoveMatcher("foobar")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::remove-matcher owner=foobar::"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_Group(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.Group("mygroup")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::group::mygroup"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_EndGroup(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.EndGroup()
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::endgroup::"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_Debugf(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.Debugf("fail: %s", "thing")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::debug::fail: thing"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_Noticef(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.Noticef("fail: %s", "thing")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::notice::fail: thing"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_Warningf(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.Warningf("fail: %s", "thing")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::warning::fail: thing"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_Errorf(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
|
||||||
|
a.Errorf("fail: %s", "thing")
|
||||||
|
|
||||||
|
if got, want := b.String(), fmt.Sprintln("::error::fail: thing"); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_AddPath(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// expect a file command to be issued when env file is set.
|
||||||
|
file, err := os.CreateTemp("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create a temp env file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
a.env = newFakeGetenvFunc(t, "GITHUB_PATH", file.Name())
|
||||||
|
|
||||||
|
a.AddPath("/custom/bin")
|
||||||
|
|
||||||
|
if got, want := b.String(), ""; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect an empty stdout buffer
|
||||||
|
if got, want := b.String(), ""; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect the message to be written to the file.
|
||||||
|
data, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to read temp env file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := string(data), "/custom/bin"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_SaveState(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
file, err := os.CreateTemp("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create a temp env file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
a.env = newFakeGetenvFunc(t, "GITHUB_STATE", file.Name())
|
||||||
|
|
||||||
|
a.SaveState("key", "value")
|
||||||
|
a.SaveState("key2", "value2")
|
||||||
|
|
||||||
|
// expect an empty stdout buffer
|
||||||
|
if got, want := b.String(), ""; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect the command to be written to the file.
|
||||||
|
data, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to read temp env file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := fmt.Sprintf("key<<%s\nvalue\n%s", multiLineFileDelim, multiLineFileDelim)
|
||||||
|
want += fmt.Sprintf("key2<<%s\nvalue2\n%s", multiLineFileDelim, multiLineFileDelim)
|
||||||
|
if got := string(data); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_AddStepSummary(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// expectations for env file env commands
|
||||||
|
var b bytes.Buffer
|
||||||
|
file, err := os.CreateTemp("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create a temp env file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
a.env = newFakeGetenvFunc(t, "GITHUB_STEP_SUMMARY", file.Name())
|
||||||
|
|
||||||
|
a.AddStepSummary(`
|
||||||
|
## This is
|
||||||
|
|
||||||
|
some markdown
|
||||||
|
`)
|
||||||
|
a.AddStepSummary(`
|
||||||
|
- content
|
||||||
|
`)
|
||||||
|
|
||||||
|
// expect an empty stdout buffer
|
||||||
|
if got, want := b.String(), ""; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect the command to be written to the file.
|
||||||
|
data, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to read temp summary file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := "\n## This is\n\nsome markdown\n\n- content\n"
|
||||||
|
if got := string(data); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_SetEnv(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// expectations for env file env commands
|
||||||
|
var b bytes.Buffer
|
||||||
|
file, err := os.CreateTemp("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create a temp env file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
a.env = newFakeGetenvFunc(t, "GITHUB_ENV", file.Name())
|
||||||
|
|
||||||
|
a.SetEnv("key", "value")
|
||||||
|
a.SetEnv("key2", "value2")
|
||||||
|
|
||||||
|
// expect an empty stdout buffer
|
||||||
|
if got, want := b.String(), ""; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect the command to be written to the file.
|
||||||
|
data, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to read temp env file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := fmt.Sprintf("key<<%s\nvalue\n%s", multiLineFileDelim, multiLineFileDelim)
|
||||||
|
want += fmt.Sprintf("key2<<%s\nvalue2\n%s", multiLineFileDelim, multiLineFileDelim)
|
||||||
|
if got := string(data); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAction_SetOutput(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// expectations for env file env commands
|
||||||
|
var b bytes.Buffer
|
||||||
|
file, err := os.CreateTemp("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create a temp env file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
a := New()
|
||||||
|
a.w = &b
|
||||||
|
a.env = newFakeGetenvFunc(t, "GITHUB_OUTPUT", file.Name())
|
||||||
|
|
||||||
|
a.SetOutput("key", "value")
|
||||||
|
a.SetOutput("key2", "value2")
|
||||||
|
|
||||||
|
// expect an empty stdout buffer
|
||||||
|
if got, want := b.String(), ""; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect the command to be written to the file.
|
||||||
|
data, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to read temp env file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := fmt.Sprintf("key<<%s\nvalue\n%s", multiLineFileDelim, multiLineFileDelim)
|
||||||
|
want += fmt.Sprintf("key2<<%s\nvalue2\n%s", multiLineFileDelim, multiLineFileDelim)
|
||||||
|
if got := string(data); got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
9
cmd/main.go
Normal file
9
cmd/main.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "git.geekeey.de/actions/sdk"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
a := sdk.New()
|
||||||
|
a.AddMask("hello")
|
||||||
|
a.WithFieldsSlice("foo=bar", "biz=baz").Debugf("hello world")
|
||||||
|
}
|
83
command.go
Normal file
83
command.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdSeparator = "::"
|
||||||
|
cmdPropertiesPrefix = " "
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommandProperties is a named "map[string]string" type to hold key-value pairs
|
||||||
|
// passed to an actions command.
|
||||||
|
type CommandProperties map[string]string
|
||||||
|
|
||||||
|
// String encodes the CommandProperties to a string as comma separated
|
||||||
|
// 'key=value' pairs. The pairs are joined in a chronological order.
|
||||||
|
func (props *CommandProperties) String() string {
|
||||||
|
l := make([]string, 0, len(*props))
|
||||||
|
for k, v := range *props {
|
||||||
|
l = append(l, fmt.Sprintf("%s=%s", k, escapeProperty(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(l)
|
||||||
|
return strings.Join(l, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command can be issued by a GitHub action by writing to `stdout` with
|
||||||
|
// following format.
|
||||||
|
//
|
||||||
|
// ::name key=value,key=value::message
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// ::warning::This is the message
|
||||||
|
// ::set-env name=MY_VAR::some value
|
||||||
|
type Command struct {
|
||||||
|
Name string
|
||||||
|
Message string
|
||||||
|
Properties CommandProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes the Command to a string in the following format:
|
||||||
|
//
|
||||||
|
// ::name key=value,key=value::message
|
||||||
|
func (cmd *Command) String() string {
|
||||||
|
if cmd.Name == "" {
|
||||||
|
cmd.Name = "missing.command"
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.WriteString(cmdSeparator)
|
||||||
|
builder.WriteString(cmd.Name)
|
||||||
|
if len(cmd.Properties) > 0 {
|
||||||
|
builder.WriteString(cmdPropertiesPrefix)
|
||||||
|
builder.WriteString(cmd.Properties.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.WriteString(cmdSeparator)
|
||||||
|
builder.WriteString(escapeData(cmd.Message))
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeData escapes string values for presentation in the output of a command.
|
||||||
|
// This is a not-so-well-documented requirement of commands that define a message.
|
||||||
|
func escapeData(v string) string {
|
||||||
|
v = strings.ReplaceAll(v, "%", "%25")
|
||||||
|
v = strings.ReplaceAll(v, "\r", "%0D")
|
||||||
|
v = strings.ReplaceAll(v, "\n", "%0A")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeData escapes command property values for presentation in the output of
|
||||||
|
// a command.
|
||||||
|
func escapeProperty(v string) string {
|
||||||
|
v = strings.ReplaceAll(v, "%", "%25")
|
||||||
|
v = strings.ReplaceAll(v, "\r", "%0D")
|
||||||
|
v = strings.ReplaceAll(v, "\n", "%0A")
|
||||||
|
v = strings.ReplaceAll(v, ":", "%3A")
|
||||||
|
v = strings.ReplaceAll(v, ",", "%2C")
|
||||||
|
return v
|
||||||
|
}
|
45
command_test.go
Normal file
45
command_test.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCommandProperties_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
props := CommandProperties{"hello": "world"}
|
||||||
|
if got, want := props.String(), "hello=world"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
props["foo"] = "bar"
|
||||||
|
if got, want := props.String(), "foo=bar,hello=world"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommand_String(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cmd := Command{Name: "foo"}
|
||||||
|
if got, want := cmd.String(), "::foo::"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = Command{Name: "foo", Message: "bar"}
|
||||||
|
if got, want := cmd.String(), "::foo::bar"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = Command{
|
||||||
|
Name: "foo",
|
||||||
|
Message: "bar",
|
||||||
|
Properties: CommandProperties{"bar": "foo"},
|
||||||
|
}
|
||||||
|
if got, want := cmd.String(), "::foo bar=foo::bar"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = Command{Message: "quux"}
|
||||||
|
if got, want := cmd.String(), "::missing.command::quux"; got != want {
|
||||||
|
t.Errorf("expected %q to be %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
189
context.go
Normal file
189
context.go
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitHubContext of current workflow.
|
||||||
|
// See: https://docs.github.com/en/actions/learn-github-actions/environment-variables
|
||||||
|
type GitHubContext struct {
|
||||||
|
Action string `env:"GITHUB_ACTION"`
|
||||||
|
ActionPath string `env:"GITHUB_ACTION_PATH"`
|
||||||
|
ActionRepository string `env:"GITHUB_ACTION_REPOSITORY"`
|
||||||
|
Actions bool `env:"GITHUB_ACTIONS"`
|
||||||
|
Actor string `env:"GITHUB_ACTOR"`
|
||||||
|
APIURL string `env:"GITHUB_API_URL,default=https://api.github.com"`
|
||||||
|
BaseRef string `env:"GITHUB_BASE_REF"`
|
||||||
|
Env string `env:"GITHUB_ENV"`
|
||||||
|
EventName string `env:"GITHUB_EVENT_NAME"`
|
||||||
|
EventPath string `env:"GITHUB_EVENT_PATH"`
|
||||||
|
GraphqlURL string `env:"GITHUB_GRAPHQL_URL,default=https://api.github.com/graphql"`
|
||||||
|
HeadRef string `env:"GITHUB_HEAD_REF"`
|
||||||
|
Job string `env:"GITHUB_JOB"`
|
||||||
|
Path string `env:"GITHUB_PATH"`
|
||||||
|
Ref string `env:"GITHUB_REF"`
|
||||||
|
RefName string `env:"GITHUB_REF_NAME"`
|
||||||
|
RefProtected bool `env:"GITHUB_REF_PROTECTED"`
|
||||||
|
RefType string `env:"GITHUB_REF_TYPE"`
|
||||||
|
|
||||||
|
Repository string `env:"GITHUB_REPOSITORY"`
|
||||||
|
RepositoryOwner string `env:"GITHUB_REPOSITORY_OWNER"`
|
||||||
|
|
||||||
|
RetentionDays int64 `env:"GITHUB_RETENTION_DAYS"`
|
||||||
|
RunAttempt int64 `env:"GITHUB_RUN_ATTEMPT"`
|
||||||
|
RunID int64 `env:"GITHUB_RUN_ID"`
|
||||||
|
RunNumber int64 `env:"GITHUB_RUN_NUMBER"`
|
||||||
|
ServerURL string `env:"GITHUB_SERVER_URL,default=https://github.com"`
|
||||||
|
SHA string `env:"GITHUB_SHA"`
|
||||||
|
StepSummary string `env:"GITHUB_STEP_SUMMARY"`
|
||||||
|
Workflow string `env:"GITHUB_WORKFLOW"`
|
||||||
|
Workspace string `env:"GITHUB_WORKSPACE"`
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var merr error
|
||||||
|
context := &GitHubContext{
|
||||||
|
APIURL: "https://api.github.com",
|
||||||
|
GraphqlURL: "https://api.github.com/graphql",
|
||||||
|
ServerURL: "https://github.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := c.env("GITHUB_ACTION"); v != "" {
|
||||||
|
context.Action = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_ACTION_PATH"); v != "" {
|
||||||
|
context.ActionPath = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_ACTION_REPOSITORY"); v != "" {
|
||||||
|
context.ActionRepository = v
|
||||||
|
}
|
||||||
|
if v, err := parseBool(c.env("GITHUB_ACTIONS")); err == nil {
|
||||||
|
context.Actions = v
|
||||||
|
} else {
|
||||||
|
merr = errors.Join(merr, err)
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_ACTOR"); v != "" {
|
||||||
|
context.Actor = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_API_URL"); v != "" {
|
||||||
|
context.APIURL = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_BASE_REF"); v != "" {
|
||||||
|
context.BaseRef = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_ENV"); v != "" {
|
||||||
|
context.Env = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_EVENT_NAME"); v != "" {
|
||||||
|
context.EventName = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_EVENT_PATH"); v != "" {
|
||||||
|
context.EventPath = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_GRAPHQL_URL"); v != "" {
|
||||||
|
context.GraphqlURL = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_HEAD_REF"); v != "" {
|
||||||
|
context.HeadRef = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_JOB"); v != "" {
|
||||||
|
context.Job = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_PATH"); v != "" {
|
||||||
|
context.Path = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_REF"); v != "" {
|
||||||
|
context.Ref = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_REF_NAME"); v != "" {
|
||||||
|
context.RefName = v
|
||||||
|
}
|
||||||
|
if v, err := parseBool(c.env("GITHUB_REF_PROTECTED")); err == nil {
|
||||||
|
context.RefProtected = v
|
||||||
|
} else {
|
||||||
|
merr = errors.Join(merr, err)
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_REF_TYPE"); v != "" {
|
||||||
|
context.RefType = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := c.env("GITHUB_REPOSITORY"); v != "" {
|
||||||
|
context.Repository = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_REPOSITORY_OWNER"); v != "" {
|
||||||
|
context.RepositoryOwner = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := parseInt(c.env("GITHUB_RETENTION_DAYS")); err == nil {
|
||||||
|
context.RetentionDays = v
|
||||||
|
} else {
|
||||||
|
merr = errors.Join(merr, err)
|
||||||
|
}
|
||||||
|
if v, err := parseInt(c.env("GITHUB_RUN_ATTEMPT")); err == nil {
|
||||||
|
context.RunAttempt = v
|
||||||
|
} else {
|
||||||
|
merr = errors.Join(merr, err)
|
||||||
|
}
|
||||||
|
if v, err := parseInt(c.env("GITHUB_RUN_ID")); err == nil {
|
||||||
|
context.RunID = v
|
||||||
|
} else {
|
||||||
|
merr = errors.Join(merr, err)
|
||||||
|
}
|
||||||
|
if v, err := parseInt(c.env("GITHUB_RUN_NUMBER")); err == nil {
|
||||||
|
context.RunNumber = v
|
||||||
|
} else {
|
||||||
|
merr = errors.Join(merr, err)
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_SERVER_URL"); v != "" {
|
||||||
|
context.ServerURL = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_SHA"); v != "" {
|
||||||
|
context.SHA = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_STEP_SUMMARY"); v != "" {
|
||||||
|
context.StepSummary = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_WORKFLOW"); v != "" {
|
||||||
|
context.Workflow = v
|
||||||
|
}
|
||||||
|
if v := c.env("GITHUB_WORKSPACE"); v != "" {
|
||||||
|
context.Workspace = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.EventPath != "" {
|
||||||
|
eventData, err := os.ReadFile(context.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, &context.Event); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal event payload: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context, merr
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBool(v string) (bool, error) {
|
||||||
|
if v == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return strconv.ParseBool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(v string) (int64, error) {
|
||||||
|
if v == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return strconv.ParseInt(v, 10, 64)
|
||||||
|
}
|
144
context_test.go
Normal file
144
context_test.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
package sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
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_ACTION": "__repo-owner_name-of-action-repo",
|
||||||
|
"GITHUB_ACTION_PATH": "/path/to/action",
|
||||||
|
"GITHUB_ACTION_REPOSITORY": "repo-owner/name-of-action-repo",
|
||||||
|
"GITHUB_ACTIONS": "true",
|
||||||
|
"GITHUB_ACTOR": "sethvargo",
|
||||||
|
"GITHUB_API_URL": "https://foo.com",
|
||||||
|
"GITHUB_BASE_REF": "main",
|
||||||
|
"GITHUB_ENV": "/path/to/env",
|
||||||
|
"GITHUB_EVENT_NAME": "event_name",
|
||||||
|
"GITHUB_HEAD_REF": "headbranch",
|
||||||
|
"GITHUB_GRAPHQL_URL": "https://baz.com",
|
||||||
|
"GITHUB_JOB": "12",
|
||||||
|
"GITHUB_PATH": "/path/to/path",
|
||||||
|
"GITHUB_REF": "refs/tags/v1.0",
|
||||||
|
"GITHUB_REF_NAME": "v1.0",
|
||||||
|
"GITHUB_REF_PROTECTED": "true",
|
||||||
|
"GITHUB_REF_TYPE": "tag",
|
||||||
|
"GITHUB_REPOSITORY": "sethvargo/baz",
|
||||||
|
"GITHUB_REPOSITORY_OWNER": "sethvargo",
|
||||||
|
"GITHUB_RETENTION_DAYS": "90",
|
||||||
|
"GITHUB_RUN_ATTEMPT": "6",
|
||||||
|
"GITHUB_RUN_ID": "56",
|
||||||
|
"GITHUB_RUN_NUMBER": "34",
|
||||||
|
"GITHUB_SERVER_URL": "https://bar.com",
|
||||||
|
"GITHUB_SHA": "abcd1234",
|
||||||
|
"GITHUB_STEP_SUMMARY": "/path/to/summary",
|
||||||
|
"GITHUB_WORKFLOW": "test",
|
||||||
|
"GITHUB_WORKSPACE": "/path/to/workspace",
|
||||||
|
},
|
||||||
|
exp: &GitHubContext{
|
||||||
|
Action: "__repo-owner_name-of-action-repo",
|
||||||
|
ActionPath: "/path/to/action",
|
||||||
|
ActionRepository: "repo-owner/name-of-action-repo",
|
||||||
|
Actions: true,
|
||||||
|
Actor: "sethvargo",
|
||||||
|
APIURL: "https://foo.com",
|
||||||
|
BaseRef: "main",
|
||||||
|
Env: "/path/to/env",
|
||||||
|
EventName: "event_name",
|
||||||
|
// NOTE: No EventPath
|
||||||
|
GraphqlURL: "https://baz.com",
|
||||||
|
Job: "12",
|
||||||
|
HeadRef: "headbranch",
|
||||||
|
Path: "/path/to/path",
|
||||||
|
Ref: "refs/tags/v1.0",
|
||||||
|
RefName: "v1.0",
|
||||||
|
RefProtected: true,
|
||||||
|
RefType: "tag",
|
||||||
|
Repository: "sethvargo/baz",
|
||||||
|
RepositoryOwner: "sethvargo",
|
||||||
|
RetentionDays: 90,
|
||||||
|
RunAttempt: 6,
|
||||||
|
RunID: 56,
|
||||||
|
RunNumber: 34,
|
||||||
|
ServerURL: "https://bar.com",
|
||||||
|
SHA: "abcd1234",
|
||||||
|
StepSummary: "/path/to/summary",
|
||||||
|
Workflow: "test",
|
||||||
|
Workspace: "/path/to/workspace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
a.env = func(s string) string { return tc.env[s] }
|
||||||
|
got, err := a.Context()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, tc.exp) {
|
||||||
|
t.Errorf("expected\n\n%#v\n\nto be\n\n%#v\n", got, tc.exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue