Skip to content

Commit

Permalink
GH-111 Add user permission resource & data source (#119)
Browse files Browse the repository at this point in the history
Along with docs.
  • Loading branch information
zahiar authored Aug 29, 2022
1 parent c716d34 commit 9b6cf02
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 3 deletions.
1 change: 1 addition & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ jobs:
env:
BITBUCKET_USERNAME: ${{ secrets.BITBUCKET_USERNAME }}
BITBUCKET_PASSWORD: ${{ secrets.BITBUCKET_PASSWORD }}
BITBUCKET_MEMBER_ACCOUNT_UUID: ${{ secrets.BITBUCKET_MEMBER_ACCOUNT_UUID }}
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,21 @@ $ make test
```

### Acceptance Tests
This will require you to specify two environment variables (`BITBUCKET_USERNAME` & `BITBUCKET_PASSWORD`), as these tests
will provision actual resources in your account, and it will tear them down afterwards to ensure it leaves your account clean.
This will require you to specify the following environment variables, as these tests will provision actual resources in
your account, and it will tear them down afterwards to ensure it leaves your account clean.

You will also require a UUID of another account that is a member of your workspace in order for the `bitbucket_user_permission`
tests to run, as Bitbucket's API will reject the account owner's UUID.

* `BITBUCKET_USERNAME` - Username of the account to run the tests against
* `BITBUCKET_PASSWORD` - Password of the account to run the tests against
* `BITBUCKET_MEMBER_ACCOUNT_UUID` - Account UUID of the member who is part of your account

**NOTE**: if a test fails, it may leave dangling resources in your account so please bear this in mind.

If you have two-factor authentication enabled, then be sure to set up an [app password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) and use that instead.
```shell
$ BITBUCKET_USERNAME=myUsername BITBUCKET_PASSWORD=myPassword make testacc
$ BITBUCKET_USERNAME=myUsername BITBUCKET_PASSWORD=myPassword BITBUCKET_MEMBER_ACCOUNT_UUID=myMemberUUID make testacc
```

### Documentation
Expand Down
39 changes: 39 additions & 0 deletions bitbucket/data_source_bitbucket_user_permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package bitbucket

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataSourceBitbucketUserPermission() *schema.Resource {
return &schema.Resource{
ReadContext: resourceBitbucketUserPermissionRead,
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the user permission.",
Type: schema.TypeString,
Computed: true,
},
"workspace": {
Description: "The UUID (including the enclosing `{}`) of the workspace.",
Type: schema.TypeString,
Required: true,
},
"repository": {
Description: "The slug of the repository (must consist of only lowercase ASCII letters, numbers, underscores and hyphens).",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validateRepositoryName,
},
"user": {
Description: "The UUID (including the enclosing `{}`) of the user.",
Type: schema.TypeString,
Required: true,
},
"permission": {
Description: "The permission this user will have. Must be one of 'read', 'write', 'admin'.",
Type: schema.TypeString,
Computed: true,
},
},
}
}
69 changes: 69 additions & 0 deletions bitbucket/data_source_bitbucket_user_permission_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package bitbucket

import (
"fmt"
"os"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccBitbucketUserPermissionDataSource_basic(t *testing.T) {
workspaceSlug := os.Getenv("BITBUCKET_USERNAME")
projectName := "tf-acc-test-" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
projectKey := strings.ToUpper(acctest.RandStringFromCharSet(3, acctest.CharSetAlpha))
repoName := "tf-acc-test-" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
user := os.Getenv("BITBUCKET_MEMBER_ACCOUNT_UUID")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
data "bitbucket_workspace" "testacc" {
id = "%s"
}
resource "bitbucket_project" "testacc" {
workspace = data.bitbucket_workspace.testacc.id
name = "%s"
key = "%s"
}
resource "bitbucket_repository" "testacc" {
workspace = data.bitbucket_workspace.testacc.id
project_key = bitbucket_project.testacc.key
name = "%s"
}
data "bitbucket_user" "testacc" {
id = "%s"
}
resource "bitbucket_user_permission" "testacc" {
workspace = data.bitbucket_workspace.testacc.uuid
repository = bitbucket_repository.testacc.name
user = data.bitbucket_user.testacc.id
permission = "read"
}
data "bitbucket_user_permission" "testacc" {
workspace = data.bitbucket_workspace.testacc.uuid
repository = bitbucket_repository.testacc.name
user = data.bitbucket_user.testacc.id
}`, workspaceSlug, projectName, projectKey, repoName, user),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.bitbucket_user_permission.testacc", "repository", repoName),
resource.TestCheckResourceAttr("data.bitbucket_user_permission.testacc", "user", user),

resource.TestCheckResourceAttrSet("data.bitbucket_user_permission.testacc", "id"),
resource.TestCheckResourceAttrSet("data.bitbucket_user_permission.testacc", "workspace"),
resource.TestCheckResourceAttrSet("data.bitbucket_user_permission.testacc", "permission"),
),
},
},
})
}
2 changes: 2 additions & 0 deletions bitbucket/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func Provider() *schema.Provider {
"bitbucket_project": dataSourceBitbucketProject(),
"bitbucket_repository": dataSourceBitbucketRepository(),
"bitbucket_user": dataSourceBitbucketUser(),
"bitbucket_user_permission": dataSourceBitbucketUserPermission(),
"bitbucket_user_workspace": dataSourceBitbucketUserWorkspace(),
"bitbucket_webhook": dataSourceBitbucketWebhook(),
"bitbucket_workspace": dataSourceBitbucketWorkspace(),
Expand All @@ -56,6 +57,7 @@ func Provider() *schema.Provider {
"bitbucket_pipeline_variable": resourceBitbucketPipelineVariable(),
"bitbucket_project": resourceBitbucketProject(),
"bitbucket_repository": resourceBitbucketRepository(),
"bitbucket_user_permission": resourceBitbucketUserPermission(),
"bitbucket_webhook": resourceBitbucketWebhook(),
},

Expand Down
137 changes: 137 additions & 0 deletions bitbucket/resource_bitbucket_user_permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package bitbucket

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/ktrysmt/go-bitbucket"
)

func resourceBitbucketUserPermission() *schema.Resource {
return &schema.Resource{
CreateContext: resourceBitbucketUserPermissionCreate,
ReadContext: resourceBitbucketUserPermissionRead,
DeleteContext: resourceBitbucketUserPermissionDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceBitbucketUserPermissionImport,
},
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the user permission.",
Type: schema.TypeString,
Computed: true,
},
"workspace": {
Description: "The UUID (including the enclosing `{}`) of the workspace.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"repository": {
Description: "The slug of the repository (must consist of only lowercase ASCII letters, numbers, underscores and hyphens).",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validateRepositoryName,
ForceNew: true,
},
"user": {
Description: "The UUID (including the enclosing `{}`) of the user.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"permission": {
Description: "The permission this user will have. Must be one of 'read', 'write', 'admin'.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"read", "write", "admin"}, false),
ForceNew: true,
},
},
}
}

func resourceBitbucketUserPermissionCreate(ctx context.Context, resourceData *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*Clients).V2

_, err := client.Repositories.Repository.SetUserPermissions(&bitbucket.RepositoryUserPermissionsOptions{
Owner: resourceData.Get("workspace").(string),
RepoSlug: resourceData.Get("repository").(string),
User: resourceData.Get("user").(string),
Permission: resourceData.Get("permission").(string),
})

if err != nil {
return diag.FromErr(fmt.Errorf("unable to create user permission with error: %s", err))
}

return resourceBitbucketUserPermissionRead(ctx, resourceData, meta)
}

func resourceBitbucketUserPermissionRead(ctx context.Context, resourceData *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*Clients).V2

workspace := resourceData.Get("workspace").(string)
repository := resourceData.Get("repository").(string)

userPermission, err := client.Repositories.Repository.GetUserPermissions(&bitbucket.RepositoryUserPermissionsOptions{
Owner: workspace,
RepoSlug: repository,
User: resourceData.Get("user").(string),
Permission: resourceData.Get("permission").(string),
})
if err != nil {
return diag.FromErr(fmt.Errorf("unable to get user permission with error: %s", err))
}

_ = resourceData.Set("user", userPermission.User.Uuid)
_ = resourceData.Set("permission", userPermission.Permission)

resourceData.SetId(generateUserPermissionResourceId(workspace, repository, userPermission.User.Uuid))

return nil
}

func resourceBitbucketUserPermissionDelete(ctx context.Context, resourceData *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*Clients).V2

_, err := client.Repositories.Repository.DeleteUserPermissions(&bitbucket.RepositoryUserPermissionsOptions{
Owner: resourceData.Get("workspace").(string),
RepoSlug: resourceData.Get("repository").(string),
User: resourceData.Get("user").(string),
Permission: resourceData.Get("permission").(string),
})

if err != nil {
return diag.FromErr(fmt.Errorf("unable to delete user permission with error: %s", err))
}

resourceData.SetId("")

return nil
}

func resourceBitbucketUserPermissionImport(ctx context.Context, resourceData *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
ret := []*schema.ResourceData{resourceData}

splitID := strings.Split(resourceData.Id(), "/")
if len(splitID) < 3 {
return ret, fmt.Errorf("invalid import ID. It must to be in this format \"<workspace-uuid>/<repo-slug>/<user-uuid>\"")
}

_ = resourceData.Set("workspace", splitID[0])
_ = resourceData.Set("repository", splitID[1])
_ = resourceData.Set("user", splitID[2])

_ = resourceBitbucketUserPermissionRead(ctx, resourceData, meta)

return ret, nil
}

func generateUserPermissionResourceId(workspace string, repo string, user string) string {
return fmt.Sprintf("%s-%s-%s", workspace, repo, user)
}
81 changes: 81 additions & 0 deletions bitbucket/resource_bitbucket_user_permission_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package bitbucket

import (
"fmt"
"os"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/stretchr/testify/assert"
)

func TestAccBitbucketUserPermissionResource_basic(t *testing.T) {
workspaceSlug := os.Getenv("BITBUCKET_USERNAME")
projectName := "tf-acc-test-" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
projectKey := strings.ToUpper(acctest.RandStringFromCharSet(3, acctest.CharSetAlpha))
repoName := "tf-acc-test-" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
user := os.Getenv("BITBUCKET_MEMBER_ACCOUNT_UUID")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
data "bitbucket_workspace" "testacc" {
id = "%s"
}
resource "bitbucket_project" "testacc" {
workspace = data.bitbucket_workspace.testacc.id
name = "%s"
key = "%s"
}
resource "bitbucket_repository" "testacc" {
workspace = data.bitbucket_workspace.testacc.id
project_key = bitbucket_project.testacc.key
name = "%s"
}
data "bitbucket_user" "testacc" {
id = "%s"
}
resource "bitbucket_user_permission" "testacc" {
workspace = data.bitbucket_workspace.testacc.uuid
repository = bitbucket_repository.testacc.name
user = data.bitbucket_user.testacc.id
permission = "read"
}`, workspaceSlug, projectName, projectKey, repoName, user),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("bitbucket_user_permission.testacc", "repository", repoName),
resource.TestCheckResourceAttr("bitbucket_user_permission.testacc", "user", user),
resource.TestCheckResourceAttr("bitbucket_user_permission.testacc", "permission", "read"),

resource.TestCheckResourceAttrSet("bitbucket_user_permission.testacc", "id"),
resource.TestCheckResourceAttrSet("bitbucket_user_permission.testacc", "workspace"),
),
},
{
ResourceName: "bitbucket_user_permission.testacc",
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: func(state *terraform.State) (string, error) {
resources := state.Modules[0].Resources
workspaceResourceAttr := resources["data.bitbucket_workspace.testacc"].Primary.Attributes
return fmt.Sprintf("%s/%s/%s", workspaceResourceAttr["uuid"], repoName, user), nil
},
},
},
})
}

func TestGenerateUserPermissionResourceId(t *testing.T) {
expected := "{my-workspace-uuid}-my-test-repo-{my-user-uuid}"
result := generateUserPermissionResourceId("{my-workspace-uuid}", "my-test-repo", "{my-user-uuid}")
assert.Equal(t, expected, result)
}
22 changes: 22 additions & 0 deletions docs/data-sources/bitbucket_user_permission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Data Source: bitbucket_user_permission
Use this data source to get the user permission resource, you can then reference its attributes without having to hardcode them.

## Example Usage
```hcl
data "bitbucket_user_permission" "example" {
workspace = "{workspace-uuid}"
repository = "example-repository"
user = "{user-uuid}"
}
```

## Argument Reference
The following arguments are supported:
* `workspace` - (Required) The UUID (including the enclosing `{}`) of the workspace.
* `repository` - (Required) The slug of the repository.
* `user` - (Required) The UUID (including the enclosing `{}`) of the user.

## Attribute Reference
In addition to the arguments above, the following additional attributes are exported:
* `id` - The ID of the user permission.
* `permission` - The permission this user will have. Is one of 'read', 'write', or 'admin'.
Loading

0 comments on commit 9b6cf02

Please sign in to comment.