diff --git a/bitbucket/provider.go b/bitbucket/provider.go index 4448fd37..8fe20321 100644 --- a/bitbucket/provider.go +++ b/bitbucket/provider.go @@ -31,6 +31,8 @@ func Provider() terraform.ResourceProvider { "bitbucket_repository_variable": resourceRepositoryVariable(), "bitbucket_project": resourceProject(), "bitbucket_branch_restriction": resourceBranchRestriction(), + "bitbucket_deployment": resourceDeployment(), + "bitbucket_deployment_variable": resourceDeploymentVariable(), }, DataSourcesMap: map[string]*schema.Resource{ "bitbucket_user": dataUser(), diff --git a/bitbucket/resource_deployment.go b/bitbucket/resource_deployment.go new file mode 100644 index 00000000..99affaed --- /dev/null +++ b/bitbucket/resource_deployment.go @@ -0,0 +1,169 @@ +package bitbucket + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "io/ioutil" + "log" + "net/url" +) + +// Deployment structure for handling key info +type Deployment struct { + Name string `json:"name"` + Stage *Stage `json:"environment_type"` + UUID string `json:"uuid,omitempty"` +} + +type Stage struct { + Name string `json:"name"` +} + +func resourceDeployment() *schema.Resource { + return &schema.Resource{ + Create: resourceDeploymentCreate, + Update: resourceDeploymentUpdate, + Read: resourceDeploymentRead, + Delete: resourceDeploymentDelete, + + Schema: map[string]*schema.Schema{ + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "stage": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "Test", + "Staging", + "Production", + }, + false), + }, + "repository": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func newDeploymentFromResource(d *schema.ResourceData) *Deployment { + dk := &Deployment{ + Name: d.Get("name").(string), + Stage: &Stage{ + Name: d.Get("stage").(string), + }, + } + return dk +} + +func resourceDeploymentCreate(d *schema.ResourceData, m interface{}) error { + + client := m.(*Client) + rvcr := newDeploymentFromResource(d) + bytedata, err := json.Marshal(rvcr) + + if err != nil { + return err + } + req, err := client.Post(fmt.Sprintf("2.0/repositories/%s/environments/", + d.Get("repository").(string), + ), bytes.NewBuffer(bytedata)) + + if err != nil { + return err + } + + var deployment Deployment + + body, readerr := ioutil.ReadAll(req.Body) + if readerr != nil { + return readerr + } + + decodeerr := json.Unmarshal(body, &deployment) + if decodeerr != nil { + return decodeerr + } + d.Set("uuid", deployment.UUID) + d.SetId(fmt.Sprintf("%s:%s", d.Get("repository"), deployment.UUID)) + + return resourceDeploymentRead(d, m) +} + +func resourceDeploymentRead(d *schema.ResourceData, m interface{}) error { + + client := m.(*Client) + req, _ := client.Get(fmt.Sprintf("2.0/repositories/%s/environments/%s", + d.Get("repository").(string), + d.Get("uuid").(string), + )) + + log.Printf("ID: %s", url.PathEscape(d.Id())) + + if req.StatusCode == 200 { + var Deployment Deployment + body, readerr := ioutil.ReadAll(req.Body) + if readerr != nil { + return readerr + } + + decodeerr := json.Unmarshal(body, &Deployment) + if decodeerr != nil { + return decodeerr + } + + d.Set("uuid", Deployment.UUID) + d.Set("name", Deployment.Name) + d.Set("stage", Deployment.Stage.Name) + } + + if req.StatusCode == 404 { + d.SetId("") + return nil + } + + return nil +} + +func resourceDeploymentUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + rvcr := newDeploymentFromResource(d) + bytedata, err := json.Marshal(rvcr) + + if err != nil { + return err + } + req, err := client.Put(fmt.Sprintf("2.0/repositories/%s/environments/%s", + d.Get("repository").(string), + d.Get("uuid").(string), + ), bytes.NewBuffer(bytedata)) + + if err != nil { + return err + } + + if req.StatusCode != 200 { + return nil + } + + return resourceDeploymentRead(d, m) +} + +func resourceDeploymentDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + _, err := client.Delete(fmt.Sprintf("2.0/repositories/%s/environments/%s", + d.Get("repository").(string), + d.Get("uuid").(string), + )) + return err +} diff --git a/bitbucket/resource_deployment_test.go b/bitbucket/resource_deployment_test.go new file mode 100644 index 00000000..8008a7fa --- /dev/null +++ b/bitbucket/resource_deployment_test.go @@ -0,0 +1,70 @@ +package bitbucket + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccBitbucketDeployment_basic(t *testing.T) { + var repo Deployment + + testUser := os.Getenv("BITBUCKET_USERNAME") + testAccBitbucketDeploymentConfig := fmt.Sprintf(` + resource "bitbucket_repository "test_repo" { + owner = "%s" + name = "test-repo-for-deployment-test" + } + resource "bitbucket_deployment" "test_deploy" { + name = "test_deploy" + stage = "Staging" + repository = bitbucket_repository.test_repo.id + } + `, testUser) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckBitbucketDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccBitbucketDeploymentConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckBitbucketDeploymentExists("bitbucket_deployment.test_deploy", &repo), + ), + }, + }, + }) +} + +func testAccCheckBitbucketDeploymentDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*Client) + rs, ok := s.RootModule().Resources["bitbucket_deployment.test_deploy"] + if !ok { + return fmt.Errorf("Not found %s", "bitbucket_deployment.test_deploy") + } + + response, _ := client.Get(fmt.Sprintf("2.0/repositories/%s/%s", rs.Primary.Attributes["owner"], rs.Primary.Attributes["name"])) + + if response.StatusCode != 404 { + return fmt.Errorf("Deployment still exists") + } + + return nil +} + +func testAccCheckBitbucketDeploymentExists(n string, deployment *Deployment) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No deployment ID is set") + } + return nil + } +} diff --git a/bitbucket/resource_deployment_variable.go b/bitbucket/resource_deployment_variable.go new file mode 100644 index 00000000..871693d8 --- /dev/null +++ b/bitbucket/resource_deployment_variable.go @@ -0,0 +1,199 @@ +package bitbucket + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "io/ioutil" + "log" + "net/url" + "strings" + "time" +) + +// DeploymentVariable structure for handling key info +type DeploymentVariable struct { + Key string `json:"key"` + Value string `json:"value"` + UUID string `json:"uuid,omitempty"` + Secured bool `json:"secured"` +} + +// PaginatedReviewers is a paginated list that the bitbucket api returns +type PaginatedDeploymentVariables struct { + Values []DeploymentVariable `json:"values,omitempty"` + Page int `json:"page,omitempty"` + Size int `json:"size,omitempty"` + Next string `json:"next,omitempty"` +} + +func resourceDeploymentVariable() *schema.Resource { + return &schema.Resource{ + Create: resourceDeploymentVariableCreate, + Update: resourceDeploymentVariableUpdate, + Read: resourceDeploymentVariableRead, + Delete: resourceDeploymentVariableDelete, + + Schema: map[string]*schema.Schema{ + "uuid": { + Type: schema.TypeString, + Computed: true, + }, + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + "secured": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "deployment": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func newDeploymentVariableFromResource(d *schema.ResourceData) *DeploymentVariable { + dk := &DeploymentVariable{ + Key: d.Get("key").(string), + Value: d.Get("value").(string), + Secured: d.Get("secured").(bool), + } + return dk +} + +func parseDeploymentId(str string) (repository string, deployment string) { + parts := strings.SplitN(str, ":", 2) + return parts[0], parts[1] +} + +func resourceDeploymentVariableCreate(d *schema.ResourceData, m interface{}) error { + + client := m.(*Client) + rvcr := newDeploymentVariableFromResource(d) + bytedata, err := json.Marshal(rvcr) + + if err != nil { + return err + } + repository, deployment := parseDeploymentId(d.Get("deployment").(string)) + req, err := client.Post(fmt.Sprintf("2.0/repositories/%s/deployments_config/environments/%s/variables", + repository, + deployment, + ), bytes.NewBuffer(bytedata)) + + if err != nil { + return err + } + + var rv DeploymentVariable + + body, readerr := ioutil.ReadAll(req.Body) + if readerr != nil { + return readerr + } + + decodeerr := json.Unmarshal(body, &rv) + if decodeerr != nil { + return decodeerr + } + d.Set("uuid", rv.UUID) + d.SetId(rv.UUID) + + time.Sleep(5000 * time.Millisecond) // sleep for a while, to allow BitBucket cache to catch up + return resourceDeploymentVariableRead(d, m) +} + +func resourceDeploymentVariableRead(d *schema.ResourceData, m interface{}) error { + + repository, deployment := parseDeploymentId(d.Get("deployment").(string)) + client := m.(*Client) + rvReq, _ := client.Get(fmt.Sprintf("2.0/repositories/%s/deployments_config/environments/%s/variables", + repository, + deployment, + )) + + log.Printf("ID: %s", url.PathEscape(d.Id())) + + if rvReq.StatusCode == 200 { + var prv PaginatedDeploymentVariables + body, readerr := ioutil.ReadAll(rvReq.Body) + if readerr != nil { + return readerr + } + + decodeerr := json.Unmarshal(body, &prv) + if decodeerr != nil { + return decodeerr + } + + if prv.Size < 1 { + d.SetId("") + return nil + } + + var uuid = d.Get("uuid").(string) + for _, rv := range prv.Values { + if rv.UUID == uuid { + d.SetId(rv.UUID) + d.Set("key", rv.Key) + d.Set("value", rv.Value) + d.Set("secured", rv.Secured) + return nil + } + } + d.SetId("") + } + + if rvReq.StatusCode == 404 { + d.SetId("") + return nil + } + + return nil +} + +func resourceDeploymentVariableUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*Client) + rvcr := newDeploymentVariableFromResource(d) + bytedata, err := json.Marshal(rvcr) + + if err != nil { + return err + } + repository, deployment := parseDeploymentId(d.Get("deployment").(string)) + req, err := client.Put(fmt.Sprintf("2.0/repositories/%s/deployments_config/environments/%s/variables/%s", + repository, + deployment, + d.Get("uuid").(string), + ), bytes.NewBuffer(bytedata)) + + if err != nil { + return err + } + + if req.StatusCode != 200 { + return nil + } + + return resourceDeploymentVariableRead(d, m) +} + +func resourceDeploymentVariableDelete(d *schema.ResourceData, m interface{}) error { + repository, deployment := parseDeploymentId(d.Get("deployment").(string)) + client := m.(*Client) + _, err := client.Delete(fmt.Sprintf(fmt.Sprintf("2.0/repositories/%s/deployments_config/environments/%s/variables/%s", + repository, + deployment, + d.Get("uuid").(string), + ))) + return err +} diff --git a/bitbucket/resource_deployment_variable_test.go b/bitbucket/resource_deployment_variable_test.go new file mode 100644 index 00000000..c0bac93f --- /dev/null +++ b/bitbucket/resource_deployment_variable_test.go @@ -0,0 +1,73 @@ +package bitbucket + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "os" + "testing" +) + +func TestAccBitbucketDeploymentVariable_basic(t *testing.T) { + + testUser := os.Getenv("BITBUCKET_USERNAME") + testAccBitbucketDeploymentVariableConfig := fmt.Sprintf(` + resource "bitbucket_repository" "test_repo" { + owner = "%s" + name = "test-repo-default-reviewers" + } + resource "bitbucket_deployment" "test_deploy" { + name = "testdeploy" + stage = "Test" + repository = bitbucket_repository.test_repo.id + } + resource "bitbucket_deployment_variable" "testvar" { + key = "test" + value = "test" + deployment = bitbucket_deployment.test_deploy.id + secured = false + } + `, testUser) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckBitbucketDeploymentVariableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccBitbucketDeploymentVariableConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckBitbucketDeploymentVariableExists("bitbucket_deployment_variable.testvar", "test", "test"), + ), + }, + }, + }) +} + +func testAccCheckBitbucketDeploymentVariableDestroy(s *terraform.State) error { + _, ok := s.RootModule().Resources["bitbucket_deployment_variable.testvar"] + if !ok { + return fmt.Errorf("Not found %s", "bitbucket_deployment_variable.testvar") + } + return nil +} + +func testAccCheckBitbucketDeploymentVariableExists(n, key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found %s", n) + } + + if rs.Primary.Attributes["key"] != key { + return fmt.Errorf("Key not set") + } + + if rs.Primary.Attributes["value"] != value { + return fmt.Errorf("Key not set") + } + + return nil + } +} diff --git a/bitbucket/resource_repository_variable.go b/bitbucket/resource_repository_variable.go index 2d6e4185..825e942b 100644 --- a/bitbucket/resource_repository_variable.go +++ b/bitbucket/resource_repository_variable.go @@ -42,7 +42,7 @@ func resourceRepositoryVariable() *schema.Resource { "secured": { Type: schema.TypeBool, Optional: true, - Default: true, + Default: false, }, "repository": { Type: schema.TypeString, diff --git a/website/docs/r/deployment.html.markdown b/website/docs/r/deployment.html.markdown new file mode 100644 index 00000000..43e0e36b --- /dev/null +++ b/website/docs/r/deployment.html.markdown @@ -0,0 +1,34 @@ +--- +layout: "bitbucket" +page_title: "Bitbucket: bitbucket_deployment" +sidebar_current: "docs-bitbucket-resource-deployment" +description: |- + Manage your pipelines repository deployment environments +--- + + +# bitbucket\_deployment + +This resource allows you to setup pipelines deployment environments. + +# Example Usage + +```hcl +resource "bitbucket_repository" "monorepo" { + owner = "gob" + name = "illusions" + pipelines_enabled = true +} +resource "bitbucket_deployment" "test" { + repository = bitbucket_repository.monorepo.id + name = "test" + stage = "Test" +} +``` + +# Argument Reference + +* `name` - (Required) The name of the deployment environment +* `stage` - (Required) The stage (Test, Staging, Production) +* `repository` - (Required) The repository ID to which you want to assign this deployment environment to +* `uuid` - (Computed) The UUID of the deployment environment diff --git a/website/docs/r/deployment_variable.html.markdown b/website/docs/r/deployment_variable.html.markdown new file mode 100644 index 00000000..8c2f7a64 --- /dev/null +++ b/website/docs/r/deployment_variable.html.markdown @@ -0,0 +1,41 @@ +--- +layout: "bitbucket" +page_title: "Bitbucket: bitbucket_deployment_variable" +sidebar_current: "docs-bitbucket-resource-deployment-variable" +description: |- + Manage variables for your pipelines deployment environments +--- + + +# bitbucket\_deployment\_variable + +This resource allows you to configure deployment variables. + +# Example Usage + +```hcl +resource "bitbucket_repository" "monorepo" { + owner = "gob" + name = "illusions" + pipelines_enabled = true +} +resource "bitbucket_deployment" "test" { + repository = bitbucket_repository.monorepo.id + name = "test" + stage = "Test" +} +resource "bitbucket_deployment_variable" "country" { + deployment = bitbucket_deployment.test.id + name = "COUNTRY" + value = "Kenya" + secured = false +} +``` + +# Argument Reference + +* `deployment` - (Required) The deployment ID you want to assign this variable to. +* `name` - (Required) The name of the variable +* `value` - (Required) The stage (Test, Staging, Production) +* `secured` - (Optional) Boolean indicating whether the variable contains sensitive data +* `uuid` - (Computed) The UUID of the variable