manual-approval/approval.go
Thomas Stringer 14ea447010
Fix newline for denied (#22)
This was originally fixed for approved in #12 but the denied case was
missing. This adds a failing test and the fix for it.
2022-06-29 20:54:06 -04:00

170 lines
4.2 KiB
Go

package main
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/google/go-github/v43/github"
)
type approvalEnvironment struct {
client *github.Client
repoFullName string
repo string
repoOwner string
runID int
approvers []string
minimumApprovals int
approvalIssue *github.Issue
approvalIssueNumber int
issueTitle string
}
func newApprovalEnvironment(client *github.Client, repoFullName, repoOwner string, runID int, approvers []string, minimumApprovals int, issueTitle string) (*approvalEnvironment, error) {
repoOwnerAndName := strings.Split(repoFullName, "/")
if len(repoOwnerAndName) != 2 {
return nil, fmt.Errorf("repo owner and name in unexpected format: %s", repoFullName)
}
repo := repoOwnerAndName[1]
return &approvalEnvironment{
client: client,
repoFullName: repoFullName,
repo: repo,
repoOwner: repoOwner,
runID: runID,
approvers: approvers,
minimumApprovals: minimumApprovals,
issueTitle: issueTitle,
}, nil
}
func (a approvalEnvironment) runURL() string {
return fmt.Sprintf("https://github.com/%s/actions/runs/%d", a.repoFullName, a.runID)
}
func (a *approvalEnvironment) createApprovalIssue(ctx context.Context) error {
issueTitle := fmt.Sprintf("Manual approval required for workflow run %d", a.runID)
if a.issueTitle != "" {
issueTitle = fmt.Sprintf("%s: %s", issueTitle, a.issueTitle)
}
issueBody := fmt.Sprintf(`Workflow is pending manual review.
URL: %s
Required approvers: %s
Respond %s to continue workflow or %s to cancel.`,
a.runURL(),
a.approvers,
formatAcceptedWords(approvedWords),
formatAcceptedWords(deniedWords),
)
var err error
fmt.Printf(
"Creating issue in repo %s/%s with the following content:\nTitle: %s\nApprovers: %s\nBody:\n%s\n",
a.repoOwner,
a.repo,
issueTitle,
a.approvers,
issueBody,
)
a.approvalIssue, _, err = a.client.Issues.Create(ctx, a.repoOwner, a.repo, &github.IssueRequest{
Title: &issueTitle,
Body: &issueBody,
Assignees: &a.approvers,
})
a.approvalIssueNumber = a.approvalIssue.GetNumber()
return err
}
func approvalFromComments(comments []*github.IssueComment, approvers []string, minimumApprovals int) (approvalStatus, error) {
remainingApprovers := make([]string, len(approvers))
copy(remainingApprovers, approvers)
if minimumApprovals == 0 {
minimumApprovals = len(approvers)
}
for _, comment := range comments {
commentUser := comment.User.GetLogin()
approverIdx := approversIndex(remainingApprovers, commentUser)
if approverIdx < 0 {
continue
}
commentBody := comment.GetBody()
isApprovalComment, err := isApproved(commentBody)
if err != nil {
return approvalStatusPending, err
}
if isApprovalComment {
if len(remainingApprovers) == len(approvers)-minimumApprovals+1 {
return approvalStatusApproved, nil
}
remainingApprovers[approverIdx] = remainingApprovers[len(remainingApprovers)-1]
remainingApprovers = remainingApprovers[:len(remainingApprovers)-1]
continue
}
isDenialComment, err := isDenied(commentBody)
if err != nil {
return approvalStatusPending, err
}
if isDenialComment {
return approvalStatusDenied, nil
}
}
return approvalStatusPending, nil
}
func approversIndex(approvers []string, name string) int {
for idx, approver := range approvers {
if approver == name {
return idx
}
}
return -1
}
func isApproved(commentBody string) (bool, error) {
for _, approvedWord := range approvedWords {
matched, err := regexp.MatchString(fmt.Sprintf("(?i)^%s[.!]*\n*$", approvedWord), commentBody)
if err != nil {
return false, err
}
if matched {
return true, nil
}
}
return false, nil
}
func isDenied(commentBody string) (bool, error) {
for _, deniedWord := range deniedWords {
matched, err := regexp.MatchString(fmt.Sprintf("(?i)^%s[.!]*\n*$", deniedWord), commentBody)
if err != nil {
return false, err
}
if matched {
return true, nil
}
}
return false, nil
}
func formatAcceptedWords(words []string) string {
var quotedWords []string
for _, word := range words {
quotedWords = append(quotedWords, fmt.Sprintf("\"%s\"", word))
}
return strings.Join(quotedWords, ", ")
}