diff --git a/action.yaml b/action.yaml index 9998363..a61c3c9 100644 --- a/action.yaml +++ b/action.yaml @@ -41,6 +41,13 @@ inputs: description: Whether or not to fail the workflow if the approval is denied required: false default: 'true' +outputs: + issue-number: + description: The number of the issue created + issue-url: + description: The URL of the issue created + approval-status: + description: The status of the approval ("approved" or "denied") runs: using: docker image: docker://ghcr.io/trstringer/manual-approval:1.9.1 diff --git a/approval.go b/approval.go index 1ed12d9..9516b64 100644 --- a/approval.go +++ b/approval.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "os" "regexp" "strings" @@ -112,6 +113,44 @@ func (a *approvalEnvironment) createApprovalIssue(ctx context.Context) error { return nil } +func (a *approvalEnvironment) SetActionOutputs(outputs map[string]string) (bool, error) { + outputFile := os.Getenv("GITHUB_OUTPUT") + if outputFile == "" { + return false, nil + } + + f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + + if err != nil { + return false, err + } + defer f.Close() + + var pairs []string + + for key, value := range outputs { + pairs = append(pairs, fmt.Sprintf("%s=%s", key, value)) + } + + // Add a newline before writing the new outputs if the file is not empty. This prevents + // two outputs from being written on the same line. + fileInfo, err := f.Stat() + if err != nil { + return false, err + } + if fileInfo.Size() > 0 { + if _, err := f.WriteString("\n"); err != nil { + return false, err + } + } + + if _, err := f.WriteString(strings.Join(pairs, "\n")); err != nil { + return false, err + } + + return true, nil +} + func approvalFromComments(comments []*github.IssueComment, approvers []string, minimumApprovals int) (approvalStatus, error) { remainingApprovers := make([]string, len(approvers)) copy(remainingApprovers, approvers) diff --git a/approval_test.go b/approval_test.go index afc215f..cefe1b0 100644 --- a/approval_test.go +++ b/approval_test.go @@ -1,6 +1,8 @@ package main import ( + "errors" + "os" "testing" "github.com/google/go-github/v43/github" @@ -427,3 +429,60 @@ func TestDeniedCommentBody(t *testing.T) { }) } } + +func TestSaveOutput(t *testing.T) { + testCases := []struct { + name string + approvalIssueNumber int + env_github_output string + isSuccess bool + }{ + { + name: "save_output_with_env", + approvalIssueNumber: 123, + env_github_output: "./output.txt", + isSuccess: true, + }, + { + name: "fail_save_output_without_env", + approvalIssueNumber: 123, + env_github_output: "", + isSuccess: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + os.Setenv("GITHUB_OUTPUT", testCase.env_github_output) + a := approvalEnvironment{ + client: nil, + repoFullName: "", + repo: "", + repoOwner: "", + runID: -1, + approvalIssueNumber: testCase.approvalIssueNumber, + issueTitle: "", + issueBody: "", + issueApprovers: nil, + minimumApprovals: 0, + } + + os.Remove(testCase.env_github_output) + actual, err := a.SetActionOutputs(nil) + + if err != nil { + t.Fatalf("error creating output file: %v: %v", testCase.env_github_output, err) + } + + if actual != testCase.isSuccess { + t.Fatalf("expected %v but got %v", testCase.isSuccess, actual) + } + + if actual == true { + if _, err := os.Stat(testCase.env_github_output); errors.Is(err, os.ErrNotExist) { + t.Fatalf("expected create output file %v but it was not", testCase.env_github_output) + } + } + }) + } +} diff --git a/main.go b/main.go index b771f92..e439961 100644 --- a/main.go +++ b/main.go @@ -13,19 +13,6 @@ import ( "golang.org/x/oauth2" ) -func setActionOutput(name, value string) error { - f, err := os.OpenFile(os.Getenv("GITHUB_OUTPUT"), os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - return err - } - defer f.Close() - - if _, err = f.WriteString(fmt.Sprintf("%s=%s\n", name, value)); err != nil { - return err - } - return nil -} - func handleInterrupt(ctx context.Context, client *github.Client, apprv *approvalEnvironment) { newState := "closed" closeComment := "Workflow cancelled, closing issue." @@ -235,6 +222,16 @@ func main() { os.Exit(1) } + outputs := map[string]string { + "issue-number": fmt.Sprintf("%d", apprv.approvalIssueNumber), + "issue-url": apprv.approvalIssue.GetHTMLURL(), + } + _, err = apprv.SetActionOutputs(outputs) + if err != nil { + fmt.Printf("error saving output: %v", err) + os.Exit(1) + } + killSignalChannel := make(chan os.Signal, 1) signal.Notify(killSignalChannel, os.Interrupt) @@ -252,7 +249,10 @@ func main() { } else { approvalStatus = "approved" } - if err := setActionOutput("approval_status", approvalStatus); err != nil { + outputs := map[string]string { + "approval-status": approvalStatus, + } + if _, err := apprv.SetActionOutputs(outputs); err != nil { fmt.Printf("error setting action output: %v\n", err) exitCode = 1 }