Set minimum approvals (#1)
* Allow approval of a worflow with a set number of approvals * update action interface * Added test for when not enough approvals have been registered and minimumApprovals is set * Warning raised by invalid minimumApprovals value should be an error * update new input name to use hyphens * Input env variable does in fact contain hyphens
This commit is contained in:
parent
31a4889bfd
commit
4d12fc4297
6 changed files with 95 additions and 22 deletions
|
|
@ -30,6 +30,8 @@ steps:
|
|||
with:
|
||||
secret: ${{ github.TOKEN }}
|
||||
approvers: user1,user2
|
||||
minimum-approvals: 1
|
||||
```
|
||||
|
||||
`approvers` is a comma-delimited list of all required approvers.
|
||||
- `approvers` is a comma-delimited list of all required approvers.
|
||||
- `minimum-approvals` is an integer that sets the minimum number of approvals required to progress the workflow. Defaults to ALL approvers.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ inputs:
|
|||
secret:
|
||||
description: Secret
|
||||
required: true
|
||||
minimum-approvals:
|
||||
description: Minimum number of approvals to progress workflow
|
||||
required: false
|
||||
runs:
|
||||
using: docker
|
||||
image: docker://ghcr.io/trstringer/manual-approval:1.1.3
|
||||
|
|
|
|||
24
approval.go
24
approval.go
|
|
@ -16,11 +16,12 @@ type approvalEnvironment struct {
|
|||
repoOwner string
|
||||
runID int
|
||||
approvers []string
|
||||
minimumApprovals int
|
||||
approvalIssue *github.Issue
|
||||
approvalIssueNumber int
|
||||
}
|
||||
|
||||
func newApprovalEnvironment(client *github.Client, repoFullName, repoOwner string, runID int, approvers []string) (*approvalEnvironment, error) {
|
||||
func newApprovalEnvironment(client *github.Client, repoFullName, repoOwner string, runID int, approvers []string, minimumApprovals int) (*approvalEnvironment, error) {
|
||||
repoOwnerAndName := strings.Split(repoFullName, "/")
|
||||
if len(repoOwnerAndName) != 2 {
|
||||
return nil, fmt.Errorf("repo owner and name in unexpected format: %s", repoFullName)
|
||||
|
|
@ -28,12 +29,13 @@ func newApprovalEnvironment(client *github.Client, repoFullName, repoOwner strin
|
|||
repo := repoOwnerAndName[1]
|
||||
|
||||
return &approvalEnvironment{
|
||||
client: client,
|
||||
repoFullName: repoFullName,
|
||||
repo: repo,
|
||||
repoOwner: repoOwner,
|
||||
runID: runID,
|
||||
approvers: approvers,
|
||||
client: client,
|
||||
repoFullName: repoFullName,
|
||||
repo: repo,
|
||||
repoOwner: repoOwner,
|
||||
runID: runID,
|
||||
approvers: approvers,
|
||||
minimumApprovals: minimumApprovals,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -64,10 +66,14 @@ Respond %s to continue workflow or %s to cancel.`,
|
|||
return err
|
||||
}
|
||||
|
||||
func approvalFromComments(comments []*github.IssueComment, approvers []string) (approvalStatus, error) {
|
||||
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)
|
||||
|
|
@ -81,7 +87,7 @@ func approvalFromComments(comments []*github.IssueComment, approvers []string) (
|
|||
return approvalStatusPending, err
|
||||
}
|
||||
if isApprovalComment {
|
||||
if len(remainingApprovers) == 1 {
|
||||
if len(remainingApprovers) == len(approvers)-minimumApprovals+1 {
|
||||
return approvalStatusApproved, nil
|
||||
}
|
||||
remainingApprovers[approverIdx] = remainingApprovers[len(remainingApprovers)-1]
|
||||
|
|
|
|||
|
|
@ -9,15 +9,17 @@ import (
|
|||
func TestApprovalFromComments(t *testing.T) {
|
||||
login1 := "login1"
|
||||
login2 := "login2"
|
||||
login3 := "login3"
|
||||
bodyApproved := "Approved"
|
||||
bodyDenied := "Denied"
|
||||
bodyPending := "not approval or denial"
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
comments []*github.IssueComment
|
||||
approvers []string
|
||||
expectedStatus approvalStatus
|
||||
name string
|
||||
comments []*github.IssueComment
|
||||
approvers []string
|
||||
minimumApprovals int
|
||||
expectedStatus approvalStatus
|
||||
}{
|
||||
{
|
||||
name: "single_approver_single_comment_approved",
|
||||
|
|
@ -112,11 +114,55 @@ func TestApprovalFromComments(t *testing.T) {
|
|||
approvers: []string{login1, login2},
|
||||
expectedStatus: approvalStatusDenied,
|
||||
},
|
||||
{
|
||||
name: "multi_approver_minimum_one_approval",
|
||||
comments: []*github.IssueComment{
|
||||
{
|
||||
User: &github.User{Login: &login1},
|
||||
Body: &bodyPending,
|
||||
},
|
||||
{
|
||||
User: &github.User{Login: &login2},
|
||||
Body: &bodyApproved,
|
||||
},
|
||||
},
|
||||
approvers: []string{login1, login2},
|
||||
expectedStatus: approvalStatusApproved,
|
||||
minimumApprovals: 1,
|
||||
},
|
||||
{
|
||||
name: "multi_approver_minimum_two_approvals",
|
||||
comments: []*github.IssueComment{
|
||||
{
|
||||
User: &github.User{Login: &login1},
|
||||
Body: &bodyApproved,
|
||||
},
|
||||
{
|
||||
User: &github.User{Login: &login2},
|
||||
Body: &bodyApproved,
|
||||
},
|
||||
},
|
||||
approvers: []string{login1, login2, login3},
|
||||
expectedStatus: approvalStatusApproved,
|
||||
minimumApprovals: 2,
|
||||
},
|
||||
{
|
||||
name: "multi_approver_approvals_less_than_minimum",
|
||||
comments: []*github.IssueComment{
|
||||
{
|
||||
User: &github.User{Login: &login1},
|
||||
Body: &bodyApproved,
|
||||
},
|
||||
},
|
||||
approvers: []string{login1, login2, login3},
|
||||
expectedStatus: approvalStatusPending,
|
||||
minimumApprovals: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
actual, err := approvalFromComments(testCase.comments, testCase.approvers)
|
||||
actual, err := approvalFromComments(testCase.comments, testCase.approvers, testCase.minimumApprovals)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting approval from comments: %v", err)
|
||||
}
|
||||
|
|
|
|||
11
constants.go
11
constants.go
|
|
@ -5,11 +5,12 @@ import "time"
|
|||
const (
|
||||
pollingInterval time.Duration = 10 * time.Second
|
||||
|
||||
envVarRepoFullName string = "GITHUB_REPOSITORY"
|
||||
envVarRunID string = "GITHUB_RUN_ID"
|
||||
envVarRepoOwner string = "GITHUB_REPOSITORY_OWNER"
|
||||
envVarToken string = "INPUT_SECRET"
|
||||
envVarApprovers string = "INPUT_APPROVERS"
|
||||
envVarRepoFullName string = "GITHUB_REPOSITORY"
|
||||
envVarRunID string = "GITHUB_RUN_ID"
|
||||
envVarRepoOwner string = "GITHUB_REPOSITORY_OWNER"
|
||||
envVarToken string = "INPUT_SECRET"
|
||||
envVarApprovers string = "INPUT_APPROVERS"
|
||||
envVarMinimumApprovals string = "INPUT_MINIMUM-APPROVALS"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
19
main.go
19
main.go
|
|
@ -37,7 +37,22 @@ func main() {
|
|||
fmt.Printf("Required approvers: %s\n", requiredApproversRaw)
|
||||
approvers := strings.Split(requiredApproversRaw, ",")
|
||||
|
||||
apprv, err := newApprovalEnvironment(client, repoFullName, repoOwner, runID, approvers)
|
||||
minimumApprovalsRaw := os.Getenv(envVarMinimumApprovals)
|
||||
minimumApprovals := len(approvers)
|
||||
if minimumApprovalsRaw != "" {
|
||||
minimumApprovals, err = strconv.Atoi(minimumApprovalsRaw)
|
||||
if err != nil {
|
||||
fmt.Printf("error parsing minimum number of approvals: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if minimumApprovals > len(approvers) {
|
||||
fmt.Printf("error: minimum required approvals (%v) is greater than the total number of approvers (%v)\n", minimumApprovals, len(approvers))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
apprv, err := newApprovalEnvironment(client, repoFullName, repoOwner, runID, approvers, minimumApprovals)
|
||||
if err != nil {
|
||||
fmt.Printf("error creating approval environment: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
|
@ -57,7 +72,7 @@ commentLoop:
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
approved, err := approvalFromComments(comments, approvers)
|
||||
approved, err := approvalFromComments(comments, approvers, minimumApprovals)
|
||||
if err != nil {
|
||||
fmt.Printf("error getting approval from comments: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue