Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update set outputs to latest specs #66

Merged
merged 1 commit into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
id: gofmt
run: |
gofmt -w .
echo ::set-output name=formatted_files::$(git status -s -uno | wc -l)
echo "formatted_files=$(git status -s -uno | wc -l)" >> $GITHUB_OUTPUT

- name: push formatting fixes
if: steps.gofmt.outputs.formatted_files > 0
Expand Down Expand Up @@ -62,8 +62,16 @@ jobs:
run: go mod download

- name: run tests
if: ${{ matrix.os }} != "windows-latest"
id: tests
env:
# unauthenticated calls have lower limits than authenticated ones
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: go test -v -race ${{ matrix.coverageArgs }} ./...

- name: run tests with inputs
env:
ACTIONS_OUTPUT_SET: "true"
# unauthenticated calls have lower limits than authenticated ones
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
my_output: "${{ steps.tests.outputs.my-output }}"
run: go test -v -run TestOutputTasks ./core/...
4 changes: 2 additions & 2 deletions core/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func IssueCommand(kind string, properties map[string]string, message string) {
// issueFileCommand implements stores the command in a file
// see https://github.com/actions/toolkit/pull/571/files#diff-9ce6eb99f5fb5529e795254801e03ae56d67d3d5fcbec635f91e9a8a61ad8b64R10
func issueFileCommand(command string, message string) error {
path, ok := os.LookupEnv("GITHUB_" + command)
path, ok := os.LookupEnv(command)
if ok {
fd, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
Expand All @@ -57,7 +57,7 @@ func issueFileCommand(command string, message string) error {
fmt.Fprintln(fd, message)
return nil
}
return fmt.Errorf("unable to find command file GITHUB_%s", command)
return fmt.Errorf("unable to find command file %s", command)
}

type command struct {
Expand Down
48 changes: 43 additions & 5 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,60 @@ package core

import (
"fmt"
"io"
"os"
"strings"
"sync"
)

const (
delimiter = "_GitHubActionsGoFileCommandDelimeter_"

// StatusFailed is returned by Status() in case this action has been marked as failed
StatusFailed = 1
// StatusSuccess is returned by Status() in case this action has not been marked as failed. By default an action is claimed as successful
StatusSuccess = 0

GitHubOutputFilePathEnvName = "GITHUB_OUTPUT"
GitHubStateFilePathEnvName = "GITHUB_STATE"
GitHubExportEnvFilePathEnvName = "GITHUB_ENV"
GitHubPathFilePathEnvName = "GITHUB_PATH"
)

var (
status = StatusSuccess
statusAccess = &sync.Mutex{}
lookupEnv = os.LookupEnv
open = func(path string, flag int, perm os.FileMode) (File, error) {
fd, err := os.OpenFile(path, flag, perm)
if err != nil {
return nil, err
}
return fd, nil
}
)

type File interface {
io.Reader
io.Writer
io.Closer
}

func formatOutput(name, value string) string {
return strings.Join(
[]string{
fmt.Sprintf("%s<<%s", name, delimiter),
value,
delimiter,
"",
},
EOF,
)
}

// ExportVariable sets the environment varaible name (for this action and future actions)
func ExportVariable(name, value string) {
const delimiter = "_GitHubActionsFileCommandDelimeter_"
if err := issueFileCommand("ENV", fmt.Sprintf("%s<<%s%s%s%s%s", name, delimiter, EOF, value, delimiter, EOF)); err != nil {
if err := issueFileCommand(GitHubExportEnvFilePathEnvName, formatOutput(name, value)); err != nil {
IssueCommand("set-env", map[string]string{"name": name}, value)
}
os.Setenv(name, value)
Expand All @@ -36,7 +68,7 @@ func SetSecret(secret string) {

// AddPath prepends inputPath to the PATH (for this action and future actions)
func AddPath(path string) {
if err := issueFileCommand("PATH", path); err != nil {
if err := issueFileCommand(GitHubPathFilePathEnvName, path); err != nil {
Issue("add-path", path)
}
// TODO js: process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`
Expand Down Expand Up @@ -65,7 +97,10 @@ func GetInputOrDefault(name, dflt string) string {

// SetOutput sets the value of an output for future actions
func SetOutput(name, value string) {
IssueCommand("set-output", map[string]string{"name": name}, value)
if err := issueFileCommand(GitHubOutputFilePathEnvName, formatOutput(name, value)); err != nil {
Warningf("did not find output file from environment variable %s, falling back to the deprecated command implementation", GitHubOutputFilePathEnvName)
IssueCommand("set-output", map[string]string{"name": name}, value)
}
}

// SetFailedf sets the action status to failed and sets an error message
Expand Down Expand Up @@ -142,7 +177,10 @@ func Group(name string, f func()) func() {

// SaveState saves state for current action, the state can only be retrieved by this action's post job execution.
func SaveState(name, value string) {
IssueCommand("save-state", map[string]string{"name": name}, value)
if err := issueFileCommand(GitHubStateFilePathEnvName, formatOutput(name, value)); err != nil {
Warningf("did not find state file from environment variable %s, falling back to the deprecated command implementation", GitHubStateFilePathEnvName)
IssueCommand("save-state", map[string]string{"name": name}, value)
}
}

// GetState gets the value of an state set by this action's main execution.
Expand Down
22 changes: 22 additions & 0 deletions core/core_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
package core

import (
"os"
"runtime"
"testing"

"github.com/stretchr/testify/assert"
)

func TestFormatOutput(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("This test only runs on unix with \\n line separator")
}
assert.Equal(t, "my-name<<_GitHubActionsGoFileCommandDelimeter_\nmy-value\n_GitHubActionsGoFileCommandDelimeter_\n", formatOutput("my-name", "my-value"))
}

func TestOutputTasks(t *testing.T) {
if _, ok := os.LookupEnv("ACTIONS_OUTPUT_SET"); ok {
// state is only available in pre and post actions:
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
// assert.Equal(t, "my-state-value", GetState("my-state"))
assert.Equal(t, "my-output-value", os.Getenv("my_output"))
assert.Equal(t, "my-env-value", os.Getenv("my_env"))
}
SaveState("my-state", "my-state-value")
ExportVariable("my_env", "my-env-value")
SetOutput("my-output", "my-output-value")
}

func TestGetInput(t *testing.T) {
t.Run("when environment variable is not net", func(t *testing.T) {
lookupEnv = func(name string) (string, bool) {
Expand Down
39 changes: 23 additions & 16 deletions github/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"strings"

"github.com/actions-go/toolkit/core"
"github.com/google/go-github/v42/github"
)

Expand Down Expand Up @@ -79,15 +80,18 @@ type ActionRepo struct {

// ActionContext contains details on the workflow execution
type ActionContext struct {
Payload WebhookPayload
EventName string
SHA string
Ref string
Workflow string
Action string
Actor string
Issue ActionIssue
Repo ActionRepo
Payload WebhookPayload
EventName string
SHA string
Ref string
Workflow string
Action string
Actor string
Issue ActionIssue
Repo ActionRepo
OutputFilePath string
StateFilePath string
ExportEnvFilePath string
}

func noGitHubEvent(path string) {
Expand All @@ -109,13 +113,16 @@ func ParseActionEnv() ActionContext {
Repo: getIndex(r, 1),
}
ctx := ActionContext{
EventName: os.Getenv("GITHUB_EVENT_NAME"),
SHA: os.Getenv("GITHUB_SHA"),
Ref: os.Getenv("GITHUB_REF"),
Workflow: os.Getenv("GITHUB_WORKFLOW"),
Action: os.Getenv("GITHUB_ACTION"),
Actor: os.Getenv("GITHUB_ACTOR"),
Repo: repo,
EventName: os.Getenv("GITHUB_EVENT_NAME"),
SHA: os.Getenv("GITHUB_SHA"),
Ref: os.Getenv("GITHUB_REF"),
Workflow: os.Getenv("GITHUB_WORKFLOW"),
Action: os.Getenv("GITHUB_ACTION"),
Actor: os.Getenv("GITHUB_ACTOR"),
OutputFilePath: os.Getenv(core.GitHubOutputFilePathEnvName),
StateFilePath: os.Getenv(core.GitHubStateFilePathEnvName),
ExportEnvFilePath: os.Getenv(core.GitHubExportEnvFilePathEnvName),
Repo: repo,
Issue: ActionIssue{
Owner: repo.Owner,
Repo: repo.Repo,
Expand Down
Loading