diff --git a/bitbucket/resource_repository_webhook.go b/bitbucket/resource_repository_webhook.go new file mode 100644 index 0000000..87893eb --- /dev/null +++ b/bitbucket/resource_repository_webhook.go @@ -0,0 +1,263 @@ +package bitbucket + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "io/ioutil" + "strings" +) + +type Webhook struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + CreatedDate string `json:"createdDate,omitempty"` + UpdatedDate string `json:"updatedDate,omitempty"` + URL string `json:"url,omitempty"` + Active bool `json:"active,omitempty"` + Events []string `json:"events"` +} + +type WebhookListResponse struct { + Size int `json:"size,omitempty"` + Limit int `json:"limit,omitempty"` + Start int `json:"start,omitempty"` + IsLastPage bool `json:"isLastPage,omitempty"` + Values []Webhook `json:"values"` +} + +func resourceRepositoryWebhook() *schema.Resource { + return &schema.Resource{ + Create: resourceRepositoryWebhookCreate, + Update: resourceRepositoryWebhookUpdate, + Read: resourceRepositoryHookRead, + Delete: resourceRepositoryHookDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "repository": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "webhook_url": { + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + "events": { + Type: schema.TypeList, + Required: true, + ForceNew: false, + }, + "active": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "webhook_id": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func resourceRepositoryWebhookUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + + project := d.Get("project").(string) + repository := d.Get("repository").(string) + id := d.Get("id").(int) + webhook := newWebhookFromResource(d) + + request, err := json.Marshal(webhook) + + _, err = client.Put(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/webhooks/%d", + project, + repository, + id, + ), bytes.NewBuffer(request)) + + if err != nil { + return err + } + + return resourceRepositoryHookRead(d, m) +} + +func resourceRepositoryWebhookCreate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + + project := d.Get("project").(string) + repository := d.Get("repository").(string) + webhook := newWebhookFromResource(d) + + request, err := json.Marshal(webhook) + + res, err := client.Post(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/webhooks", + project, + repository, + ), bytes.NewBuffer(request)) + + if err != nil { + return err + } + + var webhookResponse Webhook + + body, err := ioutil.ReadAll(res.Body) + + if err != nil { + return err + } + + err = json.Unmarshal(body, &webhookResponse) + + if err != nil { + return err + } + + _ = d.Set("webhook_id", webhookResponse.ID) + + d.SetId(fmt.Sprintf("%s/%s/%s", d.Get("project").(string), d.Get("repository").(string), d.Get("name").(string))) + return resourceRepositoryWebhookRead(d, m) +} + +func resourceRepositoryWebhookRead(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id != "" { + parts := strings.Split(id, "/") + if len(parts) == 3 { + _ = d.Set("project", parts[0]) + _ = d.Set("repository", parts[1]) + _ = d.Set("name", parts[2]) + } else { + return fmt.Errorf("incorrect ID format, should match `project/repository/name`") + } + } + + webhookId := d.Get("webhook_id").(int) + + var err error + + if webhookId != 0 { + err = getRepositoryWebhookFromId(d, m) + } else { + err = getRepositoryWebhookFromList(d, m) + } + + if err != nil { + return err + } + + return nil +} + +func resourceRepositoryWebhookDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + _, err := client.Delete(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/webhooks/%s", + d.Get("project").(string), + d.Get("repository").(string), + d.Get("webhook_id").(string))) + + return err +} + +func newWebhookFromResource(d *schema.ResourceData) (Hook *Webhook) { + webhook := &Webhook{ + Name: d.Get("name").(string), + URL: d.Get("webhook_url").(string), + Active: d.Get("active").(bool), + Events: d.Get("events").([]string), + } + + return webhook +} + +func getRepositoryWebhookFromId(d *schema.ResourceData, m interface{}) error { + project := d.Get("project").(string) + repository := d.Get("repository").(string) + id := d.Get("webhook_id").(string) + + client := m.(*BitbucketServerProvider).BitbucketClient + + resp, err := client.Get(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/webhooks/%s", + project, + repository, + id, + )) + + if err != nil { + return err + } + + var webhook Webhook + + decoder := json.NewDecoder(resp.Body) + + err = decoder.Decode(&webhook) + + if err != nil { + return err + } + + _ = d.Set("webhook_id", webhook.ID) + _ = d.Set("webhook_url", webhook.URL) + _ = d.Set("active", webhook.Active) + _ = d.Set("events", webhook.Events) + + return nil +} + +func getRepositoryWebhookFromList(d *schema.ResourceData, m interface{}) error { + project := d.Get("project").(string) + repository := d.Get("repository").(string) + name := d.Get("name").(string) + + client := m.(*BitbucketServerProvider).BitbucketClient + + resp, err := client.Get(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/webhooks", + project, + repository, + )) + + if err != nil { + return err + } + + var webhookListResponse WebhookListResponse + + decoder := json.NewDecoder(resp.Body) + + err = decoder.Decode(&webhookListResponse) + + if err != nil { + return err + } + + for _, webhook := range webhookListResponse.Values { + if webhook.Name == name { + _ = d.Set("webhook_id", webhook.ID) + _ = d.Set("webhook_url", webhook.URL) + _ = d.Set("active", webhook.Active) + _ = d.Set("events", webhook.Events) + return nil + } + } + + return fmt.Errorf("incorrect ID format, should match `project/repository/name`") +} diff --git a/bitbucket/resource_repository_webhook_test.go b/bitbucket/resource_repository_webhook_test.go new file mode 100644 index 0000000..7666cf4 --- /dev/null +++ b/bitbucket/resource_repository_webhook_test.go @@ -0,0 +1,49 @@ +package bitbucket + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBitbucketResourceRepositoryWebhook_simple(t *testing.T) { + projectKey := fmt.Sprintf("TEST%v", rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + + config := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "%v" + name = "test-project-%v" + } + + resource "bitbucketserver_repository" "test" { + project = bitbucketserver_project.test.key + name = "repo" + } + + resource "bitbucketserver_repository_webhook" "test" { + project = bitbucketserver_project.test.key + repository = bitbucketserver_repository.test.slug + name = "google" + webhook_url = "https://www.google.com/" + events = ["repo:refs_changed"] + active = true + } + `, projectKey, projectKey) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("bitbucketserver_repository_webhook.test", "project", projectKey), + resource.TestCheckResourceAttr("bitbucketserver_repository_webhook.test", "repository", "repo"), + resource.TestCheckResourceAttr("bitbucketserver_repository_webhook.test", "webhook_url", "https://www.google.com/"), + ), + }, + }, + }) +} diff --git a/docs/resources/bitbucketserver_repository_webhook.md b/docs/resources/bitbucketserver_repository_webhook.md new file mode 100644 index 0000000..18fecb7 --- /dev/null +++ b/docs/resources/bitbucketserver_repository_webhook.md @@ -0,0 +1,47 @@ +# Resource: bitbucketserver_repository_webhook + +Manage a repository level hook. Extends what Bitbucket does every time a repository changes, for example when code is pushed or a pull request is merged. + +## Example Usage + +```hcl +resource "bitbucketserver_project" "main" { + key = "MYPROJ" + name = "my-project" +} + +resource "bitbucketserver_repository" "main" { + project = bitbucketserver_project.test.key + name = "repo" +} + +resource "bitbucketserver_repository_webhook" "main" { + project = bitbucketserver_project.test.key + repository = bitbucketserver_repository.test.slug + name = "google" + webhook_url = "https://www.google.com/" + events = ["repo:refs_changed"] + active = true +} +``` + +## Argument Reference + +* `project` - Required. Project Key the repository is contained within. +* `repository` - Required. Repository slug to enable hook for. +* `name` - Required. Name of the webhook. +* `webhook_url` - Required. The URL of the webhook. +* `events` - Required. A list of events to trigger the webhook url. +* `active` - Optional. Enable or disable the webhook. Default: true + +## Attribute Reference + +* `webhook_id` - The webhook id. + +## Import + +Import a user reference using the project key, repository name and webhook name. + +``` +terraform import bitbucketserver_repository_webhook.main MYPROJ/repo/google +```