diff --git a/Makefile b/Makefile index 3ec553a..8c3fc9b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ IMAGE_REPO=ghcr.io/trstringer/manual-approval +TARGET_PLATFORM=linux/amd64 .PHONY: build build: @@ -6,7 +7,7 @@ build: echo "VERSION is required"; \ exit 1; \ fi - docker build -t $(IMAGE_REPO):$$VERSION . + docker build --platform $(TARGET_PLATFORM) -t $(IMAGE_REPO):$$VERSION . .PHONY: push push: diff --git a/action.yaml b/action.yaml index 7866c0b..bef33d7 100644 --- a/action.yaml +++ b/action.yaml @@ -24,13 +24,20 @@ inputs: required: false exclude-workflow-initiator-as-approver: description: Whether or not to filter out the user who initiated the workflow as an approver if they are in the approvers list - default: false + required: false + default: 'false' additional-approved-words: description: Comma separated list of words that can be used to approve beyond the defaults. + required: false default: '' additional-denied-words: description: Comma separated list of words that can be used to deny beyond the defaults. + required: false default: '' + fail-on-denial: + description: Whether or not to fail the workflow if the approval is denied + required: false + default: 'true' runs: using: docker image: docker://ghcr.io/trstringer/manual-approval:1.9.1 diff --git a/approval.go b/approval.go index e2bb13f..d69df54 100644 --- a/approval.go +++ b/approval.go @@ -21,9 +21,10 @@ type approvalEnvironment struct { issueBody string issueApprovers []string minimumApprovals int + failOnDenial bool } -func newApprovalEnvironment(client *github.Client, repoFullName, repoOwner string, runID int, approvers []string, minimumApprovals int, issueTitle, issueBody string) (*approvalEnvironment, error) { +func newApprovalEnvironment(client *github.Client, repoFullName, repoOwner string, runID int, approvers []string, minimumApprovals int, issueTitle, issueBody string, failOnDenial bool) (*approvalEnvironment, error) { repoOwnerAndName := strings.Split(repoFullName, "/") if len(repoOwnerAndName) != 2 { return nil, fmt.Errorf("repo owner and name in unexpected format: %s", repoFullName) @@ -40,6 +41,7 @@ func newApprovalEnvironment(client *github.Client, repoFullName, repoOwner strin minimumApprovals: minimumApprovals, issueTitle: issueTitle, issueBody: issueBody, + failOnDenial: failOnDenial, }, nil } diff --git a/constants.go b/constants.go index 03e4b4e..0e08102 100644 --- a/constants.go +++ b/constants.go @@ -21,6 +21,7 @@ const ( envVarExcludeWorkflowInitiatorAsApprover string = "INPUT_EXCLUDE-WORKFLOW-INITIATOR-AS-APPROVER" envVarAdditionalApprovedWords string = "INPUT_ADDITIONAL-APPROVED-WORDS" envVarAdditionalDeniedWords string = "INPUT_ADDITIONAL-DENIED-WORDS" + envVarFailOnDenial string = "INPUT_FAIL-ON-DENIAL" ) var ( diff --git a/main.go b/main.go index 5aad15e..5349a87 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,19 @@ 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." @@ -71,7 +84,14 @@ func newCommentLoopChannel(ctx context.Context, apprv *approvalEnvironment, clie close(channel) case approvalStatusDenied: newState := "closed" - closeComment := "Request denied. Closing issue and failing workflow." + closeComment := "Request denied. Closing issue " + if !apprv.failOnDenial { + closeComment += "but continuing" + } else { + closeComment += "and failing" + } + closeComment += " workflow." + _, _, err := client.Issues.CreateComment(ctx, apprv.repoOwner, apprv.repo, apprv.approvalIssueNumber, &github.IssueComment{ Body: &closeComment, }) @@ -170,6 +190,16 @@ func main() { os.Exit(1) } + failOnDenial := true + failOnDenialRaw := os.Getenv(envVarFailOnDenial) + if failOnDenialRaw != "" { + failOnDenial, err = strconv.ParseBool(failOnDenialRaw) + if err != nil { + fmt.Printf("error parsing fail on denial: %v\n", err) + os.Exit(1) + } + } + issueTitle := os.Getenv(envVarIssueTitle) issueBody := os.Getenv(envVarIssueBody) minimumApprovalsRaw := os.Getenv(envVarMinimumApprovals) @@ -181,7 +211,7 @@ func main() { os.Exit(1) } } - apprv, err := newApprovalEnvironment(client, repoFullName, repoOwner, runID, approvers, minimumApprovals, issueTitle, issueBody) + apprv, err := newApprovalEnvironment(client, repoFullName, repoOwner, runID, approvers, minimumApprovals, issueTitle, issueBody, failOnDenial) if err != nil { fmt.Printf("error creating approval environment: %v\n", err) os.Exit(1) @@ -200,6 +230,20 @@ func main() { select { case exitCode := <-commentLoopChannel: + approvalStatus := "" + + if (!failOnDenial && exitCode == 1) { + approvalStatus = "denied" + exitCode = 0 + } else if (exitCode == 1) { + approvalStatus = "denied" + } else { + approvalStatus = "approved" + } + if err := setActionOutput("approval_status", approvalStatus); err != nil { + fmt.Printf("error setting action output: %v\n", err) + exitCode = 1 + } os.Exit(exitCode) case <-killSignalChannel: handleInterrupt(ctx, client, apprv)