diff --git a/README.md b/README.md index dfe4712..6f24dcb 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,27 @@ $ terraform import bitbucketserver_project_permissions_group.test TEST/stash-use ``` +### Assign Project Permissions for User + +```hcl +resource "bitbucketserver_project_permissions_user" "test" { + project = "TEST" + user = "admin" + permission = "PROJECT_WRITE" +} +``` + +* `project` - Required. Project key to set permissions for. +* `user` - Required. Name of the user 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_user.test TEST/admin +``` + + ### Create a Bitbucket Repository ```hcl @@ -219,6 +240,24 @@ data "bitbucketserver_project_permissions_groups" "proj" { * `groups` - List of maps containing `name` and `permission` keys. + +### Project Permissions Users + +Retrieve a list of users that have been granted at least one permission for the specified project. + +```hcl +data "bitbucketserver_project_permissions_users" "proj" { + project = "TEST" +} +``` + +* `project` - Required. Project Key to lookup permissions for. +* `filter` - Optional. If specified only user names containing the supplied string will be returned. + +#### Attributes + +* `users` - List of maps containing `name`, `email_address`, `display_name`, `active` and `permission` keys. + --- ## Development Guide diff --git a/bitbucket/data_project_permissions_users.go b/bitbucket/data_project_permissions_users.go new file mode 100644 index 0000000..ff3aca8 --- /dev/null +++ b/bitbucket/data_project_permissions_users.go @@ -0,0 +1,158 @@ +package bitbucket + +import ( + "encoding/json" + "fmt" + "github.com/hashicorp/terraform/helper/schema" +) + +type PaginatedProjectPermissionsUsersValue struct { + User struct { + Name string `json:"name,omitempty"` + EmailAddress string `json:"emailAddress,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Active bool `json:"active,omitempty"` + } `json:"user,omitempty"` + Permission string `json:"permission,omitempty"` +} + +type ProjectPermissionsUser struct { + Name string + EmailAddress string `json:"emailAddress,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Active bool `json:"active,omitempty"` + Permission string +} + +type PaginatedProjectPermissionsUsers struct { + Values []PaginatedProjectPermissionsUsersValue `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 dataSourceProjectPermissionsUsers() *schema.Resource { + return &schema.Resource{ + Read: dataSourceProjectPermissionsUsersRead, + + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Required: true, + }, + "filter": { + Type: schema.TypeString, + Optional: true, + }, + "users": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "email_address": { + Type: schema.TypeString, + Computed: true, + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + }, + "active": { + Type: schema.TypeBool, + Computed: true, + }, + "permission": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceProjectPermissionsUsersRead(d *schema.ResourceData, m interface{}) error { + users, err := readProjectPermissionsUsers(m, d.Get("project").(string), d.Get("filter").(string)) + if err != nil { + return err + } + + d.SetId(d.Get("project").(string)) + + var terraformUsers []interface{} + for _, group := range users { + g := make(map[string]interface{}) + g["name"] = group.Name + g["email_address"] = group.EmailAddress + g["display_name"] = group.DisplayName + g["active"] = group.Active + g["permission"] = group.Permission + terraformUsers = append(terraformUsers, g) + } + + d.Set("users", terraformUsers) + return nil +} + +func readProjectPermissionsUsers(m interface{}, project string, filter string) ([]ProjectPermissionsUser, error) { + client := m.(*BitbucketClient) + + resourceURL := fmt.Sprintf("/rest/api/1.0/projects/%s/permissions/users", + project, + ) + + if filter != "" { + resourceURL += "?filter=" + filter + } + + var projectUsers PaginatedProjectPermissionsUsers + var users []ProjectPermissionsUser + + for { + resp, err := client.Get(resourceURL) + if err != nil { + return nil, err + } + + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&projectUsers) + if err != nil { + return nil, err + } + + for _, user := range projectUsers.Values { + g := ProjectPermissionsUser{ + Name: user.User.Name, + EmailAddress: user.User.EmailAddress, + DisplayName: user.User.DisplayName, + Active: user.User.Active, + Permission: user.Permission, + } + users = append(users, g) + } + + if projectUsers.IsLastPage == false { + resourceURL = fmt.Sprintf("/rest/api/1.0/projects/%s/permissions/users?start=%d", + project, + projectUsers.NextPageStart, + ) + + if filter != "" { + resourceURL += "&filter=" + filter + } + + projectUsers = PaginatedProjectPermissionsUsers{} + } else { + break + } + } + + return users, nil +} diff --git a/bitbucket/data_project_permissions_users_test.go b/bitbucket/data_project_permissions_users_test.go new file mode 100644 index 0000000..7e4238c --- /dev/null +++ b/bitbucket/data_project_permissions_users_test.go @@ -0,0 +1,83 @@ +package bitbucket + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBitbucketDataProjectPermissionsUsers_check_creator_included(t *testing.T) { + config := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "TEST%v" + name = "test-repo-for-repository-test" + } + + data "bitbucketserver_project_permissions_users" "test" { + project = bitbucketserver_project.test.key + } + `, 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_users.test", "users.#", "1"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.name", "admin"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.display_name", "Admin"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.email_address", "admin@example.com"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.active", "true"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.permission", "PROJECT_ADMIN"), + ), + }, + }, + }) +} + +func TestAccBitbucketDataProjectPermissionsUsers_additional(t *testing.T) { + config := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "TEST%v" + name = "test-repo-for-repository-test" + } + + resource "bitbucketserver_project_permissions_user" "test" { + project = bitbucketserver_project.test.key + user = "admin2" + permission = "PROJECT_WRITE" + } + + data "bitbucketserver_project_permissions_users" "test" { + project = bitbucketserver_project_permissions_user.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_users.test", "users.#", "2"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.name", "admin"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.display_name", "Admin"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.email_address", "admin@example.com"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.active", "true"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.0.permission", "PROJECT_ADMIN"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.1.name", "admin2"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.1.display_name", "Admin 2"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.1.email_address", "admin2@example.com"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.1.active", "true"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_permissions_users.test", "users.1.permission", "PROJECT_WRITE"), + ), + }, + }, + }) +} diff --git a/bitbucket/provider.go b/bitbucket/provider.go index dc25f54..a0e6175 100644 --- a/bitbucket/provider.go +++ b/bitbucket/provider.go @@ -31,12 +31,14 @@ func Provider() terraform.ResourceProvider { DataSourcesMap: map[string]*schema.Resource{ "bitbucketserver_application_properties": dataSourceApplicationProperties(), "bitbucketserver_project_permissions_groups": dataSourceProjectPermissionsGroups(), + "bitbucketserver_project_permissions_users": dataSourceProjectPermissionsUsers(), }, ResourcesMap: map[string]*schema.Resource{ "bitbucketserver_admin_license": resourceAdminLicense(), "bitbucketserver_admin_mail_server": resourceAdminMailServer(), "bitbucketserver_project": resourceProject(), "bitbucketserver_project_permissions_group": resourceProjectPermissionsGroup(), + "bitbucketserver_project_permissions_user": resourceProjectPermissionsUser(), "bitbucketserver_repository": resourceRepository(), }, } diff --git a/bitbucket/resource_project_permissions_user.go b/bitbucket/resource_project_permissions_user.go new file mode 100644 index 0000000..e07d625 --- /dev/null +++ b/bitbucket/resource_project_permissions_user.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 resourceProjectPermissionsUser() *schema.Resource { + return &schema.Resource{ + Create: resourceProjectPermissionsUserCreate, + Update: resourceProjectPermissionsUserUpdate, + Read: resourceProjectPermissionsUserRead, + Delete: resourceProjectPermissionsUserDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "user": { + 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 resourceProjectPermissionsUserUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketClient) + _, err := client.Put(fmt.Sprintf("/rest/api/1.0/projects/%s/permissions/users?permission=%s&name=%s", + d.Get("project").(string), + url.QueryEscape(d.Get("permission").(string)), + url.QueryEscape(d.Get("user").(string)), + ), nil) + + if err != nil { + return err + } + + return resourceProjectPermissionsUserRead(d, m) +} + +func resourceProjectPermissionsUserCreate(d *schema.ResourceData, m interface{}) error { + err := resourceProjectPermissionsUserUpdate(d, m) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("%s/%s", d.Get("project").(string), d.Get("user").(string))) + return resourceProjectPermissionsUserRead(d, m) +} + +func resourceProjectPermissionsUserRead(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("user", parts[1]) + } else { + return fmt.Errorf("incorrect ID format, should match `project/user`") + } + } + + user := d.Get("user").(string) + users, err := readProjectPermissionsUsers(m, d.Get("project").(string), user) + if err != nil { + return err + } + + // API only filters but we need to find an exact match + for _, g := range users { + if g.Name == user { + d.Set("permission", g.Permission) + break + } + } + + return nil +} + +func resourceProjectPermissionsUserDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketClient) + _, err := client.Delete(fmt.Sprintf("/rest/api/1.0/projects/%s/permissions/users?name=%s", + d.Get("project").(string), + url.QueryEscape(d.Get("user").(string)), + )) + + if err != nil { + return err + } + + return resourceProjectPermissionsUserRead(d, m) +} diff --git a/bitbucket/resource_project_permissions_user_test.go b/bitbucket/resource_project_permissions_user_test.go new file mode 100644 index 0000000..e71bb43 --- /dev/null +++ b/bitbucket/resource_project_permissions_user_test.go @@ -0,0 +1,42 @@ +package bitbucket + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBitbucketResourceProjectPermissionsUser(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_user" "test" { + project = bitbucketserver_project.test.key + user = "admin2" + permission = "PROJECT_READ" + } + `, 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_user.test", "id", projectKey+"/admin2"), + resource.TestCheckResourceAttr("bitbucketserver_project_permissions_user.test", "project", projectKey), + resource.TestCheckResourceAttr("bitbucketserver_project_permissions_user.test", "user", "admin2"), + resource.TestCheckResourceAttr("bitbucketserver_project_permissions_user.test", "permission", "PROJECT_READ"), + ), + }, + }, + }) +}