From a4982b37489330bcd75495043a13a6b7d0704152 Mon Sep 17 00:00:00 2001 From: Gavin Bunney Date: Tue, 15 Oct 2019 13:47:59 -0700 Subject: [PATCH] Added `bitbucket_user_access_token` resource --- bitbucket/provider.go | 1 + bitbucket/resource_user_access_token.go | 188 ++++++++++++++++++ bitbucket/resource_user_access_token_test.go | 54 +++++ docusaurus/docs/resource_user_access_token.md | 48 +++++ docusaurus/website/i18n/en.json | 3 + docusaurus/website/sidebars.json | 1 + 6 files changed, 295 insertions(+) create mode 100644 bitbucket/resource_user_access_token.go create mode 100644 bitbucket/resource_user_access_token_test.go create mode 100644 docusaurus/docs/resource_user_access_token.md diff --git a/bitbucket/provider.go b/bitbucket/provider.go index 84e051a..a4fe949 100644 --- a/bitbucket/provider.go +++ b/bitbucket/provider.go @@ -61,6 +61,7 @@ func Provider() terraform.ResourceProvider { "bitbucketserver_repository_permissions_group": resourceRepositoryPermissionsGroup(), "bitbucketserver_repository_permissions_user": resourceRepositoryPermissionsUser(), "bitbucketserver_user": resourceUser(), + "bitbucketserver_user_access_token": resourceUserAccessToken(), "bitbucketserver_user_group": resourceUserGroup(), }, } diff --git a/bitbucket/resource_user_access_token.go b/bitbucket/resource_user_access_token.go new file mode 100644 index 0000000..cac3b92 --- /dev/null +++ b/bitbucket/resource_user_access_token.go @@ -0,0 +1,188 @@ +package bitbucket + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "io/ioutil" +) + +type AccessTokenRequest struct { + Name string `json:"name,omitempty"` + Permissions []interface{} `json:"permissions,omitempty"` +} + +type AccessTokenResponse struct { + Id string `json:"id,omitempty"` + CreatedDate jsonTime `json:"createdDate,omitempty"` + LastAuthenticated jsonTime `json:"lastAuthenticated,omitempty"` + Name string `json:"name,omitempty"` + Permissions []string `json:"permissions,omitempty"` + Token string `json:"token,omitempty"` +} + +func resourceUserAccessToken() *schema.Resource { + return &schema.Resource{ + Create: resourceUserAccessTokenCreate, + Update: resourceUserAccessTokenUpdate, + Read: resourceUserAccessTokenRead, + Exists: resourceUserAccessTokenExists, + Delete: resourceUserAccessTokenDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "user": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "permissions": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "created_date": { + Type: schema.TypeString, + Computed: true, + }, + "last_authenticated": { + Type: schema.TypeString, + Computed: true, + }, + "access_token": { + Type: schema.TypeString, + Sensitive: true, + Computed: true, + }, + }, + } +} + +func resourceUserAccessTokenCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + + accessTokenRequest := &AccessTokenRequest{ + Name: d.Get("name").(string), + Permissions: d.Get("permissions").([]interface{}), + } + + byteData, err := json.Marshal(accessTokenRequest) + if err != nil { + return err + } + + res, err := client.Put(fmt.Sprintf("/rest/access-tokens/1.0/users/%s", + d.Get("user").(string), + ), bytes.NewBuffer(byteData)) + + if err != nil { + return err + } + + var accessTokenResponse AccessTokenResponse + + body, readErr := ioutil.ReadAll(res.Body) + if readErr != nil { + return readErr + } + + decodeErr := json.Unmarshal(body, &accessTokenResponse) + if decodeErr != nil { + return decodeErr + } + + d.SetId(accessTokenResponse.Id) + _ = d.Set("access_token", accessTokenResponse.Token) + + return resourceUserAccessTokenRead(d, m) +} + +func resourceUserAccessTokenUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + accessTokenRequest := &AccessTokenRequest{ + Name: d.Get("name").(string), + Permissions: d.Get("permissions").([]interface{}), + } + + byteData, err := json.Marshal(accessTokenRequest) + if err != nil { + return err + } + + _, err = client.Post(fmt.Sprintf("/rest/access-tokens/1.0/users/%s/%s", + d.Get("user").(string), + d.Id(), + ), bytes.NewBuffer(byteData)) + + if err != nil { + return err + } + + return resourceUserAccessTokenRead(d, m) +} + +func resourceUserAccessTokenRead(d *schema.ResourceData, m interface{}) error { + + client := m.(*BitbucketServerProvider).BitbucketClient + res, err := client.Get(fmt.Sprintf("/rest/access-tokens/1.0/users/%s/%s", + d.Get("user").(string), + d.Id(), + )) + + if err != nil { + return err + } + + var accessTokenResponse AccessTokenResponse + + body, readErr := ioutil.ReadAll(res.Body) + if readErr != nil { + return readErr + } + + decodeErr := json.Unmarshal(body, &accessTokenResponse) + if decodeErr != nil { + return decodeErr + } + + _ = d.Set("name", accessTokenResponse.Name) + _ = d.Set("created_date", accessTokenResponse.CreatedDate.String()) + _ = d.Set("last_authenticated", accessTokenResponse.LastAuthenticated.String()) + + return nil +} + +func resourceUserAccessTokenExists(d *schema.ResourceData, m interface{}) (bool, error) { + client := m.(*BitbucketServerProvider).BitbucketClient + req, err := client.Get(fmt.Sprintf("/rest/access-tokens/1.0/users/%s/%s", + d.Get("user").(string), + d.Id(), + )) + + if err != nil { + return false, fmt.Errorf("failed to get access token %s for user %s from bitbucket: %+v", d.Id(), d.Get("user").(string), err) + } + + if req.StatusCode == 200 { + return true, nil + } else { + return false, nil + } +} + +func resourceUserAccessTokenDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + _, err := client.Delete(fmt.Sprintf("/rest/access-tokens/1.0/users/%s/%s", + d.Get("user").(string), + d.Id(), + )) + + return err +} diff --git a/bitbucket/resource_user_access_token_test.go b/bitbucket/resource_user_access_token_test.go new file mode 100644 index 0000000..70d8286 --- /dev/null +++ b/bitbucket/resource_user_access_token_test.go @@ -0,0 +1,54 @@ +package bitbucket + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "testing" +) + +func TestAccBitbucketUserAccessToken(t *testing.T) { + testAccBitbucketUserConfig := ` + resource "bitbucketserver_user_access_token" "test" { + user = "admin" + name = "my-token" + permissions = ["REPO_READ", "PROJECT_WRITE"] + } + ` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckBitbucketUserAccessTokenDestroy, + Steps: []resource.TestStep{ + { + Config: testAccBitbucketUserConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("bitbucketserver_user_access_token.test", "name", "my-token"), + resource.TestCheckResourceAttr("bitbucketserver_user_access_token.test", "permissions.#", "2"), + resource.TestCheckResourceAttr("bitbucketserver_user_access_token.test", "permissions.0", "REPO_READ"), + resource.TestCheckResourceAttr("bitbucketserver_user_access_token.test", "permissions.1", "PROJECT_WRITE"), + resource.TestCheckResourceAttrSet("bitbucketserver_user_access_token.test", "created_date"), + resource.TestCheckResourceAttrSet("bitbucketserver_user_access_token.test", "last_authenticated"), + resource.TestCheckResourceAttrSet("bitbucketserver_user_access_token.test", "access_token"), + ), + }, + }, + }) +} + +func testAccCheckBitbucketUserAccessTokenDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*BitbucketServerProvider).BitbucketClient + rs, ok := s.RootModule().Resources["bitbucketserver_user_access_token.test"] + if !ok { + return fmt.Errorf("not found %s", "bitbucketserver_user_access_token.test") + } + + response, _ := client.Get(fmt.Sprintf("/rest/access-tokens/1.0/users/%s/%s", rs.Primary.Attributes["user"], rs.Primary.ID)) + + if response.StatusCode != 404 { + return fmt.Errorf("access token still exists") + } + + return nil +} diff --git a/docusaurus/docs/resource_user_access_token.md b/docusaurus/docs/resource_user_access_token.md new file mode 100644 index 0000000..fb5fef2 --- /dev/null +++ b/docusaurus/docs/resource_user_access_token.md @@ -0,0 +1,48 @@ +--- +id: bitbucketserver_user_access_token +title: bitbucketserver_user_access_token +--- + +Personal access tokens can be used to replace passwords over https, or to authenticate using the Bitbucket Server REST API over Basic Auth. + +For git operations, you can use your personal access token as a substitute for your password. + +> Note: You can only create access tokens for your user account - i.e. the one that the provisioner has been configured to authenticate with! +> This is a restriction in the Bitbucket APIs. + +## Example Usage + +```hcl +resource "bitbucketserver_user_access_token" "token" { + user = "admin" + name = "my-token" + permissions = ["REPO_READ", "PROJECT_ADMIN"] +} +``` + +## Argument Reference + +* `user` - Required. Username of the user. +* `name` - Required. Name of the access token. +* `permissions` - Required. List of permissions to grant the access token. + + * `PROJECT_READ` + * `PROJECT_WRITE` + * `PROJECT_ADMIN` + * `REPO_READ` + * `REPO_WRITE` + * `REPO_ADMIN` + +## Attribute Reference + +* `access_token` - The generated access token. Only available if token was generated on Terraform resource creation, not import/update. +* `created_date` - When the access token was generated. +* `last_authenticated` - When the access token was last used for authentication. + +## Import + +Import a user token reference via the token id. + +``` +terraform import bitbucketserver_user_access_token.test 413460754380 +``` diff --git a/docusaurus/website/i18n/en.json b/docusaurus/website/i18n/en.json index 72d6d70..181e87b 100644 --- a/docusaurus/website/i18n/en.json +++ b/docusaurus/website/i18n/en.json @@ -91,6 +91,9 @@ "bitbucketserver_repository": { "title": "bitbucketserver_repository" }, + "bitbucketserver_user_access_token": { + "title": "bitbucketserver_user_access_token" + }, "bitbucketserver_user_group": { "title": "bitbucketserver_user_group" }, diff --git a/docusaurus/website/sidebars.json b/docusaurus/website/sidebars.json index 107807e..a0711d0 100755 --- a/docusaurus/website/sidebars.json +++ b/docusaurus/website/sidebars.json @@ -35,6 +35,7 @@ "bitbucketserver_repository_permissions_group", "bitbucketserver_repository_permissions_user", "bitbucketserver_user", + "bitbucketserver_user_access_token", "bitbucketserver_user_group" ] }