From 37df957f4107068dc3e20af654fcaeb942fe093d Mon Sep 17 00:00:00 2001 From: Gavin Bunney Date: Tue, 15 Oct 2019 09:07:05 -0700 Subject: [PATCH] Added `bitbucketserver_banner` resource and `bitbucketserver_cluster` data source --- bitbucket/data_cluster.go | 121 +++++++++++++++++++++++++++ bitbucket/data_cluster_test.go | 36 ++++++++ bitbucket/provider.go | 2 + bitbucket/resource_banner.go | 128 +++++++++++++++++++++++++++++ bitbucket/resource_banner_test.go | 53 ++++++++++++ docusaurus/docs/data_cluster.md | 32 ++++++++ docusaurus/docs/provider.md | 19 ++++- docusaurus/docs/resource_banner.md | 28 +++++++ docusaurus/website/i18n/en.json | 6 ++ docusaurus/website/sidebars.json | 2 + 10 files changed, 424 insertions(+), 3 deletions(-) create mode 100644 bitbucket/data_cluster.go create mode 100644 bitbucket/data_cluster_test.go create mode 100644 bitbucket/resource_banner.go create mode 100644 bitbucket/resource_banner_test.go create mode 100644 docusaurus/docs/data_cluster.md create mode 100644 docusaurus/docs/resource_banner.md diff --git a/bitbucket/data_cluster.go b/bitbucket/data_cluster.go new file mode 100644 index 0000000..95b3b40 --- /dev/null +++ b/bitbucket/data_cluster.go @@ -0,0 +1,121 @@ +package bitbucket + +import ( + "encoding/json" + "github.com/hashicorp/terraform/helper/schema" + "io/ioutil" +) + +type ClusterNode struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Address struct { + Hostname string `json:"hostName,omitempty"` + Port int `json:"port,omitempty"` + } `json:"address,omitempty"` + Local bool `json:"local,omitempty"` +} + +type Cluster struct { + LocalNode ClusterNode `json:"localNode,omitempty"` + Nodes []ClusterNode `json:"nodes,omitempty"` + Running bool `json:"running,omitempty"` +} + +func dataSourceCluster() *schema.Resource { + return &schema.Resource{ + Read: dataSourceClusterRead, + + Schema: map[string]*schema.Schema{ + "local_node": { + Type: schema.TypeList, + Computed: true, + Elem: clusterNodeResourceSchema(), + }, + "nodes": { + Type: schema.TypeList, + Computed: true, + Elem: clusterNodeResourceSchema(), + }, + "running": { + Type: schema.TypeBool, + Computed: true, + }, + }, + } +} + +func clusterNodeResourceSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "hostname": { + Type: schema.TypeString, + Computed: true, + }, + "port": { + Type: schema.TypeInt, + Computed: true, + }, + "local": { + Type: schema.TypeBool, + Computed: true, + }, + }, + } +} + +func dataSourceClusterRead(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + req, err := client.Get("/rest/api/1.0/admin/cluster") + + if err != nil { + return err + } + + var cluster Cluster + + body, readErr := ioutil.ReadAll(req.Body) + if readErr != nil { + return readErr + } + + decodeErr := json.Unmarshal(body, &cluster) + if decodeErr != nil { + return decodeErr + } + + d.SetId("cluster") + _ = d.Set("running", cluster.Running) + + var nodes []interface{} + for _, node := range cluster.Nodes { + n := make(map[string]interface{}) + n["id"] = node.ID + n["name"] = node.Name + n["hostname"] = node.Address.Hostname + n["port"] = node.Address.Port + n["local"] = node.Local + nodes = append(nodes, n) + } + _ = d.Set("nodes", nodes) + + var localNode []interface{} + n := make(map[string]interface{}) + n["id"] = cluster.LocalNode.ID + n["name"] = cluster.LocalNode.Name + n["hostname"] = cluster.LocalNode.Address.Hostname + n["port"] = cluster.LocalNode.Address.Port + n["local"] = cluster.LocalNode.Local + localNode = append(localNode, n) + _ = d.Set("local_node", localNode) + + return nil +} diff --git a/bitbucket/data_cluster_test.go b/bitbucket/data_cluster_test.go new file mode 100644 index 0000000..a2de509 --- /dev/null +++ b/bitbucket/data_cluster_test.go @@ -0,0 +1,36 @@ +package bitbucket + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBitbucketDataCluster(t *testing.T) { + config := ` + data "bitbucketserver_cluster" "main" {} + ` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.bitbucketserver_cluster.main", "running", "true"), + resource.TestCheckResourceAttr("data.bitbucketserver_cluster.main", "local_node.#", "1"), + resource.TestCheckResourceAttrSet("data.bitbucketserver_cluster.main", "local_node.0.id"), + resource.TestCheckResourceAttrSet("data.bitbucketserver_cluster.main", "local_node.0.hostname"), + resource.TestCheckResourceAttrSet("data.bitbucketserver_cluster.main", "local_node.0.port"), + resource.TestCheckResourceAttr("data.bitbucketserver_cluster.main", "local_node.0.local", "true"), + resource.TestCheckResourceAttr("data.bitbucketserver_cluster.main", "nodes.#", "1"), + resource.TestCheckResourceAttrSet("data.bitbucketserver_cluster.main", "nodes.0.id"), + resource.TestCheckResourceAttrSet("data.bitbucketserver_cluster.main", "nodes.0.hostname"), + resource.TestCheckResourceAttrSet("data.bitbucketserver_cluster.main", "nodes.0.port"), + resource.TestCheckResourceAttr("data.bitbucketserver_cluster.main", "nodes.0.local", "true"), + ), + }, + }, + }) +} diff --git a/bitbucket/provider.go b/bitbucket/provider.go index e89a930..f1bb954 100644 --- a/bitbucket/provider.go +++ b/bitbucket/provider.go @@ -31,6 +31,7 @@ func Provider() terraform.ResourceProvider { ConfigureFunc: providerConfigure, DataSourcesMap: map[string]*schema.Resource{ "bitbucketserver_application_properties": dataSourceApplicationProperties(), + "bitbucketserver_cluster": dataSourceCluster(), "bitbucketserver_global_permissions_groups": dataSourceGlobalPermissionsGroups(), "bitbucketserver_global_permissions_users": dataSourceGlobalPermissionsUsers(), "bitbucketserver_groups": dataSourceGroups(), @@ -42,6 +43,7 @@ func Provider() terraform.ResourceProvider { "bitbucketserver_repository_permissions_users": dataSourceRepositoryPermissionsUsers(), }, ResourcesMap: map[string]*schema.Resource{ + "bitbucketserver_banner": resourceBanner(), "bitbucketserver_global_permissions_group": resourceGlobalPermissionsGroup(), "bitbucketserver_global_permissions_user": resourceGlobalPermissionsUser(), "bitbucketserver_group": resourceGroup(), diff --git a/bitbucket/resource_banner.go b/bitbucket/resource_banner.go new file mode 100644 index 0000000..da7881e --- /dev/null +++ b/bitbucket/resource_banner.go @@ -0,0 +1,128 @@ +package bitbucket + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "io/ioutil" +) + +type Banner struct { + Message string `json:"message,omitempty"` + Audience string `json:"audience,omitempty"` + Enabled bool `json:"enabled,omitempty"` +} + +func resourceBanner() *schema.Resource { + return &schema.Resource{ + Create: resourceBannerCreate, + Update: resourceBannerUpdate, + Read: resourceBannerRead, + Exists: resourceBannerExists, + Delete: resourceBannerDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "message": { + Type: schema.TypeString, + Required: true, + }, + "audience": { + Type: schema.TypeString, + Optional: true, + Default: "ALL", + ValidateFunc: validation.StringInSlice([]string{"ALL", "AUTHENTICATED"}, false), + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + } +} + +func newBannerFromResource(d *schema.ResourceData) *Banner { + banner := &Banner{ + Message: d.Get("message").(string), + Audience: d.Get("audience").(string), + Enabled: d.Get("enabled").(bool), + } + + return banner +} + +func resourceBannerUpdate(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + banner := newBannerFromResource(d) + + bytedata, err := json.Marshal(banner) + + if err != nil { + return err + } + + _, err = client.Put("/rest/api/1.0/admin/banner", bytes.NewBuffer(bytedata)) + if err != nil { + return err + } + + d.SetId("banner") + return resourceBannerRead(d, m) +} + +func resourceBannerCreate(d *schema.ResourceData, m interface{}) error { + return resourceBannerUpdate(d, m) +} + +func resourceBannerRead(d *schema.ResourceData, m interface{}) error { + + client := m.(*BitbucketServerProvider).BitbucketClient + req, err := client.Get("/rest/api/1.0/admin/banner") + + if err != nil { + return err + } + + var banner Banner + + body, readErr := ioutil.ReadAll(req.Body) + if readErr != nil { + return readErr + } + + decodeErr := json.Unmarshal(body, &banner) + if decodeErr != nil { + return decodeErr + } + + _ = d.Set("message", banner.Message) + _ = d.Set("audience", banner.Audience) + _ = d.Set("enabled", banner.Enabled) + + return nil +} + +func resourceBannerExists(d *schema.ResourceData, m interface{}) (bool, error) { + client := m.(*BitbucketServerProvider).BitbucketClient + repoReq, err := client.Get("/rest/api/1.0/admin/banner") + if err != nil { + return false, fmt.Errorf("failed to get banner from bitbucket: %+v", err) + } + + if repoReq.StatusCode == 200 { + return true, nil + } else { + return false, nil + } +} + +func resourceBannerDelete(d *schema.ResourceData, m interface{}) error { + client := m.(*BitbucketServerProvider).BitbucketClient + _, err := client.Delete("/rest/api/1.0/admin/banner") + return err +} diff --git a/bitbucket/resource_banner_test.go b/bitbucket/resource_banner_test.go new file mode 100644 index 0000000..ddc6d78 --- /dev/null +++ b/bitbucket/resource_banner_test.go @@ -0,0 +1,53 @@ +package bitbucket + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccBitbucketBanner_basic(t *testing.T) { + testAccBitbucketBannerConfig := ` + resource "bitbucketserver_banner" "test" { + message = "Test Banner\n*bold*" + } + ` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccBitbucketBannerConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("bitbucketserver_banner.test", "message", "Test Banner\n*bold*"), + resource.TestCheckResourceAttr("bitbucketserver_banner.test", "enabled", "true"), + resource.TestCheckResourceAttr("bitbucketserver_banner.test", "audience", "ALL"), + ), + }, + }, + }) +} + +func TestAccBitbucketBanner_authenticated(t *testing.T) { + testAccBitbucketBannerConfig := ` + resource "bitbucketserver_banner" "test" { + message = "Test Banner\n*bold*" + audience = "AUTHENTICATED" + } + ` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccBitbucketBannerConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("bitbucketserver_banner.test", "message", "Test Banner\n*bold*"), + resource.TestCheckResourceAttr("bitbucketserver_banner.test", "audience", "AUTHENTICATED"), + ), + }, + }, + }) +} diff --git a/docusaurus/docs/data_cluster.md b/docusaurus/docs/data_cluster.md new file mode 100644 index 0000000..2fdaa72 --- /dev/null +++ b/docusaurus/docs/data_cluster.md @@ -0,0 +1,32 @@ +--- +id: data_bitbucketserver_cluster +title: bitbucketserver_cluster +--- + +Gets information about the nodes that currently make up the Bitbucket cluster. + +## Example Usage + +```hcl +data "bitbucketserver_cluster" "main" { } + +output "local_hostname" { + value = "Bitbucket running on ${data.bitbucketserver_cluster.main.local_node.0.hostname}" +} +``` + +## Attribute Reference + +* `local_node` - List with a single element, containing the local node details. See `node` schema below. +* `nodes` - List of nodes of the Bitbucket cluster. +* `running` - Flag is the cluster is running. + +### Node Schema + +Each node in the attributes above contains the following elements: + +* `id` - Unique cluster identifier. +* `name` - Unique cluster identifier. +* `hostname` - Address hostname of the cluster node. Typically an IP address. +* `port` - Port of the cluster node. This is not the same as the Bitbucket UI port, rather the node cluster port. +* `local` - Flag if this is a local node. diff --git a/docusaurus/docs/provider.md b/docusaurus/docs/provider.md index 4e5327b..1919bb1 100755 --- a/docusaurus/docs/provider.md +++ b/docusaurus/docs/provider.md @@ -24,9 +24,22 @@ provider "bitbucketserver" { } ``` -You can also specify these parameters through the `BITBUCKET_SERVER`, `BITBUCKER_USERNAME` and `BITBUCKET_PASSWORD` environment variables. +### Authentication -## Creating a Project and Repository +The `username` and `password` specified should be of a user with sufficient privileges to perform the operations you are after. +Typically this is a user with `SYS_ADMIN` global permissions. + +### Environment Variables + +You can also specify the provider configuration using the following env vars: + +* `BITBUCKET_SERVER` +* `BITBUCKER_USERNAME` +* `BITBUCKET_PASSWORD` + +> Note: The hcl provider configuration takes precedence over the environment variables. + +## Example - Creating a Project and Repository Creating a project and repository is super simple with this provider: @@ -45,7 +58,7 @@ resource "bitbucketserver_project" "test" { resource "bitbucketserver_repository" "test" { project = bitbucketserver_project.test.key - name = "test-01" + name = "repo-01" description = "Test repository" } ``` diff --git a/docusaurus/docs/resource_banner.md b/docusaurus/docs/resource_banner.md new file mode 100644 index 0000000..ac053e7 --- /dev/null +++ b/docusaurus/docs/resource_banner.md @@ -0,0 +1,28 @@ +--- +id: bitbucketserver_banner +title: bitbucketserver_banner +--- + +Manage the announcement banner, updating as required. + +## Example Usage + +```hcl +resource "bitbucketserver_banner" "main" { + message = "Bitbucket is down for maintenance\n*Save your work*" +} +``` + +## Argument Reference + +* `message` - Required. Information to display to the user. Markdown supported. +* `enabled` - Optional. Turn the announcement banner on/off. Default `true`. +* `audience` - Optional. Set the audience for the announcement. Must be one of `ALL` or `AUTHENTICATED`. Default `ALL`. + +## Import + +Import the banner: + +``` +terraform import bitbucketserver_banner.main banner +``` diff --git a/docusaurus/website/i18n/en.json b/docusaurus/website/i18n/en.json index e49289b..7d6c014 100644 --- a/docusaurus/website/i18n/en.json +++ b/docusaurus/website/i18n/en.json @@ -7,6 +7,9 @@ "data_bitbucketserver_application_properties": { "title": "bitbucketserver_application_properties" }, + "data_bitbucketserver_cluster": { + "title": "bitbucketserver_cluster" + }, "data_bitbucketserver_global_permissions_groups": { "title": "bitbucketserver_global_permissions_groups" }, @@ -37,6 +40,9 @@ "provider": { "title": "Getting Started" }, + "bitbucketserver_banner": { + "title": "bitbucketserver_banner" + }, "bitbucketserver_global_permissions_group": { "title": "bitbucketserver_global_permissions_group" }, diff --git a/docusaurus/website/sidebars.json b/docusaurus/website/sidebars.json index 14323a5..8ecf30d 100755 --- a/docusaurus/website/sidebars.json +++ b/docusaurus/website/sidebars.json @@ -5,6 +5,7 @@ ], "Data Sources": [ "data_bitbucketserver_application_properties", + "data_bitbucketserver_cluster", "data_bitbucketserver_global_permissions_groups", "data_bitbucketserver_global_permissions_users", "data_bitbucketserver_group_users", @@ -16,6 +17,7 @@ "data_bitbucketserver_repository_permissions_users" ], "Resources": [ + "bitbucketserver_banner", "bitbucketserver_global_permissions_group", "bitbucketserver_global_permissions_user", "bitbucketserver_group",