diff --git a/Makefile b/Makefile index cd14cfc..3e782d3 100644 --- a/Makefile +++ b/Makefile @@ -54,4 +54,11 @@ test-compile: fi go test -c $(TEST) $(TESTARGS) -.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile build-binaries +website-serve: + @cd docusaurus/website && npm start + +website-publish: + @cd docusaurus/website && npm run build + @cd docusaurus/website && CURRENT_BRANCH=master USE_SSH=true npm run publish-gh-pages + +.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile build-binaries website-serve website-publish diff --git a/bitbucket/data_project_hooks.go b/bitbucket/data_project_hooks.go new file mode 100644 index 0000000..cb0ce31 --- /dev/null +++ b/bitbucket/data_project_hooks.go @@ -0,0 +1,204 @@ +package bitbucket + +import ( + "encoding/json" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "net/url" +) + +type PaginatedProjectHooksValue struct { + Details struct { + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Description string `json:"description,omitempty"` + Version string `json:"version,omitempty"` + ScopeTypes []string `json:"scopeTypes,omitempty"` + } `json:"details,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Configured bool `json:"configured,omitempty"` + Scope struct { + Type string `json:"type,omitempty"` + ResourceId int `json:"resourceId,omitempty"` + } `json:"scope,omitempty"` +} + +type ProjectHook struct { + Key string + Name string + Type string + Description string + Version string + ScopeTypes []string + Enabled bool + Configured bool + ScopeType string + ScopeResourceId int +} + +type PaginatedProjectHooks struct { + Values []PaginatedProjectHooksValue `json:"values,omitempty"` + Size int `json:"size,omitempty"` + Limit int `json:"limit,omitempty"` + IsLastPage bool `json:"isLastPage,omitempty"` + Start int `json:"start,omitempty"` + NextPageStart int `json:"nextPageStart,omitempty"` +} + +func dataSourceProjectHooks() *schema.Resource { + return &schema.Resource{ + Read: dataSourceProjectHooksRead, + + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"PRE_RECEIVE", "POST_RECEIVE"}, false), + }, + "hooks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "version": { + Type: schema.TypeString, + Computed: true, + }, + "scope_types": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + "enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "configured": { + Type: schema.TypeBool, + Computed: true, + }, + "scope_type": { + Type: schema.TypeString, + Computed: true, + }, + "scope_resource_id": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceProjectHooksRead(d *schema.ResourceData, m interface{}) error { + hooks, err := readProjectHooks(m, d.Get("project").(string), d.Get("type").(string)) + if err != nil { + return err + } + + d.SetId(d.Get("project").(string)) + + var terraformHooks []interface{} + for _, hook := range hooks { + h := make(map[string]interface{}) + h["key"] = hook.Key + h["name"] = hook.Name + h["type"] = hook.Type + h["description"] = hook.Description + h["version"] = hook.Version + h["scope_types"] = hook.ScopeTypes + h["enabled"] = hook.Enabled + h["configured"] = hook.Configured + h["scope_type"] = hook.ScopeType + h["scope_resource_id"] = hook.ScopeResourceId + terraformHooks = append(terraformHooks, h) + } + + _ = d.Set("hooks", terraformHooks) + return nil +} + +func readProjectHooks(m interface{}, project string, typeFilter string) ([]ProjectHook, error) { + client := m.(*BitbucketServerProvider).BitbucketClient + + resourceURL := fmt.Sprintf("/rest/api/1.0/projects/%s/settings/hooks", + project, + ) + + if typeFilter != "" { + resourceURL += "?type=" + url.QueryEscape(typeFilter) + } + + var projectHooks PaginatedProjectHooks + var hooks []ProjectHook + + for { + resp, err := client.Get(resourceURL) + if err != nil { + return nil, err + } + + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&projectHooks) + if err != nil { + return nil, err + } + + for _, hook := range projectHooks.Values { + h := ProjectHook{ + Key: hook.Details.Key, + Name: hook.Details.Name, + Type: hook.Details.Type, + Description: hook.Details.Description, + Version: hook.Details.Version, + ScopeTypes: hook.Details.ScopeTypes, + Enabled: hook.Enabled, + Configured: hook.Configured, + ScopeType: hook.Scope.Type, + ScopeResourceId: hook.Scope.ResourceId, + } + hooks = append(hooks, h) + } + + if projectHooks.IsLastPage == false { + resourceURL = fmt.Sprintf("/rest/api/1.0/projects/%s/settings/hooks?start=%d", + project, + projectHooks.NextPageStart, + ) + + if typeFilter != "" { + resourceURL += "&type=" + url.QueryEscape(typeFilter) + } + + projectHooks = PaginatedProjectHooks{} + } else { + break + } + } + + return hooks, nil +} diff --git a/bitbucket/data_project_hooks_test.go b/bitbucket/data_project_hooks_test.go new file mode 100644 index 0000000..57852ce --- /dev/null +++ b/bitbucket/data_project_hooks_test.go @@ -0,0 +1,89 @@ +package bitbucket + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBitbucketDataProjectHooks_simple(t *testing.T) { + projectKey := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + + config := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "TEST%v" + name = "test-project-%v" + } + + data "bitbucketserver_project_hooks" "test" { + project = bitbucketserver_project.test.key + } + `, projectKey, projectKey) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.#", "8"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.key", "com.atlassian.bitbucket.server.bitbucket-bundled-hooks:all-approvers-merge-check"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.name", "All reviewers approve"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.type", "PRE_PULL_REQUEST_MERGE"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.description", "Require all reviewers to approve the pull request."), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.version", "6.7.0"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.scope_types.#", "2"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.scope_types.0", "PROJECT"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.scope_types.1", "REPOSITORY"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.enabled", "false"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.configured", "false"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.scope_type", "PROJECT"), + resource.TestCheckResourceAttrSet("data.bitbucketserver_project_hooks.test", "hooks.0.scope_resource_id"), + ), + }, + }, + }) +} + +func TestAccBitbucketDataProjectHooks_type(t *testing.T) { + projectKey := rand.New(rand.NewSource(time.Now().UnixNano())).Int() + + config := fmt.Sprintf(` + resource "bitbucketserver_project" "test" { + key = "TEST%v" + name = "test-project-%v" + } + + data "bitbucketserver_project_hooks" "test" { + project = bitbucketserver_project.test.key + type = "PRE_RECEIVE" + } + `, projectKey, projectKey) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.#", "3"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.key", "com.atlassian.bitbucket.server.bitbucket-bundled-hooks:force-push-hook"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.name", "Reject Force Push"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.type", "PRE_RECEIVE"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.description", "Reject all force pushes (git push --force) to this repository"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.version", "6.7.0"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.scope_types.#", "2"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.scope_types.0", "PROJECT"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.scope_types.1", "REPOSITORY"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.enabled", "false"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.configured", "false"), + resource.TestCheckResourceAttr("data.bitbucketserver_project_hooks.test", "hooks.0.scope_type", "PROJECT"), + resource.TestCheckResourceAttrSet("data.bitbucketserver_project_hooks.test", "hooks.0.scope_resource_id"), + ), + }, + }, + }) +} diff --git a/bitbucket/provider.go b/bitbucket/provider.go index f1bb954..df30a6d 100644 --- a/bitbucket/provider.go +++ b/bitbucket/provider.go @@ -37,6 +37,7 @@ func Provider() terraform.ResourceProvider { "bitbucketserver_groups": dataSourceGroups(), "bitbucketserver_group_users": dataSourceGroupUsers(), "bitbucketserver_plugin": dataSourcePlugin(), + "bitbucketserver_project_hooks": dataSourceProjectHooks(), "bitbucketserver_project_permissions_groups": dataSourceProjectPermissionsGroups(), "bitbucketserver_project_permissions_users": dataSourceProjectPermissionsUsers(), "bitbucketserver_repository_permissions_groups": dataSourceRepositoryPermissionsGroups(), diff --git a/docusaurus/docs/data_project_hooks.md b/docusaurus/docs/data_project_hooks.md new file mode 100644 index 0000000..5d4132f --- /dev/null +++ b/docusaurus/docs/data_project_hooks.md @@ -0,0 +1,58 @@ +--- +id: data_bitbucketserver_project_hooks +title: bitbucketserver_project_hooks +--- + +Retrieve a list of project hooks and their status' for the specified project. + +## Example Usage + +```hcl +data "bitbucketserver_project_hooks" "main" { + project = "TEST" +} + +# data.bitbucketserver_project_hooks.main.hooks = [{ +# "key" = "com.atlassian.bitbucket.server.bitbucket-bundled-hooks:force-push-hook", +# "name" = "Reject Force Push", +# "type" = "PRE_RECEIVE", +# "description" = "Reject all force pushes (git push --force) to this repository", +# "version" = "6.7.0", +# "scope_types" = ["PROJECT", "REPOSITORY"], +# "enabled" = "false", +# "configured" = "false", +# "scope_type" = "PROJECT", +# }] +``` + +### Applying a Custom Filter + +Find specific types of project hooks. + +```hcl +data "bitbucketserver_project_hooks" "main" { + project = "TEST" + type = "PRE_RECEIVE" +} +``` + +## Argument Reference + +* `project` - Required. Project Key to lookup hooks for. +* `type` - Optional. Type of hook to find. Must be one of `PRE_RECEIVE`, `POST_RECEIVE` + +## Attribute Reference + +* `hooks` - List of maps containing: + + * `key` - Unique key identifying the hook e.g. `com.atlassian.bitbucket.server.bitbucket-bundled-hooks:force-push-hook` + * `name` - Name of the hook e.g. `Reject Force Push` + * `type` - Type of the hook e.g. `PRE_RECEIVE` + * `description` - Detailed description e.g. `Reject all force pushes (git push --force) to this repository` + * `version` - Version of the hook, for system hooks this is the bitbucket version e.g. `6.7.0` + * `scope_types` - List of strings containing the scopes available for this hook, e.g. `["PROJECT", "REPOSITORY"]` + * `enabled` - Set if this hook is enabled for this project + * `configured` - Set if the hook is configured for this project + * `scope_type` - Type of scope applied for this hook, e.g. `PROJECT` + * `scope_resource_id` - Reference ID of the applied scope, e.g. `1` + \ No newline at end of file diff --git a/docusaurus/website/i18n/en.json b/docusaurus/website/i18n/en.json index 1c5b16b..e191bed 100644 --- a/docusaurus/website/i18n/en.json +++ b/docusaurus/website/i18n/en.json @@ -25,6 +25,9 @@ "data_bitbucketserver_plugin": { "title": "bitbucketserver_plugin" }, + "data_bitbucketserver_project_hooks": { + "title": "bitbucketserver_project_hooks" + }, "data_bitbucketserver_project_permissions_groups": { "title": "bitbucketserver_project_permissions_groups" }, diff --git a/docusaurus/website/sidebars.json b/docusaurus/website/sidebars.json index 8ecf30d..d978dbc 100755 --- a/docusaurus/website/sidebars.json +++ b/docusaurus/website/sidebars.json @@ -11,6 +11,7 @@ "data_bitbucketserver_group_users", "data_bitbucketserver_groups", "data_bitbucketserver_plugin", + "data_bitbucketserver_project_hooks", "data_bitbucketserver_project_permissions_groups", "data_bitbucketserver_project_permissions_users", "data_bitbucketserver_repository_permissions_groups",