diff --git a/bitbucket/provider.go b/bitbucket/provider.go index 2d8e20f..21706e3 100644 --- a/bitbucket/provider.go +++ b/bitbucket/provider.go @@ -29,6 +29,7 @@ func Provider() terraform.ResourceProvider { }, ConfigureFunc: providerConfigure, ResourcesMap: map[string]*schema.Resource{ + "bitbucketserver_project": resourceProject(), "bitbucketserver_repository": resourceRepository(), }, } diff --git a/bitbucket/resource_project.go b/bitbucket/resource_project.go new file mode 100644 index 0000000..52ae2a3 --- /dev/null +++ b/bitbucket/resource_project.go @@ -0,0 +1,178 @@ +package bitbucket + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + + "github.com/hashicorp/terraform/helper/schema" +) + +type Project struct { + Name string `json:"name,omitempty"` + Key string `json:"key,omitempty"` + Description string `json:"description,omitempty"` + Public bool `json:"public,omitempty"` + Avatar string `json:"avatar,omitempty"` +} + +func resourceProject() *schema.Resource { + return &schema.Resource{ + Create: resourceProjectCreate, + Update: resourceProjectUpdate, + Read: resourceProjectRead, + Exists: resourceProjectExists, + Delete: resourceProjectDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "key": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "public": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "avatar": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func newProjectFromResource(d *schema.ResourceData) *Project { + project := &Project{ + Name: d.Get("name").(string), + Key: d.Get("key").(string), + Description: d.Get("description").(string), + Public: d.Get("public").(bool), + Avatar: d.Get("avatar").(string), + } + + return project +} + +func resourceProjectUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketClient) + project := newProjectFromResource(d) + + bytedata, err := json.Marshal(project) + + if err != nil { + return err + } + + _, err = client.Put(fmt.Sprintf("/rest/api/1.0/projects/%s", + project.Key, + ), bytes.NewBuffer(bytedata)) + + if err != nil { + return err + } + + return resourceProjectRead(d, m) +} + +func resourceProjectCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketClient) + project := newProjectFromResource(d) + + bytedata, err := json.Marshal(project) + + if err != nil { + return err + } + + _, err = client.Post("/rest/api/1.0/projects", bytes.NewBuffer(bytedata)) + + if err != nil { + return err + } + + d.SetId(project.Key) + + return resourceProjectRead(d, m) +} + +func resourceProjectRead(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id != "" { + d.Set("key", id) + } + + project := d.Get("key").(string) + + client := m.(*BitbucketClient) + project_req, err := client.Get(fmt.Sprintf("/rest/api/1.0/projects/%s", + project, + )) + + if err != nil { + return err + } + + if project_req.StatusCode == 200 { + + var project Project + + body, readerr := ioutil.ReadAll(project_req.Body) + if readerr != nil { + return readerr + } + + decodeerr := json.Unmarshal(body, &project) + if decodeerr != nil { + return decodeerr + } + + d.Set("name", project.Name) + d.Set("key", project.Key) + d.Set("description", project.Description) + d.Set("public", project.Public) + d.Set("avatar", project.Avatar) + } + + return nil +} + +func resourceProjectExists(d *schema.ResourceData, m interface{}) (bool, error) { + client := m.(*BitbucketClient) + project := d.Get("key").(string) + repo_req, err := client.Get(fmt.Sprintf("/rest/api/1.0/projects/%s", + project, + )) + + if err != nil { + return false, fmt.Errorf("failed to get project %s from bitbucket: %+v", project, err) + } + + if repo_req.StatusCode == 200 { + return true, nil + } else { + return false, nil + } +} + +func resourceProjectDelete(d *schema.ResourceData, m interface{}) error { + project := d.Get("key").(string) + client := m.(*BitbucketClient) + _, err := client.Delete(fmt.Sprintf("/rest/api/1.0/projects/%s", + project, + )) + + return err +} diff --git a/bitbucket/resource_project_test.go b/bitbucket/resource_project_test.go new file mode 100644 index 0000000..7d5e639 --- /dev/null +++ b/bitbucket/resource_project_test.go @@ -0,0 +1,65 @@ +package bitbucket + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccBitbucketProject(t *testing.T) { + var repo Repository + + testAccBitbucketProjectConfig := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "TEST%v" + name = "test-repo-for-repository-test" + } + `, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckBitbucketProjectDestroy, + Steps: []resource.TestStep{ + { + Config: testAccBitbucketProjectConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckBitbucketProjectExists("bitbucketserver_project.test", &repo), + ), + }, + }, + }) +} + +func testAccCheckBitbucketProjectDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*BitbucketClient) + rs, ok := s.RootModule().Resources["bitbucketserver_project.test"] + if !ok { + return fmt.Errorf("not found %s", "bitbucketserver_project.test") + } + + response, _ := client.Get(fmt.Sprintf("/rest/api/1.0/projects/%s", rs.Primary.Attributes["key"])) + + if response.StatusCode != 404 { + return fmt.Errorf("project still exists") + } + + return nil +} + +func testAccCheckBitbucketProjectExists(n string, repository *Repository) 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 project ID is set") + } + return nil + } +} diff --git a/bitbucket/resource_repository_test.go b/bitbucket/resource_repository_test.go index ea8047f..cda324b 100644 --- a/bitbucket/resource_repository_test.go +++ b/bitbucket/resource_repository_test.go @@ -2,7 +2,9 @@ package bitbucket import ( "fmt" + "math/rand" "testing" + "time" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" @@ -11,12 +13,17 @@ import ( func TestAccBitbucketRepository_basic(t *testing.T) { var repo Repository - testAccBitbucketRepositoryConfig := ` - resource "bitbucketserver_repository" "test_repo" { - project = "TEST" + testAccBitbucketRepositoryConfig := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "TEST%v" name = "test-repo-for-repository-test" } - ` + + resource "bitbucketserver_repository" "test_repo" { + project = bitbucketserver_project.test.key + name = "test-repo-for-repository-test" + } + `, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -36,13 +43,18 @@ func TestAccBitbucketRepository_basic(t *testing.T) { func TestAccBitbucketRepository_namewithspaces(t *testing.T) { var repo Repository - testAccBitbucketRepositoryConfig := ` + testAccBitbucketRepositoryConfig := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "TEST%v" + name = "test-repo-for-repository-test" + } + resource "bitbucketserver_repository" "test_repo" { - project = "TEST" + project = bitbucketserver_project.test.key name = "Test Repo For Repository Test" slug = "test-repo-for-repository-test" } - ` + `, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, diff --git a/scripts/start-docker-compose.sh b/scripts/start-docker-compose.sh index 86e5836..439c50c 100755 --- a/scripts/start-docker-compose.sh +++ b/scripts/start-docker-compose.sh @@ -2,7 +2,6 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - cd ${DIR} if [ ! -f ${DIR}/docker-compose ]; then @@ -15,4 +14,4 @@ echo "--> Starting docker-compose" ${DIR}/docker-compose up -d echo "--> Wait for bitbucket to be ready" -bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:7990/status)" != "200" ]]; do sleep 5; done' +bash ${DIR}/wait-for-url.sh --url http://localhost:7990/status --timeout 600 diff --git a/scripts/stop-docker-compose.sh b/scripts/stop-docker-compose.sh index 7d30322..282682a 100755 --- a/scripts/stop-docker-compose.sh +++ b/scripts/stop-docker-compose.sh @@ -2,6 +2,7 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +cd ${DIR} echo "--> Stopping docker-compose" ${DIR}/docker-compose down diff --git a/scripts/wait-for-url.sh b/scripts/wait-for-url.sh new file mode 100755 index 0000000..ac2a201 --- /dev/null +++ b/scripts/wait-for-url.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +set -e + +usage() { + echo "Usage: + -u | --url - Required. URL to wait for a 200 OK. + -c | --cookie-jar - Optional. Cookie jar to use to store cookies. + -s | --successful-requests - Optional. Number of successful requests to wait for. Default 3. + -t | --timeout - Optional. Number of seconds to wait. Default 300 (5m)." + exit 1 +} + +TIMEOUT=300 +INTERVAL=2 +TIMER_START=$SECONDS +WAIT_FOR_SUCCESSFUL_REQUESTS=3 + +while (( "$#" )); do + case "$1" in + -u|--url) + SERVICE_URL=$2 + shift 2 + ;; + -c|--cookie-jar) + COOKIE_JAR=$2 + shift 2 + ;; + -s|--successful-requests) + WAIT_FOR_SUCCESSFUL_REQUESTS=$2 + shift 2 + ;; + -t|--timeout) + TIMEOUT=$2 + shift 2 + ;; + -*|--*=) + echo "Error: Unsupported option $1" >&2 + exit 1 + ;; + esac +done + +if [[ ! ${SERVICE_URL} ]]; then + usage +fi + +if [[ "${INTERVAL}" -gt "${TIMEOUT}" ]]; then + INTERVAL=$TIMEOUT +fi + +# +# Wait for endpoint to return a 200OK +# + +SERVICE_CURL_RESULT="" +echo "> Waiting for ${SERVICE_URL} to return 200 OK (retrying every ${INTERVAL}s for ${TIMEOUT}s)" +limit=$(( ${TIMEOUT} / ${INTERVAL} )) +count=0 +successful_requests=0 +while : ; do + printf "." + if [ ! -z "${COOKIE_JAR}" ]; then + SERVICE_CURL_RESULT=$(curl --cookie "${COOKIE_JAR}" --cookie-jar "${COOKIE_JAR}" -H 'Cache-Control: no-cache' -L -s -o /dev/null -w '%{http_code}' ${SERVICE_URL} || true) + else + SERVICE_CURL_RESULT=$(curl -H 'Cache-Control: no-cache' -L -s -o /dev/null -w '%{http_code}' ${SERVICE_URL} || true) + fi + + if [[ "${SERVICE_CURL_RESULT}" -eq 200 ]]; then + successful_requests=$[$successful_requests+1] + elif [[ "${successful_requests}" -gt 0 ]]; then + printf "\n[!] Warning: Previous request was successful, current request returned: ${SERVICE_CURL_RESULT}\n" >&2 + successful_requests=$[$successful_requests-1] + fi + + if [[ "${successful_requests}" -ge "${WAIT_FOR_SUCCESSFUL_REQUESTS}" ]]; then + printf "\n" + break + fi + + if [[ "${count}" -ge "${limit}" ]]; then + printf "\n[!] Timeout waiting for Service to return 200 OK\n" >&2 + exit 1 + fi + + sleep ${INTERVAL} + count=$[$count+1] +done + +TIMER_DURATION=$(( SECONDS - TIMER_START )) + +echo "> ${SERVICE_URL} returned ${successful_requests} successful requests in ${TIMER_DURATION}s"