diff --git a/README.md b/README.md index 8a0a281..dfe4712 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ provider "bitbucketserver" { You can also specify these parameters through the `BITBUCKET_SERVER`, `BITBUCKER_USERNAME` and `BITBUCKET_PASSWORD` environment variables. + ### Create a Bitbucket Project ```hcl @@ -59,7 +60,7 @@ resource "bitbucketserver_project" "test" { } ``` -* `key` - Required. Project key to set +* `key` - Required. Project key to set. * `name` - Required. Name of the project. * `description` - Optional. Description of the project. * `avatar` - Optional. Avatar to use containing base64-encoded image data. Format: `data:(content type, e.g. image/png);base64,(data)` @@ -70,6 +71,28 @@ resource "bitbucketserver_project" "test" { $ terraform import bitbucketserver_project.test TEST ``` + +### Assign Project Permissions for Group + +```hcl +resource "bitbucketserver_project_permissions_group" "test" { + project = "TEST" + group = "stash-users" + permission = "PROJECT_WRITE" +} +``` + +* `project` - Required. Project key to set permissions for. +* `group` - Required. Name of the group permissions are for. +* `permission` - Required. The permission to grant. Available project permissions are: `PROJECT_READ`, `PROJECT_WRITE`, `PROJECT_ADMIN` + +#### Import Group Permission for Project + +```bash +$ terraform import bitbucketserver_project_permissions_group.test TEST/stash-users +``` + + ### Create a Bitbucket Repository ```hcl @@ -98,6 +121,7 @@ Additional to the above, the following attributes are emitted: $ terraform import bitbucketserver_repository.test TEST/test-01 ``` + ### Set Server License ```hcl @@ -130,6 +154,7 @@ Additional to the above, the following attributes are emitted: $ terraform import bitbucketserver_admin_license.main license ``` + ### Set Mail Server Configuration ```hcl @@ -160,8 +185,11 @@ $ terraform import bitbucketserver_admin_mail_server.mail mail.example.com ## Data Sources + ### Application Properties +Retrieve version information and other application properties. + ```hcl data "bitbucketserver_application_properties" "main" {} ``` @@ -173,7 +201,10 @@ data "bitbucketserver_application_properties" "main" {} * `build_date` - Date the Bitbucket build was made, * `display_name` - Name of the Bitbucket instance. -### Application Properties + +### Project Permissions Groups + +Retrieve a list of groups that have been granted at least one permission for the specified project. ```hcl data "bitbucketserver_project_permissions_groups" "proj" { @@ -181,6 +212,9 @@ data "bitbucketserver_project_permissions_groups" "proj" { } ``` +* `project` - Required. Project Key to lookup permissions for. +* `filter` - Optional. If specified only group names containing the supplied string will be returned. + #### Attributes * `groups` - List of maps containing `name` and `permission` keys. diff --git a/bitbucket/data_project_permissions_groups.go b/bitbucket/data_project_permissions_groups.go index 099744f..09b6f4e 100644 --- a/bitbucket/data_project_permissions_groups.go +++ b/bitbucket/data_project_permissions_groups.go @@ -6,20 +6,25 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) -type ProjectPermissionsGroup struct { +type PaginatedProjectPermissionsGroupsValue struct { Group struct { Name string `json:"name,omitempty"` } `json:"group,omitempty"` Permission string `json:"permission,omitempty"` } +type ProjectPermissionsGroup struct { + Name string + Permission string +} + type PaginatedProjectPermissionsGroups struct { - Values []ProjectPermissionsGroup `json:"values,omitempty"` - Size int `json:"size,omitempty"` - Limit int `json:"limit,omitempty"` - IsLastPage bool `json:"isLastPage,omitempty"` - Start int `json:"start,omitempty"` - NextPageStart int `json:"nextPageStart,omitempty"` + Values []PaginatedProjectPermissionsGroupsValue `json:"values,omitempty"` + Size int `json:"size,omitempty"` + Limit int `json:"limit,omitempty"` + IsLastPage bool `json:"isLastPage,omitempty"` + Start int `json:"start,omitempty"` + NextPageStart int `json:"nextPageStart,omitempty"` } func dataSourceProjectPermissionsGroups() *schema.Resource { @@ -31,6 +36,10 @@ func dataSourceProjectPermissionsGroups() *schema.Resource { Type: schema.TypeString, Required: true, }, + "filter": { + Type: schema.TypeString, + Optional: true, + }, "groups": { Type: schema.TypeList, Computed: true, @@ -52,46 +61,74 @@ func dataSourceProjectPermissionsGroups() *schema.Resource { } func dataSourceProjectPermissionsGroupsRead(d *schema.ResourceData, m interface{}) error { + groups, err := readProjectPermissionsGroups(m, d.Get("project").(string), d.Get("filter").(string)) + if err != nil { + return err + } + + d.SetId(d.Get("project").(string)) + + var terraformGroups []interface{} + for _, group := range groups { + g := make(map[string]interface{}) + g["name"] = group.Name + g["permission"] = group.Permission + terraformGroups = append(terraformGroups, g) + } + + d.Set("groups", terraformGroups) + return nil +} + +func readProjectPermissionsGroups(m interface{}, project string, filter string) ([]ProjectPermissionsGroup, error) { client := m.(*BitbucketClient) resourceURL := fmt.Sprintf("/rest/api/1.0/projects/%s/permissions/groups", - d.Get("project").(string), + project, ) + if filter != "" { + resourceURL += "?filter=" + filter + } + var projectGroups PaginatedProjectPermissionsGroups - var terraformGroups []interface{} + var groups []ProjectPermissionsGroup for { - reviewersResponse, err := client.Get(resourceURL) + resp, err := client.Get(resourceURL) if err != nil { - return err + return nil, err } - decoder := json.NewDecoder(reviewersResponse.Body) + decoder := json.NewDecoder(resp.Body) err = decoder.Decode(&projectGroups) if err != nil { - return err + return nil, err } for _, group := range projectGroups.Values { - g := make(map[string]interface{}) - g["name"] = group.Group.Name - g["permission"] = group.Permission - terraformGroups = append(terraformGroups, g) + g := ProjectPermissionsGroup{ + Name: group.Group.Name, + Permission: group.Permission, + } + groups = append(groups, g) } if projectGroups.IsLastPage == false { resourceURL = fmt.Sprintf("/rest/api/1.0/projects/%s/permissions/groups?start=%d", - d.Get("project").(string), + project, projectGroups.NextPageStart, ) + + if filter != "" { + resourceURL += "&filter=" + filter + } + projectGroups = PaginatedProjectPermissionsGroups{} } else { break } } - d.SetId(d.Get("project").(string)) - d.Set("groups", terraformGroups) - return nil + return groups, nil } diff --git a/bitbucket/data_project_permissions_groups_test.go b/bitbucket/data_project_permissions_groups_test.go index 137fce8..50b1323 100644 --- a/bitbucket/data_project_permissions_groups_test.go +++ b/bitbucket/data_project_permissions_groups_test.go @@ -9,13 +9,47 @@ import ( "github.com/hashicorp/terraform/helper/resource" ) -func TestAccBitbucketDataProjectPermissionsGroups_check_empty(t *testing.T) { +func TestAccBitbucketDataProjectPermissionsGroups_simple(t *testing.T) { config := fmt.Sprintf(` resource "bitbucketserver_project" "test" { key = "TEST%v" name = "test-repo-for-repository-test" } + + resource "bitbucketserver_project_permissions_group" "test" { + project = bitbucketserver_project.test.key + group = "stash-users" + permission = "PROJECT_WRITE" + } + data "bitbucketserver_project_permissions_groups" "test" { + project = bitbucketserver_project_permissions_group.test.project + } + `, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_groups.test", "groups.#", "1"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_groups.test", "groups.0.name", "stash-users"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_groups.test", "groups.0.permission", "PROJECT_WRITE"), + ), + }, + }, + }) +} + +func TestAccBitbucketDataProjectPermissionsGroups_check_empty(t *testing.T) { + config := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "TEST%v" + name = "test-repo-for-repository-test" + } + data "bitbucketserver_project_permissions_groups" "test" { project = bitbucketserver_project.test.key } diff --git a/bitbucket/provider.go b/bitbucket/provider.go index 6643e86..dc25f54 100644 --- a/bitbucket/provider.go +++ b/bitbucket/provider.go @@ -33,10 +33,11 @@ func Provider() terraform.ResourceProvider { "bitbucketserver_project_permissions_groups": dataSourceProjectPermissionsGroups(), }, ResourcesMap: map[string]*schema.Resource{ - "bitbucketserver_admin_license": resourceAdminLicense(), - "bitbucketserver_admin_mail_server": resourceAdminMailServer(), - "bitbucketserver_project": resourceProject(), - "bitbucketserver_repository": resourceRepository(), + "bitbucketserver_admin_license": resourceAdminLicense(), + "bitbucketserver_admin_mail_server": resourceAdminMailServer(), + "bitbucketserver_project": resourceProject(), + "bitbucketserver_project_permissions_group": resourceProjectPermissionsGroup(), + "bitbucketserver_repository": resourceRepository(), }, } } diff --git a/bitbucket/resource_project_permissions_group.go b/bitbucket/resource_project_permissions_group.go new file mode 100644 index 0000000..9c5e414 --- /dev/null +++ b/bitbucket/resource_project_permissions_group.go @@ -0,0 +1,107 @@ +package bitbucket + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "net/url" + "strings" +) + +func resourceProjectPermissionsGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceProjectPermissionsGroupCreate, + Update: resourceProjectPermissionsGroupUpdate, + Read: resourceProjectPermissionsGroupRead, + Delete: resourceProjectPermissionsGroupDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "group": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "permission": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"PROJECT_READ", "PROJECT_WRITE", "PROJECT_ADMIN"}, false), + }, + }, + } +} + +func resourceProjectPermissionsGroupUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketClient) + _, err := client.Put(fmt.Sprintf("/rest/api/1.0/projects/%s/permissions/groups?permission=%s&name=%s", + d.Get("project").(string), + url.QueryEscape(d.Get("permission").(string)), + url.QueryEscape(d.Get("group").(string)), + ), nil) + + if err != nil { + return err + } + + return resourceProjectPermissionsGroupRead(d, m) +} + +func resourceProjectPermissionsGroupCreate(d *schema.ResourceData, m interface{}) error { + err := resourceProjectPermissionsGroupUpdate(d, m) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("%s/%s", d.Get("project").(string), d.Get("group").(string))) + return resourceProjectPermissionsGroupRead(d, m) +} + +func resourceProjectPermissionsGroupRead(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id != "" { + parts := strings.Split(id, "/") + if len(parts) == 2 { + d.Set("project", parts[0]) + d.Set("group", parts[1]) + } else { + return fmt.Errorf("incorrect ID format, should match `project/group`") + } + } + + group := d.Get("group").(string) + groups, err := readProjectPermissionsGroups(m, d.Get("project").(string), group) + if err != nil { + return err + } + + // API only filters but we need to find an exact match + for _, g := range groups { + if g.Name == group { + d.Set("permission", g.Permission) + break + } + } + + return nil +} + +func resourceProjectPermissionsGroupDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketClient) + _, err := client.Delete(fmt.Sprintf("/rest/api/1.0/projects/%s/permissions/groups?name=%s", + d.Get("project").(string), + url.QueryEscape(d.Get("group").(string)), + )) + + if err != nil { + return err + } + + return resourceProjectPermissionsGroupRead(d, m) +} diff --git a/bitbucket/resource_project_permissions_group_test.go b/bitbucket/resource_project_permissions_group_test.go new file mode 100644 index 0000000..994f8ae --- /dev/null +++ b/bitbucket/resource_project_permissions_group_test.go @@ -0,0 +1,42 @@ +package bitbucket + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBitbucketResourceProjectPermissionsGroup(t *testing.T) { + projectKey := fmt.Sprintf("TEST%v", rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + config := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "%v" + name = "test-repo-for-repository-test" + } + + resource "bitbucketserver_project_permissions_group" "test" { + project = bitbucketserver_project.test.key + group = "stash-users" + permission = "PROJECT_WRITE" + } + `, projectKey) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("bitbucketserver_project_permissions_group.test", "id", projectKey+"/stash-users"), + resource.TestCheckResourceAttr("bitbucketserver_project_permissions_group.test", "project", projectKey), + resource.TestCheckResourceAttr("bitbucketserver_project_permissions_group.test", "group", "stash-users"), + resource.TestCheckResourceAttr("bitbucketserver_project_permissions_group.test", "permission", "PROJECT_WRITE"), + ), + }, + }, + }) +}