Add resource_project_branch_permissions (#47)

Resolves gavinbunney/terraform-provider-bitbucketserver#17
This commit is contained in:
Stanislau Liamniou
2022-06-17 18:27:16 +02:00
committed by GitHub
parent 2e2fc4e78f
commit bd6992d6e0
5 changed files with 498 additions and 0 deletions

21
bitbucket/helpers.go Normal file
View File

@@ -0,0 +1,21 @@
package bitbucket
import (
"fmt"
)
func baseConfigForRepositoryBasedTests(projectKey string) string {
config := fmt.Sprintf(`
resource "bitbucketserver_project" "test" {
key = "%v"
name = "test-project-%v"
}
resource "bitbucketserver_repository" "test" {
project = bitbucketserver_project.test.key
name = "repo"
}
`, projectKey, projectKey)
return config
}

View File

@@ -57,6 +57,7 @@ func Provider() terraform.ResourceProvider {
"bitbucketserver_plugin": resourcePlugin(),
"bitbucketserver_plugin_config": resourcePluginConfig(),
"bitbucketserver_project": resourceProject(),
"bitbucketserver_project_branch_permissions": resourceBranchPermissions(),
"bitbucketserver_project_hook": resourceProjectHook(),
"bitbucketserver_project_permissions_group": resourceProjectPermissionsGroup(),
"bitbucketserver_project_permissions_user": resourceProjectPermissionsUser(),

View File

@@ -0,0 +1,341 @@
package bitbucket
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)
type BranchPermissionPayload struct {
Type string `json:"type,omitempty"`
Matcher MatcherStruct `json:"matcher,omitempty"`
Users []string `json:"users,omitempty"`
Groups []string `json:"groups,omitempty"`
AccessKeys []string `json:"accessKeys,omitempty"`
}
type MatcherStruct struct {
Id string `json:"id,omitempty"`
DisplayId string `json:"displayId,omitempty"`
Type MatcherStructType `json:"type,omitempty"`
Active bool `json:"active,omitempty"`
}
type MatcherStructType struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type BranchPermissionResponse struct {
Id int `json:"id"`
Scope struct {
ResourceID int `json:"resourceId"`
Type string `json:"type"`
} `json:"scope"`
Type string `json:"type"`
Users []struct {
Name string `json:"name"`
EmailAddress string `json:"emailAddress"`
ID int `json:"id"`
DisplayName string `json:"displayName"`
Active bool `json:"active"`
Slug string `json:"slug"`
Type string `json:"type"`
} `json:"users"`
Groups []string `json:"groups"`
AccessKeys []struct {
Key struct {
ID int `json:"id"`
Text string `json:"text"`
Label string `json:"label"`
} `json:"key"`
} `json:"accessKeys"`
}
type AllRepositoryBranchPermissionsResponse struct {
Size int `json:"size"`
Limit int `json:"limit"`
IsLastPage bool `json:"isLastPage"`
Values []BranchPermissionResponse `json:"values"`
}
func resourceBranchPermissions() *schema.Resource {
return &schema.Resource{
Create: resourceBranchPermissionsCreate,
Read: resourceBranchPermissionsRead,
Update: resourceBranchPermissionsCreate,
Delete: resourceBranchPermissionsDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"project": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"ref_pattern": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"pull-request-only", "fast-forward-only", "no-deletes", "read-only"}, false),
},
"exception_users": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"exception_groups": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"exception_access_keys": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"permission_id": {
Type: schema.TypeInt,
Computed: true,
},
},
}
}
func newBranchPermissionPayloadFromResource(d *schema.ResourceData) *BranchPermissionPayload {
branchPermissionPayload := &BranchPermissionPayload{
Type: d.Get("type").(string),
}
for _, item := range d.Get("exception_users").([]interface{}) {
branchPermissionPayload.Users = append(branchPermissionPayload.Users, item.(string))
}
for _, item := range d.Get("exception_groups").([]interface{}) {
branchPermissionPayload.Groups = append(branchPermissionPayload.Groups, item.(string))
}
for _, item := range d.Get("exception_access_keys").([]interface{}) {
branchPermissionPayload.AccessKeys = append(branchPermissionPayload.AccessKeys, item.(string))
}
matcherConfig := &MatcherStruct{
Id: d.Get("ref_pattern").(string),
DisplayId: d.Get("ref_pattern").(string),
Type: MatcherStructType{
Id: "PATTERN",
Name: "Pattern",
},
Active: true,
}
branchPermissionPayload.Matcher = *matcherConfig
return branchPermissionPayload
}
func resourceBranchPermissionsCreate(d *schema.ResourceData, m interface{}) error {
client := m.(*BitbucketServerProvider).BitbucketClient
project := d.Get("project").(string)
repository := d.Get("repository").(string)
branchPermission := newBranchPermissionPayloadFromResource(d)
request, err := json.Marshal(branchPermission)
if err != nil {
return err
}
res, err := client.Post(fmt.Sprintf("/rest/branch-permissions/2.0/projects/%s/repos/%s/restrictions",
project,
repository,
), bytes.NewBuffer(request))
if err != nil {
return err
}
var branchPermissionResponse BranchPermissionResponse
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
err = json.Unmarshal(body, &branchPermissionResponse)
if err != nil {
return err
}
_ = d.Set("permission_id", branchPermissionResponse.Id)
d.SetId(fmt.Sprintf("%s|%s|%s|%s",
d.Get("project").(string),
d.Get("repository").(string),
d.Get("ref_pattern").(string),
d.Get("type").(string)),
)
return resourceBranchPermissionsRead(d, m)
}
func resourceBranchPermissionsRead(d *schema.ResourceData, m interface{}) error {
id := d.Id()
if id != "" {
parts := strings.Split(id, "|")
if len(parts) == 4 {
_ = d.Set("project", parts[0])
_ = d.Set("repository", parts[1])
_ = d.Set("ref_pattern", parts[2])
_ = d.Set("type", parts[3])
} else {
return fmt.Errorf("incorrect ID format, should match `project|repository|ref_pattern|type`")
}
}
branchPermissionId := d.Get("permission_id")
var err error
if branchPermissionId == nil {
err = getBranchPermissionFromList(d, m)
} else {
err = getBranchPermissionById(d, m)
}
if err != nil {
return err
}
return nil
}
func getBranchPermissionById(d *schema.ResourceData, m interface{}) error {
project := d.Get("project").(string)
repository := d.Get("repository").(string)
id := d.Get("permission_id").(int)
client := m.(*BitbucketServerProvider).BitbucketClient
resp, err := client.Get(fmt.Sprintf("/rest/branch-permissions/2.0/projects/%s/repos/%s/restrictions/%d",
project,
repository,
id,
))
if err != nil {
return err
}
var branchPermissionResponse BranchPermissionResponse
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&branchPermissionResponse)
if err != nil {
return err
}
_ = d.Set("permission_id", branchPermissionResponse.Id)
_ = d.Set("type", branchPermissionResponse.Type)
_ = d.Set("exception_groups", branchPermissionResponse.Groups)
// Convert slice of structs back to slice object for branchPermissionResponse.Users
exceptionUsers := make([]string, 0, len(branchPermissionResponse.Users))
for _, item := range branchPermissionResponse.Users {
exceptionUsers = append(exceptionUsers, item.Name)
}
_ = d.Set("exception_users", exceptionUsers)
// Convert slice of structs back to slice object for branchPermissionResponse.Users
exceptionAccessKeys := make([]int, 0, len(branchPermissionResponse.AccessKeys))
for _, item := range branchPermissionResponse.AccessKeys {
exceptionAccessKeys = append(exceptionAccessKeys, item.Key.ID)
}
_ = d.Set("exception_access_keys", exceptionAccessKeys)
return nil
}
func getBranchPermissionFromList(d *schema.ResourceData, m interface{}) error {
project := d.Get("project").(string)
repository := d.Get("repository").(string)
restrictionType := d.Get("type").(string)
client := m.(*BitbucketServerProvider).BitbucketClient
resp, err := client.Get(fmt.Sprintf("/rest/branch-permissions/2.0/projects/%s/repos/%s/restrictions",
project,
repository,
))
if err != nil {
return err
}
var allRepositoryBranchPermissionsResponse AllRepositoryBranchPermissionsResponse
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&allRepositoryBranchPermissionsResponse)
if err != nil {
return err
}
for _, item := range allRepositoryBranchPermissionsResponse.Values {
if strings.ToLower(strings.Replace(item.Type, "_", "-", -1)) == restrictionType {
_ = d.Set("permission_id", item.Id)
_ = d.Set("type", item.Type)
_ = d.Set("exception_groups", item.Groups)
// Convert slice of structs back to slice object for exception_users
exceptionUsers := make([]string, 0, len(item.Users))
for _, item := range item.Users {
exceptionUsers = append(exceptionUsers, item.Name)
}
_ = d.Set("exception_users", exceptionUsers)
// Convert slice of structs back to slice object for exception_access_keys
exceptionAccessKeys := make([]int, 0, len(item.AccessKeys))
for _, item := range item.AccessKeys {
exceptionAccessKeys = append(exceptionAccessKeys, item.Key.ID)
}
_ = d.Set("exception_access_keys", exceptionAccessKeys)
return nil
}
}
return fmt.Errorf("incorrect ID format, should match `project|repository|ref_pattern`")
}
func resourceBranchPermissionsDelete(d *schema.ResourceData, m interface{}) error {
client := m.(*BitbucketServerProvider).BitbucketClient
_, err := client.Delete(fmt.Sprintf("/rest/branch-permissions/2.0/projects/%s/repos/%s/restrictions/%d",
d.Get("project").(string),
d.Get("repository").(string),
d.Get("permission_id").(int)))
return err
}

View File

@@ -0,0 +1,102 @@
package bitbucket
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccBitbucketResourceBranchPermission_requiredArgumentsOnly(t *testing.T) {
projectKey := fmt.Sprintf("TEST%v", rand.New(rand.NewSource(time.Now().UnixNano())).Int())
config := baseConfigForRepositoryBasedTests(projectKey) + `
resource "bitbucketserver_project_branch_permissions" "test" {
project = bitbucketserver_project.test.key
repository = bitbucketserver_repository.test.slug
ref_pattern = "refs/heads/master"
type = "pull-request-only"
}`
resourceName := "bitbucketserver_project_branch_permissions.test"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%v|repo|refs/heads/master|pull-request-only", projectKey)),
resource.TestCheckResourceAttr(resourceName, "project", projectKey),
resource.TestCheckResourceAttr(resourceName, "repository", "repo"),
resource.TestCheckResourceAttr(resourceName, "ref_pattern", "refs/heads/master"),
resource.TestCheckResourceAttr(resourceName, "type", "pull-request-only"),
resource.TestCheckResourceAttrSet(resourceName, "permission_id"),
),
},
},
})
}
func TestAccBitbucketResourceBranchPermission_allArguments(t *testing.T) {
projectKey := fmt.Sprintf("TEST%v", rand.New(rand.NewSource(time.Now().UnixNano())).Int())
config := baseConfigForRepositoryBasedTests(projectKey) + `
resource "bitbucketserver_group" "test" {
name = bitbucketserver_project.test.name
}
resource "bitbucketserver_group" "test_2" {
name = format("%s-%s", bitbucketserver_project.test.name, "2")
}
resource "bitbucketserver_project_branch_permissions" "test" {
project = bitbucketserver_project.test.key
repository = bitbucketserver_repository.test.slug
ref_pattern = "refs/heads/master"
type = "pull-request-only"
exception_users = ["admin"]
exception_groups = [bitbucketserver_group.test.name, bitbucketserver_group.test_2.name]
}
resource "bitbucketserver_project_branch_permissions" "test_2" {
project = bitbucketserver_project.test.key
repository = bitbucketserver_repository.test.slug
ref_pattern = "refs/heads/master"
type = "no-deletes"
depends_on = [bitbucketserver_project_branch_permissions.test]
}
`
resourceName := "bitbucketserver_project_branch_permissions.test"
resourceName2 := "bitbucketserver_project_branch_permissions.test_2"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%v|repo|refs/heads/master|pull-request-only", projectKey)),
resource.TestCheckResourceAttr(resourceName, "project", projectKey),
resource.TestCheckResourceAttr(resourceName, "repository", "repo"),
resource.TestCheckResourceAttr(resourceName, "ref_pattern", "refs/heads/master"),
resource.TestCheckResourceAttr(resourceName, "type", "pull-request-only"),
resource.TestCheckResourceAttrSet(resourceName, "permission_id"),
resource.TestCheckResourceAttr(resourceName, "exception_users.#", "1"),
resource.TestCheckResourceAttr(resourceName, "exception_users.0", "admin"),
resource.TestCheckResourceAttr(resourceName, "exception_groups.#", "2"),
resource.TestCheckResourceAttr(resourceName, "exception_groups.0", fmt.Sprintf("test-project-%s", projectKey)),
resource.TestCheckResourceAttr(resourceName, "exception_groups.1", fmt.Sprintf("test-project-%s-2", projectKey)),
resource.TestCheckResourceAttr(resourceName2, "type", "no-deletes"),
resource.TestCheckResourceAttrSet(resourceName2, "permission_id"),
),
},
},
})
}