Skip to content

Commit

Permalink
Merge pull request #255 from colesgroup/MADM-312-azuredevops-addition…
Browse files Browse the repository at this point in the history
…al-branch-policy-settings-in-the-terraform-provider

azuredevops additional branch policy settings in the terraform provider
  • Loading branch information
xuzhang3 authored Jan 25, 2021
2 parents 7fd7c42 + f36b672 commit 4508083
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 40 deletions.
3 changes: 0 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
Expand Down
30 changes: 25 additions & 5 deletions azuredevops/internal/acceptancetests/resource_branchpolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/acceptancetests/testutils"
)

// TestAccBranchPolicyMinReviewers_CreateAndUpdate - acceptance test for min reviewers branch policy attributes
func TestAccBranchPolicyMinReviewers_CreateAndUpdate(t *testing.T) {
minReviewerTfNode := "azuredevops_branch_policy_min_reviewers.p"

Expand All @@ -26,13 +27,25 @@ func TestAccBranchPolicyMinReviewers_CreateAndUpdate(t *testing.T) {
resource.TestCheckResourceAttrSet(minReviewerTfNode, "id"),
resource.TestCheckResourceAttr(minReviewerTfNode, "blocking", "true"),
resource.TestCheckResourceAttr(minReviewerTfNode, "enabled", "true"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.submitter_can_vote", "false"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.allow_completion_with_rejects_or_waits", "false"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.last_pusher_cannot_approve", "false"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.on_last_iteration_require_vote", "false"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.on_last_iteration_require_vote", "false"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.on_push_reset_approved_votes", "true"),
),
}, {
Config: getMinReviewersHcl(false, false, 2, true),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(minReviewerTfNode, "id"),
resource.TestCheckResourceAttr(minReviewerTfNode, "blocking", "false"),
resource.TestCheckResourceAttr(minReviewerTfNode, "enabled", "false"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.submitter_can_vote", "true"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.allow_completion_with_rejects_or_waits", "true"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.last_pusher_cannot_approve", "true"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.on_last_iteration_require_vote", "true"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.on_last_iteration_require_vote", "true"),
resource.TestCheckResourceAttr(minReviewerTfNode, "settings.0.on_push_reset_all_votes", "true"),
),
}, {
ResourceName: minReviewerTfNode,
Expand All @@ -44,13 +57,20 @@ func TestAccBranchPolicyMinReviewers_CreateAndUpdate(t *testing.T) {
})
}

func getMinReviewersHcl(enabled bool, blocking bool, reviewers int, submitterCanVote bool) string {
func getMinReviewersHcl(enabled bool, blocking bool, reviewers int, flag bool) string {
votes := "all"
if !flag {
votes = "approved"
}
settings := fmt.Sprintf(
`
reviewer_count = %d
submitter_can_vote = %t
`, reviewers, submitterCanVote,
)
reviewer_count = %[1]d
submitter_can_vote = %[2]t
allow_completion_with_rejects_or_waits =%[2]t
last_pusher_cannot_approve = %[2]t
on_last_iteration_require_vote = %[2]t
on_push_reset_%[3]s_votes = true
`, reviewers, flag, votes)

return getBranchPolicyHcl("azuredevops_branch_policy_min_reviewers", enabled, blocking, settings)
}
Expand Down
8 changes: 6 additions & 2 deletions azuredevops/internal/acceptancetests/testutils/commons.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,16 @@ func PreCheck(t *testing.T, additionalEnvVars *[]string) {
if additionalEnvVars != nil {
requiredEnvVars = append(requiredEnvVars, *additionalEnvVars...)
}

missing := false
for _, variable := range requiredEnvVars {
if _, ok := os.LookupEnv(variable); !ok {
t.Fatalf("`%s` must be set for this acceptance test!", variable)
missing = true
t.Errorf("`%s` must be set for this acceptance test!", variable)
}
}
if missing {
t.Fatalf("Some environment variables missing.")
}
}

// GenerateResourceName generates a random name with a constant prefix, useful for acceptance tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ type autoReviewerPolicySettings struct {
}

const (
autoReviewerIds = "auto_reviewer_ids"
pathFilters = "path_filters"
displayMessage = "message"
autoReviewerIds = "auto_reviewer_ids"
pathFilters = "path_filters"
displayMessage = "message"
schemaSubmitterCanVote = "submitter_can_vote"
)

// ResourceBranchPolicyAutoReviewers schema and implementation for automatic code reviewer policy resource
Expand Down Expand Up @@ -71,13 +72,13 @@ func autoReviewersFlattenFunc(d *schema.ResourceData, policyConfig *policy.Polic
}
policyAsJSON, err := json.Marshal(policyConfig.Settings)
if err != nil {
return fmt.Errorf("Unable to marshal policy settings into JSON: %+v", err)
return fmt.Errorf("unable to marshal policy settings into JSON: %+v", err)
}

policySettings := autoReviewerPolicySettings{}
err = json.Unmarshal(policyAsJSON, &policySettings)
if err != nil {
return fmt.Errorf("Unable to unmarshal branch policy settings (%+v): %+v", policySettings, err)
return fmt.Errorf("unable to unmarshal branch policy settings (%+v): %+v", policySettings, err)
}

settingsList := d.Get(SchemaSettings).([]interface{})
Expand All @@ -87,7 +88,7 @@ func autoReviewersFlattenFunc(d *schema.ResourceData, policyConfig *policy.Polic
settings[autoReviewerIds] = policySettings.AutoReviewerIds
settings[pathFilters] = policySettings.PathFilters
settings[displayMessage] = policySettings.DisplayMessage
d.Set(SchemaSettings, settingsList)
_ = d.Set(SchemaSettings, settingsList)
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package policy
import (
"encoding/json"
"fmt"
"reflect"

"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
Expand All @@ -11,14 +12,14 @@ import (
"github.com/microsoft/azure-devops-go-api/azuredevops/policy"
)

const (
schemaReviewerCount = "reviewer_count"
schemaSubmitterCanVote = "submitter_can_vote"
)

type minReviewerPolicySettings struct {
ApprovalCount int `json:"minimumApproverCount"`
SubmitterCanVote bool `json:"creatorVoteCounts"`
ApprovalCount int `json:"minimumApproverCount" tf:"reviewer_count"`
SubmitterCanVote bool `json:"creatorVoteCounts" tf:"submitter_can_vote"`
AllowCompletionWithRejectsOrWaits bool `json:"allowDownvotes" tf:"allow_completion_with_rejects_or_waits"`
OnPushResetApprovedVotes bool `json:"resetOnSourcePush" tf:"on_push_reset_approved_votes" ConflictsWith:"on_push_reset_all_votes"`
OnLastIterationRequireVote bool `json:"requireVoteOnLastIteration" tf:"on_last_iteration_require_vote"`
OnPushResetAllVotes bool `json:"resetRejectionsOnSourcePush" tf:"on_push_reset_all_votes" ConflictsWith:"on_push_reset_approved_votes"`
LastPusherCannotVote bool `json:"blockLastPusherVote" tf:"last_pusher_cannot_approve"`
}

// ResourceBranchPolicyMinReviewers schema and implementation for min reviewer policy resource
Expand All @@ -30,19 +31,40 @@ func ResourceBranchPolicyMinReviewers() *schema.Resource {
})

settingsSchema := resource.Schema[SchemaSettings].Elem.(*schema.Resource).Schema
settingsSchema[schemaReviewerCount] = &schema.Schema{
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
}
settingsSchema[schemaSubmitterCanVote] = &schema.Schema{
Type: schema.TypeBool,
Default: false,
Optional: true,

// Dynamically create the schema based on the minReviewerPolicySettings tags
metaField := reflect.TypeOf(minReviewerPolicySettings{})
// Loop through the fields, adding schema for each one.
for i := 0; i < metaField.NumField(); i++ {
tfName := metaField.Field(i).Tag.Get("tf")
if _, ok := settingsSchema[tfName]; ok {
continue // skip those which are already set
}
if metaField.Field(i).Type == reflect.TypeOf(true) {
settingsSchema[tfName] = &schema.Schema{
Type: schema.TypeBool,
Default: false,
Optional: true,
}
}
if metaField.Field(i).Type == reflect.TypeOf(0) {
settingsSchema[tfName] = &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntAtLeast(1),
}
}
if conflicts, ok := metaField.Field(i).Tag.Lookup("ConflictsWith"); ok {
if _, ok := settingsSchema[conflicts]; ok {
settingsSchema[conflicts].ConflictsWith = []string{SchemaSettings + ".0." + tfName}
settingsSchema[tfName].ConflictsWith = []string{SchemaSettings + ".0." + conflicts}
}
}
}
return resource
}

// API to TF
func minReviewersFlattenFunc(d *schema.ResourceData, policyConfig *policy.PolicyConfiguration, projectID *string) error {
err := baseFlattenFunc(d, policyConfig, projectID)
if err != nil {
Expand All @@ -62,13 +84,23 @@ func minReviewersFlattenFunc(d *schema.ResourceData, policyConfig *policy.Policy
settingsList := d.Get(SchemaSettings).([]interface{})
settings := settingsList[0].(map[string]interface{})

settings[schemaReviewerCount] = policySettings.ApprovalCount
settings[schemaSubmitterCanVote] = policySettings.SubmitterCanVote
tipe := reflect.TypeOf(policySettings)
for i := 0; i < tipe.NumField(); i++ {
tfName := tipe.Field(i).Tag.Get("tf")
ps := reflect.ValueOf(policySettings)
if tipe.Field(i).Type == reflect.TypeOf(true) {
settings[tfName] = ps.Field(i).Bool()
}
if tipe.Field(i).Type == reflect.TypeOf(0) {
settings[tfName] = ps.Field(i).Int()
}
}

d.Set(SchemaSettings, settingsList)
return nil
}

// From TF to API
func minReviewersExpandFunc(d *schema.ResourceData, typeID uuid.UUID) (*policy.PolicyConfiguration, *string, error) {
policyConfig, projectID, err := baseExpandFunc(d, typeID)
if err != nil {
Expand All @@ -79,8 +111,22 @@ func minReviewersExpandFunc(d *schema.ResourceData, typeID uuid.UUID) (*policy.P
settings := settingsList[0].(map[string]interface{})

policySettings := policyConfig.Settings.(map[string]interface{})
policySettings["minimumApproverCount"] = settings[schemaReviewerCount].(int)
policySettings["creatorVoteCounts"] = settings[schemaSubmitterCanVote].(bool)

tipe := reflect.TypeOf(minReviewerPolicySettings{})
for i := 0; i < tipe.NumField(); i++ {
tags := tipe.Field(i).Tag
apiName := tags.Get("json")
tfName := tags.Get("tf")
if _, ok := policySettings[apiName]; ok {
continue
}
if tipe.Field(i).Type == reflect.TypeOf(true) {
policySettings[apiName] = settings[tfName].(bool)
}
if tipe.Field(i).Type == reflect.TypeOf(0) {
policySettings[apiName] = settings[tfName].(int)
}
}

return policyConfig, projectID, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// +build all resource_branchpolicy_min_reviewers
// +build !exclude_resource_branchpolicy_min_reviewers

package policy

import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/stretchr/testify/require"

"github.com/google/uuid"
"github.com/microsoft/azure-devops-go-api/azuredevops/policy"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/converter"
)

// verifies that the flatten/expand round trip path produces repeatable results
func TestBranchPolicyMinReviewers_ExpandFlatten_Roundtrip(t *testing.T) {
var projectID = uuid.New().String()
var randomUUID = uuid.New()
var testPolicy = &policy.PolicyConfiguration{
Id: converter.Int(1),
IsEnabled: converter.Bool(true),
IsBlocking: converter.Bool(true),
Type: &policy.PolicyTypeRef{
Id: &randomUUID,
},
Settings: map[string]interface{}{
"scope": []map[string]interface{}{
{
"repositoryId": "test-repo-id",
"refName": "test-ref-name",
"matchKind": "test-match-kind",
},
},
"minimumApproverCount": 2,
"creatorVoteCounts": true,
"allowDownvotes": true,
"resetOnSourcePush": true,
"requireVoteOnLastIteration": true,
"resetRejectionsOnSourcePush": true,
"blockLastPusherVote": true,
},
}

resourceData := schema.TestResourceDataRaw(t, ResourceBranchPolicyMinReviewers().Schema, nil)
err := minReviewersFlattenFunc(resourceData, testPolicy, &projectID)
require.Nil(t, err)
expandedPolicy, expandedProjectID, err := minReviewersExpandFunc(resourceData, randomUUID)
require.Nil(t, err)

require.Equal(t, testPolicy, expandedPolicy)
require.Equal(t, projectID, *expandedProjectID)
}
20 changes: 16 additions & 4 deletions website/docs/r/branch_policy_min_reviewers.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: |-

# azuredevops_branch_policy_min_reviewers

Manages a minimum reviewer branch policy within Azure DevOps.
Branch policy for reviewers on pull requests. Includes the minimum number of reviewers and other conditions.

## Example Usage

Expand All @@ -31,8 +31,12 @@ resource "azuredevops_branch_policy_min_reviewers" "p" {
blocking = true
settings {
reviewer_count = 2
reviewer_count = 7
submitter_can_vote = false
last_pusher_cannot_approve = true
allow_completion_with_rejects_or_waits = false
on_push_reset_approved_votes = true # OR on_push_reset_all_votes = true
on_last_iteration_require_vote = false
scope {
repository_id = azuredevops_git_repository.r.id
Expand All @@ -41,7 +45,7 @@ resource "azuredevops_branch_policy_min_reviewers" "p" {
}
scope {
repository_id = azuredevops_git_repository.r.id
repository_id = null # All repositories in the project
repository_ref = "refs/heads/releases"
match_type = "Prefix"
}
Expand All @@ -61,7 +65,15 @@ The following arguments are supported:
A `settings` block supports the following:

- `reviewer_count` - (Required) The number of reviewrs needed to approve.
- `submitter_can_vote` - (Optional) Controls whether or not the submitter's vote counts. Defaults to `false`.
- `submitter_can_vote` - (Optional) Allow requestors to approve their own changes. Defaults to `false`.
- `last_pusher_cannot_approve`(Optional) Prohibit the most recent pusher from approving their own changes. Defaults to `false`.
- `allow_completion_with_rejects_or_waits` (Optional) Allow completion even if some reviewers vote to wait or reject. Defaults to `false`.
- `on_push_reset_approved_votes` (Optional) When new changes are pushed reset all approval votes (does not reset votes to reject or wait). Defaults to `false`.
- `on_push_reset_all_votes` (Optional) When new changes are pushed reset all code reviewer votes. Defaults to `false`.
- `on_last_iteration_require_vote` (Optional) On last iteration require vote. Defaults to `false`.

Only one of `on_push_reset_all_votes` or `on_push_reset_approved_votes` may be specified.

- `scope` (Required) Controls which repositories and branches the policy will be enabled for. This block must be defined at least once.

A `settings` `scope` block supports the following:
Expand Down

0 comments on commit 4508083

Please sign in to comment.