Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,17 @@ your account, and it will tear them down afterwards to ensure it leaves your acc
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_USERNAME` - Username of the account to run the tests against. Even if `BITBUCKET_AUTH_METHOD` is set to `oauth`, this is still required, as this value is also used as the workspace name.
* `BITBUCKET_PASSWORD` - App Password of the account to run the tests against. Don't set if `BITBUCKET_AUTH_METHOD` is set to `oauth`.
* `BITBUCKET_MEMBER_ACCOUNT_UUID` - Account UUID of the member who is part of your account
* `BITBUCKET_OAUTH_CLIENT_ID` - The "Key" from an [OAuth consumer](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/). Make sure to mark it as private.
* `BITBUCKET_OAUTH_CLIENT_SECRET` - The "Secret" from the OAuth consumer.
* `BITBUCKET_AUTH_METHOD` - If set to `oauth`, it will use the OAuth credentials for all operations, otherwise the username and password. In any case, the OAuth credentials are required for the `NewOAuthClient` test

**NOTE**: `BITBUCKET_PASSWORD` must be an [app password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). If you use the account password, some tests will fail
**NOTE**: if a test fails, it may leave dangling resources in your account so please bear this in mind.
**NOTE**: Tests that create a group permission in a repository (resource `bitbucket_group_permission`) will fail when using OAuth authorization, because only app passwords can be used for that API, see [the official documentation](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-permissions-config-groups-group-slug-put).

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 BITBUCKET_MEMBER_ACCOUNT_UUID=myMemberUUID make testacc
```

### Documentation
Every data source or resource added must have an accompanying docs page (see `docs` directory for examples).
Expand Down
45 changes: 42 additions & 3 deletions bitbucket/api/v1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import (
"log"
"net/http"
"net/url"

"golang.org/x/net/context"
"golang.org/x/oauth2/bitbucket"
"golang.org/x/oauth2/clientcredentials"
)

type Client struct {
Auth *Auth
Auth Auth

ApiBaseUrl *url.URL
HttpClient *http.Client
Expand All @@ -16,12 +20,47 @@ type Client struct {
GroupMembers *GroupMembers
}

type Auth struct {
type Auth interface {
SetRequestAuth(request *http.Request)
}

type BasicAuth struct {
Username string
Password string
}

func NewClient(auth *Auth) *Client {
type BearerAuth struct {
Token string
}

func (auth *BasicAuth) SetRequestAuth(request *http.Request) {
request.SetBasicAuth(auth.Username, auth.Password)
}

func (auth *BearerAuth) SetRequestAuth(request *http.Request) {
request.Header.Set("Authorization", "Bearer "+auth.Token)
}

func NewOAuthClient(clientId string, clientSecret string) *Client {
ctx := context.Background()
conf := &clientcredentials.Config{
ClientID: clientId,
ClientSecret: clientSecret,
TokenURL: bitbucket.Endpoint.TokenURL,
}

tok, err := conf.Token(ctx)
if err != nil {
log.Fatal(err)
}
return newClient(&BearerAuth{Token: tok.AccessToken})
}

func NewBasicAuthClient(username string, password string) *Client {
return newClient(&BasicAuth{Username: username, Password: password})
}

func newClient(auth Auth) *Client {
apiBaseUrl, err := url.Parse("https://api.bitbucket.org/1.0")
if err != nil {
log.Fatal(err)
Expand Down
23 changes: 16 additions & 7 deletions bitbucket/api/v1/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@ package v1

import (
"net/http"
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewClient(t *testing.T) {
auth := &Auth{
Username: "test",
Password: "test",
}
client := NewClient(auth)
func TestNewBasicAuthClient(t *testing.T) {
client := NewBasicAuthClient("test", "password")

assert.Equal(t, "https://api.bitbucket.org/1.0", client.ApiBaseUrl.String())
assert.Equal(t, auth, client.Auth)
assert.IsType(t, &BasicAuth{}, client.Auth)
assert.Equal(t, client.Auth.(*BasicAuth).Username, "test")
assert.Equal(t, client.Auth.(*BasicAuth).Password, "password")
assert.IsType(t, &Groups{}, client.Groups)
assert.IsType(t, &http.Client{}, client.HttpClient)
}

func TestNewOAuthClient(t *testing.T) {
client := NewOAuthClient(os.Getenv("BITBUCKET_OAUTH_CLIENT_ID"), os.Getenv("BITBUCKET_OAUTH_CLIENT_SECRET"))

assert.Equal(t, "https://api.bitbucket.org/1.0", client.ApiBaseUrl.String())
assert.IsType(t, &BearerAuth{}, client.Auth)
assert.NotEmpty(t, client.Auth.(*BearerAuth).Token)
assert.IsType(t, &Groups{}, client.Groups)
assert.IsType(t, &http.Client{}, client.HttpClient)
}
6 changes: 3 additions & 3 deletions bitbucket/api/v1/group_members.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (gm *GroupMembers) Get(gmo *GroupMemberOptions) ([]GroupMember, error) {
return nil, err
}

request.SetBasicAuth(gm.client.Auth.Username, gm.client.Auth.Password)
gm.client.Auth.SetRequestAuth(request)

response, err := gm.client.HttpClient.Do(request)
if err != nil {
Expand Down Expand Up @@ -68,7 +68,7 @@ func (gm *GroupMembers) Create(gmo *GroupMemberOptions) (*GroupMember, error) {
return nil, err
}

request.SetBasicAuth(gm.client.Auth.Username, gm.client.Auth.Password)
gm.client.Auth.SetRequestAuth(request)
request.Header.Set("Content-Type", "application/json")

response, err := gm.client.HttpClient.Do(request)
Expand Down Expand Up @@ -98,7 +98,7 @@ func (gm *GroupMembers) Delete(gmo *GroupMemberOptions) error {
return err
}

request.SetBasicAuth(gm.client.Auth.Username, gm.client.Auth.Password)
gm.client.Auth.SetRequestAuth(request)

response, err := gm.client.HttpClient.Do(request)
if err != nil {
Expand Down
18 changes: 8 additions & 10 deletions bitbucket/api/v1/group_members_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,15 @@ func TestGroupMembers(t *testing.T) {
t.Skip("ENV TF_ACC=1 not set")
}

c := NewClient(&Auth{
Username: os.Getenv("BITBUCKET_USERNAME"),
Password: os.Getenv("BITBUCKET_PASSWORD"),
})
c := NewClient()

var group *Group
owner := os.Getenv("BITBUCKET_USERNAME")

t.Run("setup", func(t *testing.T) {
group, _ = c.Groups.Create(
&GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Name: "tf-bb-group-members-test" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum),
},
)
Expand All @@ -33,7 +31,7 @@ func TestGroupMembers(t *testing.T) {
t.Run("create", func(t *testing.T) {
result, err := c.GroupMembers.Create(
&GroupMemberOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: group.Slug,
UserUuid: group.Owner.Uuid,
},
Expand All @@ -47,7 +45,7 @@ func TestGroupMembers(t *testing.T) {
t.Run("get", func(t *testing.T) {
members, err := c.GroupMembers.Get(
&GroupMemberOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: group.Slug,
},
)
Expand All @@ -60,7 +58,7 @@ func TestGroupMembers(t *testing.T) {
t.Run("delete", func(t *testing.T) {
err := c.GroupMembers.Delete(
&GroupMemberOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: group.Slug,
UserUuid: group.Owner.Uuid,
},
Expand All @@ -69,7 +67,7 @@ func TestGroupMembers(t *testing.T) {

members, err := c.GroupMembers.Get(
&GroupMemberOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: group.Slug,
},
)
Expand All @@ -79,7 +77,7 @@ func TestGroupMembers(t *testing.T) {

t.Run("teardown", func(t *testing.T) {
opt := &GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: group.Slug,
}
err := c.Groups.Delete(opt)
Expand Down
8 changes: 4 additions & 4 deletions bitbucket/api/v1/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (g *Groups) Get(gro *GroupOptions) (*Group, error) {
return nil, err
}

request.SetBasicAuth(g.client.Auth.Username, g.client.Auth.Password)
g.client.Auth.SetRequestAuth(request)

response, err := g.client.HttpClient.Do(request)
if err != nil {
Expand Down Expand Up @@ -74,7 +74,7 @@ func (g *Groups) Create(gro *GroupOptions) (*Group, error) {
return nil, err
}

request.SetBasicAuth(g.client.Auth.Username, g.client.Auth.Password)
g.client.Auth.SetRequestAuth(request)
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

response, err := g.client.HttpClient.Do(request)
Expand Down Expand Up @@ -127,7 +127,7 @@ func (g *Groups) Update(gro *GroupOptions) (*Group, error) {
return nil, err
}

request.SetBasicAuth(g.client.Auth.Username, g.client.Auth.Password)
g.client.Auth.SetRequestAuth(request)
request.Header.Set("Content-Type", "application/json")

response, err := g.client.HttpClient.Do(request)
Expand Down Expand Up @@ -157,7 +157,7 @@ func (g *Groups) Delete(gro *GroupOptions) error {
return err
}

request.SetBasicAuth(g.client.Auth.Username, g.client.Auth.Password)
g.client.Auth.SetRequestAuth(request)

response, err := g.client.HttpClient.Do(request)
if err != nil {
Expand Down
26 changes: 11 additions & 15 deletions bitbucket/api/v1/groups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@ func TestGroups(t *testing.T) {
t.Skip("ENV TF_ACC=1 not set")
}

c := NewClient(&Auth{
Username: os.Getenv("BITBUCKET_USERNAME"),
Password: os.Getenv("BITBUCKET_PASSWORD"),
})
c := NewClient()

var groupResourceSlug string

name := "tf-bb-group-test" + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
owner := os.Getenv("BITBUCKET_USERNAME")

t.Run("create", func(t *testing.T) {
opt := &GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Name: name,
}

Expand All @@ -39,7 +37,7 @@ func TestGroups(t *testing.T) {

t.Run("get", func(t *testing.T) {
opt := &GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: groupResourceSlug,
}
group, err := c.Groups.Get(opt)
Expand All @@ -52,7 +50,7 @@ func TestGroups(t *testing.T) {

t.Run("update", func(t *testing.T) {
opt := &GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: groupResourceSlug,
Permission: "write",
}
Expand All @@ -66,7 +64,7 @@ func TestGroups(t *testing.T) {

t.Run("delete", func(t *testing.T) {
opt := &GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: groupResourceSlug,
}
err := c.Groups.Delete(opt)
Expand All @@ -79,18 +77,16 @@ func TestGroupsGracefullyHandleNoReturnedGroupsForInvalidSlug(t *testing.T) {
t.Skip("ENV TF_ACC=1 not set")
}

c := NewClient(&Auth{
Username: os.Getenv("BITBUCKET_USERNAME"),
Password: os.Getenv("BITBUCKET_PASSWORD"),
})
c := NewClient()

var groupResourceSlug string

name := "TF-BB-Group-Test"
owner := os.Getenv("BITBUCKET_USERNAME")

t.Run("create", func(t *testing.T) {
opt := &GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Name: name,
}

Expand All @@ -105,7 +101,7 @@ func TestGroupsGracefullyHandleNoReturnedGroupsForInvalidSlug(t *testing.T) {

t.Run("get", func(t *testing.T) {
opt := &GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: name, // Slugs are lowercase and the BB's API is case-sensitive, this will trigger a fail response
}
group, err := c.Groups.Get(opt)
Expand All @@ -115,7 +111,7 @@ func TestGroupsGracefullyHandleNoReturnedGroupsForInvalidSlug(t *testing.T) {

t.Run("delete", func(t *testing.T) {
opt := &GroupOptions{
OwnerUuid: c.Auth.Username,
OwnerUuid: owner,
Slug: groupResourceSlug,
}
err := c.Groups.Delete(opt)
Expand Down
22 changes: 22 additions & 0 deletions bitbucket/api/v1/test_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package v1

import (
"os"
"strings"
)

// NewAuthenticatedBasicClient creates a new BasicClient with credentials from environment variables
func NewClient() *Client {
username := os.Getenv("BITBUCKET_USERNAME")
password := os.Getenv("BITBUCKET_PASSWORD")
oauthClientId := os.Getenv("BITBUCKET_OAUTH_CLIENT_ID")
oauthClientSecret := os.Getenv("BITBUCKET_OAUTH_CLIENT_SECRET")
authMethod := os.Getenv("BITBUCKET_AUTH_METHOD")

// no detailed check necessary, it was already performed by provider_test.go
if strings.EqualFold(authMethod, "oauth") {
return NewOAuthClient(oauthClientId, oauthClientSecret)
} else {
return NewBasicAuthClient(username, password)
}
}
5 changes: 1 addition & 4 deletions bitbucket/data_source_bitbucket_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ func TestAccBitbucketUserDataSource_basic(t *testing.T) {

func getCurrentUser() (*gobb.User, error) {
if _, isSet := os.LookupEnv("TF_ACC"); isSet {
client := gobb.NewBasicAuth(
os.Getenv("BITBUCKET_USERNAME"),
os.Getenv("BITBUCKET_PASSWORD"),
)
client := NewClient()

return client.User.Profile()
} else {
Expand Down
Loading