diff --git a/azurerm/config.go b/azurerm/config.go index ffdcdbb23af5..f588dd99b2d7 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -103,6 +103,7 @@ type ArmClient struct { trafficManagerEndpointsClient trafficmanager.EndpointsClient serviceBusNamespacesClient servicebus.NamespacesClient + serviceBusQueuesClient servicebus.QueuesClient serviceBusTopicsClient servicebus.TopicsClient serviceBusSubscriptionsClient servicebus.SubscriptionsClient @@ -471,6 +472,12 @@ func (c *Config) getArmClient() (*ArmClient, error) { sbnc.Sender = autorest.CreateSender(withRequestLogging()) client.serviceBusNamespacesClient = sbnc + sbqc := servicebus.NewQueuesClientWithBaseURI(endpoint, c.SubscriptionID) + setUserAgent(&sbqc.Client) + sbqc.Authorizer = auth + sbqc.Sender = autorest.CreateSender(withRequestLogging()) + client.serviceBusQueuesClient = sbqc + sbtc := servicebus.NewTopicsClientWithBaseURI(endpoint, c.SubscriptionID) setUserAgent(&sbtc.Client) sbtc.Authorizer = auth diff --git a/azurerm/import_arm_servicebus_queue_test.go b/azurerm/import_arm_servicebus_queue_test.go new file mode 100644 index 000000000000..03d943e7d894 --- /dev/null +++ b/azurerm/import_arm_servicebus_queue_test.go @@ -0,0 +1,31 @@ +package azurerm + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAzureRMServiceBusQueue_importBasic(t *testing.T) { + resourceName := "azurerm_servicebus_queue.test" + + ri := acctest.RandInt() + config := testAccAzureRMServiceBusQueue_basic(ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusQueueDestroy, + Steps: []resource.TestStep{ + { + Config: config, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/azurerm/provider.go b/azurerm/provider.go index 2bf1b2ddf214..88d3839bd14f 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -104,6 +104,7 @@ func Provider() terraform.ResourceProvider { "azurerm_route": resourceArmRoute(), "azurerm_route_table": resourceArmRouteTable(), "azurerm_servicebus_namespace": resourceArmServiceBusNamespace(), + "azurerm_servicebus_queue": resourceArmServiceBusQueue(), "azurerm_servicebus_subscription": resourceArmServiceBusSubscription(), "azurerm_servicebus_topic": resourceArmServiceBusTopic(), "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), diff --git a/azurerm/resource_arm_servicebus_queue.go b/azurerm/resource_arm_servicebus_queue.go new file mode 100644 index 000000000000..3b8be54402c5 --- /dev/null +++ b/azurerm/resource_arm_servicebus_queue.go @@ -0,0 +1,255 @@ +package azurerm + +import ( + "fmt" + "log" + "net/http" + + "github.com/Azure/azure-sdk-for-go/arm/servicebus" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceArmServiceBusQueue() *schema.Resource { + return &schema.Resource{ + Create: resourceArmServiceBusQueueCreateUpdate, + Read: resourceArmServiceBusQueueRead, + Update: resourceArmServiceBusQueueCreateUpdate, + Delete: resourceArmServiceBusQueueDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "namespace_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": locationSchema(), + + "resource_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "auto_delete_on_idle": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "default_message_ttl": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "duplicate_detection_history_time_window": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "enable_batched_operations": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + + "enable_express": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + + "enable_partitioning": { + Type: schema.TypeBool, + Default: false, + Optional: true, + ForceNew: true, + }, + + "max_size_in_megabytes": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "requires_duplicate_detection": { + Type: schema.TypeBool, + Default: false, + Optional: true, + ForceNew: true, + }, + + "support_ordering": { + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + }, + } +} + +func resourceArmServiceBusQueueCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).serviceBusQueuesClient + log.Printf("[INFO] preparing arguments for AzureRM ServiceBus Queue creation/update.") + + name := d.Get("name").(string) + namespaceName := d.Get("namespace_name").(string) + location := d.Get("location").(string) + resGroup := d.Get("resource_group_name").(string) + + enableBatchedOps := d.Get("enable_batched_operations").(bool) + enableExpress := d.Get("enable_express").(bool) + enablePartitioning := d.Get("enable_partitioning").(bool) + maxSize := int64(d.Get("max_size_in_megabytes").(int)) + requiresDuplicateDetection := d.Get("requires_duplicate_detection").(bool) + supportOrdering := d.Get("support_ordering").(bool) + + parameters := servicebus.QueueCreateOrUpdateParameters{ + Name: &name, + Location: &location, + QueueProperties: &servicebus.QueueProperties{ + EnableBatchedOperations: &enableBatchedOps, + EnableExpress: &enableExpress, + EnablePartitioning: &enablePartitioning, + MaxSizeInMegabytes: &maxSize, + RequiresDuplicateDetection: &requiresDuplicateDetection, + SupportOrdering: &supportOrdering, + }, + } + + if autoDeleteOnIdle := d.Get("auto_delete_on_idle").(string); autoDeleteOnIdle != "" { + parameters.QueueProperties.AutoDeleteOnIdle = &autoDeleteOnIdle + } + + if defaultTTL := d.Get("default_message_ttl").(string); defaultTTL != "" { + parameters.QueueProperties.DefaultMessageTimeToLive = &defaultTTL + } + + if duplicateWindow := d.Get("duplicate_detection_history_time_window").(string); duplicateWindow != "" { + parameters.QueueProperties.DuplicateDetectionHistoryTimeWindow = &duplicateWindow + } + + // We need to retrieve the namespace because Premium namespace works differently from Basic and Standard, + // so it needs different rules applied to it. + namespace, nsErr := meta.(*ArmClient).serviceBusNamespacesClient.Get(resGroup, namespaceName) + if nsErr != nil { + return nsErr + } + + // Enforce Premium namespace to have partitioning enabled in Terraform. It is always enabled in Azure for + // Premium SKU. + if namespace.Sku.Name == servicebus.Premium && !d.Get("enable_partitioning").(bool) { + return fmt.Errorf("ServiceBus Queue (%s) must have Partitioning enabled for Premium SKU", name) + } + + // Enforce Premium namespace to have Express Entities disabled in Terraform since they are not supported for + // Premium SKU. + if namespace.Sku.Name == servicebus.Premium && d.Get("enable_express").(bool) { + return fmt.Errorf("ServiceBus Queue (%s) does not support Express Entities in Premium SKU and must be disabled", name) + } + + _, err := client.CreateOrUpdate(resGroup, namespaceName, name, parameters) + if err != nil { + return err + } + + read, err := client.Get(resGroup, namespaceName, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read ServiceBus Queue %s (resource group %s) ID", name, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmServiceBusQueueRead(d, meta) +} + +func resourceArmServiceBusQueueRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).serviceBusQueuesClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + namespaceName := id.Path["namespaces"] + name := id.Path["queues"] + + resp, err := client.Get(resGroup, namespaceName, name) + if err != nil { + return fmt.Errorf("Error making Read request on Azure ServiceBus Queue %s: %s", name, err) + } + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + d.Set("namespace_name", namespaceName) + d.Set("location", azureRMNormalizeLocation(*resp.Location)) + + if resp.QueueProperties == nil { + return fmt.Errorf("Missing QueueProperties in response for Azure ServiceBus Queue %s: %s", name, err) + } + + props := resp.QueueProperties + d.Set("auto_delete_on_idle", props.AutoDeleteOnIdle) + d.Set("default_message_ttl", props.DefaultMessageTimeToLive) + d.Set("duplicate_detection_history_time_window", props.DuplicateDetectionHistoryTimeWindow) + + d.Set("enable_batched_operations", props.EnableBatchedOperations) + d.Set("enable_express", props.EnableExpress) + d.Set("enable_partitioning", props.EnablePartitioning) + d.Set("requires_duplicate_detection", props.RequiresDuplicateDetection) + d.Set("support_ordering", props.SupportOrdering) + + maxSize := int(*props.MaxSizeInMegabytes) + + // If the queue is NOT in a premium namespace (ie. it is Basic or Standard) and partitioning is enabled + // then the max size returned by the API will be 16 times greater than the value set. + if *props.EnablePartitioning { + namespace, err := meta.(*ArmClient).serviceBusNamespacesClient.Get(resGroup, namespaceName) + if err != nil { + return err + } + + if namespace.Sku.Name != servicebus.Premium { + const partitionCount = 16 + maxSize = int(*props.MaxSizeInMegabytes / partitionCount) + } + } + + d.Set("max_size_in_megabytes", maxSize) + + return nil +} + +func resourceArmServiceBusQueueDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).serviceBusQueuesClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + namespaceName := id.Path["namespaces"] + name := id.Path["queues"] + + _, err = client.Delete(resGroup, namespaceName, name) + + return err +} diff --git a/azurerm/resource_arm_servicebus_queue_test.go b/azurerm/resource_arm_servicebus_queue_test.go new file mode 100644 index 000000000000..75733d079cac --- /dev/null +++ b/azurerm/resource_arm_servicebus_queue_test.go @@ -0,0 +1,326 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAzureRMServiceBusQueue_basic(t *testing.T) { + resourceName := "azurerm_servicebus_queue.test" + ri := acctest.RandInt() + config := testAccAzureRMServiceBusQueue_basic(ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusQueueDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusQueueExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enable_batched_operations", "false"), + resource.TestCheckResourceAttr(resourceName, "enable_express", "false"), + resource.TestCheckResourceAttr(resourceName, "enable_partitioning", "false"), + ), + }, + }, + }) +} + +func TestAccAzureRMServiceBusQueue_update(t *testing.T) { + resourceName := "azurerm_servicebus_queue.test" + ri := acctest.RandInt() + preConfig := testAccAzureRMServiceBusQueue_basic(ri) + postConfig := testAccAzureRMServiceBusQueue_update(ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusQueueDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusQueueExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enable_batched_operations", "false"), + resource.TestCheckResourceAttr(resourceName, "enable_express", "false"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "enable_batched_operations", "true"), + resource.TestCheckResourceAttr(resourceName, "enable_express", "true"), + resource.TestCheckResourceAttr(resourceName, "max_size_in_megabytes", "2048"), + ), + }, + }, + }) +} + +func TestAccAzureRMServiceBusQueue_enablePartitioningStandard(t *testing.T) { + resourceName := "azurerm_servicebus_queue.test" + ri := acctest.RandInt() + preConfig := testAccAzureRMServiceBusQueue_basic(ri) + postConfig := testAccAzureRMServiceBusQueue_enablePartitioningStandard(ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusQueueDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusQueueExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enable_partitioning", "false"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "enable_partitioning", "true"), + // Ensure size is read back in it's original value and not the x16 value returned by Azure + resource.TestCheckResourceAttr(resourceName, "max_size_in_megabytes", "5120"), + ), + }, + }, + }) +} + +func TestAccAzureRMServiceBusQueue_defaultEnablePartitioningPremium(t *testing.T) { + resourceName := "azurerm_servicebus_queue.test" + ri := acctest.RandInt() + config := testAccAzureRMServiceBusQueue_Premium(ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusQueueDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusQueueExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "enable_partitioning", "true"), + resource.TestCheckResourceAttr(resourceName, "enable_express", "false"), + ), + }, + }, + }) +} + +func TestAccAzureRMServiceBusQueue_enableDuplicateDetection(t *testing.T) { + resourceName := "azurerm_servicebus_queue.test" + ri := acctest.RandInt() + preConfig := testAccAzureRMServiceBusQueue_basic(ri) + postConfig := testAccAzureRMServiceBusQueue_enableDuplicateDetection(ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMServiceBusQueueDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMServiceBusQueueExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "requires_duplicate_detection", "false"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "requires_duplicate_detection", "true"), + ), + }, + }, + }) +} + +func testCheckAzureRMServiceBusQueueDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).serviceBusQueuesClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_servicebus_queue" { + continue + } + + name := rs.Primary.Attributes["name"] + namespaceName := rs.Primary.Attributes["namespace_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(resourceGroup, namespaceName, name) + if err != nil { + if resp.StatusCode == http.StatusNotFound { + return nil + } + return err + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("ServiceBus Queue still exists:\n%#v", resp.QueueProperties) + } + } + + return nil +} + +func testCheckAzureRMServiceBusQueueExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + queueName := rs.Primary.Attributes["name"] + namespaceName := rs.Primary.Attributes["namespace_name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for queue: %s", queueName) + } + + client := testAccProvider.Meta().(*ArmClient).serviceBusQueuesClient + + resp, err := client.Get(resourceGroup, namespaceName, queueName) + if err != nil { + return fmt.Errorf("Bad: Get on serviceBusQueuesClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Queue %q (resource group: %q) does not exist", namespaceName, resourceGroup) + } + + return nil + } +} + +func testAccAzureRMServiceBusQueue_basic(rInt int) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "West US" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + sku = "standard" +} + +resource "azurerm_servicebus_queue" "test" { + name = "acctestservicebusqueue-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + namespace_name = "${azurerm_servicebus_namespace.test.name}" +} +`, rInt, rInt, rInt) +} + +func testAccAzureRMServiceBusQueue_Premium(rInt int) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "West US" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + sku = "premium" +} + +resource "azurerm_servicebus_queue" "test" { + name = "acctestservicebusqueue-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + enable_partitioning = true + enable_express = false +} +`, rInt, rInt, rInt) +} + +func testAccAzureRMServiceBusQueue_update(rInt int) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "West US" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + sku = "standard" +} + +resource "azurerm_servicebus_queue" "test" { + name = "acctestservicebusqueue-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + enable_batched_operations = true + enable_express = true + max_size_in_megabytes = 2048 +} +`, rInt, rInt, rInt) +} + +func testAccAzureRMServiceBusQueue_enablePartitioningStandard(rInt int) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "West US" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + sku = "standard" +} + +resource "azurerm_servicebus_queue" "test" { + name = "acctestservicebusqueue-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + enable_partitioning = true + max_size_in_megabytes = 5120 +} +`, rInt, rInt, rInt) +} + +func testAccAzureRMServiceBusQueue_enableDuplicateDetection(rInt int) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "West US" +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + sku = "standard" +} + +resource "azurerm_servicebus_queue" "test" { + name = "acctestservicebusqueue-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + requires_duplicate_detection = true +} +`, rInt, rInt, rInt) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 22e434fe8a75..1dc68166ff3c 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -288,6 +288,10 @@ azurerm_servicebus_namespace +