From 9979b5936bffae1fa9d66f5dffe3f243ecb9c168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maksymilian=20Bogu=C5=84?= Date: Mon, 10 Jun 2019 11:58:08 +0200 Subject: [PATCH 1/6] iothub_endpoint_storage_container --- azurerm/helpers/validate/iothub.go | 19 ++ azurerm/helpers/validate/storage_container.go | 25 ++ azurerm/provider.go | 1 + azurerm/resource_arm_iothub.go | 45 +-- ...e_arm_iothub_endpoint_storage_container.go | 290 ++++++++++++++++++ ..._iothub_endpoint_storage_container_test.go | 231 ++++++++++++++ azurerm/resource_arm_storage_container.go | 23 +- .../resource_arm_storage_container_test.go | 5 +- website/docs/r/iothub.html.markdown | 2 + ...b_endpoint_storage_container.html.markdown | 102 ++++++ 10 files changed, 691 insertions(+), 52 deletions(-) create mode 100644 azurerm/helpers/validate/storage_container.go create mode 100644 azurerm/resource_arm_iothub_endpoint_storage_container.go create mode 100644 azurerm/resource_arm_iothub_endpoint_storage_container_test.go create mode 100644 website/docs/r/iothub_endpoint_storage_container.html.markdown diff --git a/azurerm/helpers/validate/iothub.go b/azurerm/helpers/validate/iothub.go index ef467d561cba..0909fa42abb5 100644 --- a/azurerm/helpers/validate/iothub.go +++ b/azurerm/helpers/validate/iothub.go @@ -26,3 +26,22 @@ func IoTHubConsumerGroupName(v interface{}, k string) (warnings []string, errors return warnings, errors } + +func IoTHubEndpointName(v interface{}, _ string) (warnings []string, errors []error) { + value := v.(string) + + reservedNames := []string{ + "events", + "operationsMonitoringEvents", + "fileNotifications", + "$default", + } + + for _, name := range reservedNames { + if name == value { + errors = append(errors, fmt.Errorf("The reserved endpoint name %s could not be used as a name for a custom endpoint", name)) + } + } + + return warnings, errors +} diff --git a/azurerm/helpers/validate/storage_container.go b/azurerm/helpers/validate/storage_container.go new file mode 100644 index 000000000000..78272ee65ef2 --- /dev/null +++ b/azurerm/helpers/validate/storage_container.go @@ -0,0 +1,25 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func StorageContainerName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + + if !regexp.MustCompile(`^\$root$|^\$web$|^[0-9a-z-]+$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "only lowercase alphanumeric characters and hyphens allowed in %q: %q", + k, value)) + } + if len(value) < 3 || len(value) > 63 { + errors = append(errors, fmt.Errorf( + "%q must be between 3 and 63 characters: %q", k, value)) + } + if regexp.MustCompile(`^-`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q cannot begin with a hyphen: %q", k, value)) + } + return warnings, errors +} diff --git a/azurerm/provider.go b/azurerm/provider.go index 656f10c15c87..ba512002d652 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -248,6 +248,7 @@ func Provider() terraform.ResourceProvider { "azurerm_iot_dps_certificate": resourceArmIotDPSCertificate(), "azurerm_iothub_consumer_group": resourceArmIotHubConsumerGroup(), "azurerm_iothub": resourceArmIotHub(), + "azurerm_iothub_endpoint_storage_container": resourceArmIotHubEndpointStorageContainer(), "azurerm_iothub_shared_access_policy": resourceArmIotHubSharedAccessPolicy(), "azurerm_key_vault_access_policy": resourceArmKeyVaultAccessPolicy(), "azurerm_key_vault_certificate": resourceArmKeyVaultCertificate(), diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 7640f1c7d4b9..d67aa632a427 100755 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -224,6 +224,7 @@ func resourceArmIotHub() *schema.Resource { "endpoint": { Type: schema.TypeList, Optional: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": { @@ -254,7 +255,7 @@ func resourceArmIotHub() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ValidateFunc: validateIoTHubEndpointName, + ValidateFunc: validate.IoTHubEndpointName, }, "batch_frequency_in_seconds": { Type: schema.TypeInt, @@ -460,11 +461,21 @@ func resourceArmIotHubCreateUpdate(d *schema.ResourceData, meta interface{}) err location := azure.NormalizeLocation(d.Get("location").(string)) skuInfo := expandIoTHubSku(d) t := d.Get("tags").(map[string]interface{}) + fallbackRoute := expandIoTHubFallbackRoute(d) + routes := expandIoTHubRoutes(d) - endpoints, err := expandIoTHubEndpoints(d, subscriptionID) - if err != nil { - return fmt.Errorf("Error expanding `endpoint`: %+v", err) + routingProperties := devices.RoutingProperties{ + Routes: routes, + FallbackRoute: fallbackRoute, + } + + if _, ok := d.GetOk("endpoint"); ok { + endpoints, err := expandIoTHubEndpoints(d, subscriptionID) + if err != nil { + return fmt.Errorf("Error expanding `endpoint`: %+v", err) + } + routingProperties.Endpoints = endpoints } storageEndpoints, messagingEndpoints, enableFileUploadNotifications, err := expandIoTHubFileUpload(d) @@ -472,7 +483,6 @@ func resourceArmIotHubCreateUpdate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error expanding `file_upload`: %+v", err) } - routes := expandIoTHubRoutes(d) ipFilterRules := expandIPFilterRules(d) properties := devices.IotHubDescription{ @@ -481,11 +491,7 @@ func resourceArmIotHubCreateUpdate(d *schema.ResourceData, meta interface{}) err Sku: skuInfo, Properties: &devices.IotHubProperties{ IPFilterRules: ipFilterRules, - Routing: &devices.RoutingProperties{ - Endpoints: endpoints, - Routes: routes, - FallbackRoute: fallbackRoute, - }, + Routing: &routingProperties, StorageEndpoints: storageEndpoints, MessagingEndpoints: messagingEndpoints, EnableFileUploadNotifications: &enableFileUploadNotifications, @@ -1052,25 +1058,6 @@ func flattenIoTHubFallbackRoute(input *devices.RoutingProperties) []interface{} return []interface{}{output} } -func validateIoTHubEndpointName(v interface{}, _ string) (warnings []string, errors []error) { - value := v.(string) - - reservedNames := []string{ - "events", - "operationsMonitoringEvents", - "fileNotifications", - "$default", - } - - for _, name := range reservedNames { - if name == value { - errors = append(errors, fmt.Errorf("The reserved endpoint name %s could not be used as a name for a custom endpoint", name)) - } - } - - return warnings, errors -} - func validateIoTHubFileNameFormat(v interface{}, k string) (warnings []string, errors []error) { value := v.(string) diff --git a/azurerm/resource_arm_iothub_endpoint_storage_container.go b/azurerm/resource_arm_iothub_endpoint_storage_container.go new file mode 100644 index 000000000000..87acffc9acec --- /dev/null +++ b/azurerm/resource_arm_iothub_endpoint_storage_container.go @@ -0,0 +1,290 @@ +package azurerm + +import ( + "fmt" + "regexp" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmIotHubEndpointStorageContainer() *schema.Resource { + return &schema.Resource{ + Create: resourceArmIotHubEndpointStorageContainerCreateUpdate, + Read: resourceArmIotHubEndpointStorageContainerRead, + Update: resourceArmIotHubEndpointStorageContainerCreateUpdate, + Delete: resourceArmIotHubEndpointStorageContainerDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubEndpointName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "iothub_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubName, + }, + + "container_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.StorageContainerName, + }, + + "file_name_format": { + Type: schema.TypeString, + Optional: true, + Default: false, + }, + + "batch_frequency_in_seconds": { + Type: schema.TypeInt, + Optional: true, + Default: 300, + ValidateFunc: validation.IntBetween(60, 720), + }, + + "max_chunk_size_in_bytes": { + Type: schema.TypeInt, + Optional: true, + Default: 314572800, + ValidateFunc: validation.IntBetween(10485760, 524288000), + }, + + "connection_string": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + accountKeyRegex := regexp.MustCompile("AccountKey=[^;]+") + + maskedNew := accountKeyRegex.ReplaceAllString(new, "AccountKey=****") + return (new == d.Get(k).(string)) && (maskedNew == old) + }, + Sensitive: true, + }, + + "encoding": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(devices.Avro), + string(devices.AvroDeflate), + string(devices.JSON), + }, true), + }, + }, + } +} + +func resourceArmIotHubEndpointStorageContainerCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + subscriptionID := meta.(*ArmClient).subscriptionId + + iothubName := d.Get("iothub_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + azureRMLockByName(iothubName, iothubResourceName) + defer azureRMUnlockByName(iothubName, iothubResourceName) + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + endpointName := d.Get("name").(string) + + resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName) + + connectionStr := d.Get("connection_string").(string) + containerName := d.Get("container_name").(string) + fileNameFormat := d.Get("file_name_format").(string) + batchFrequencyInSeconds := int32(d.Get("batch_frequency_in_seconds").(int)) + maxChunkSizeInBytes := int32(d.Get("max_chunk_size_in_bytes").(int)) + encoding := d.Get("encoding").(string) + + storageContainerEndpoint := devices.RoutingStorageContainerProperties{ + ConnectionString: &connectionStr, + Name: &endpointName, + SubscriptionID: &subscriptionID, + ResourceGroup: &resourceGroup, + ContainerName: &containerName, + FileNameFormat: &fileNameFormat, + BatchFrequencyInSeconds: &batchFrequencyInSeconds, + MaxChunkSizeInBytes: &maxChunkSizeInBytes, + Encoding: devices.Encoding(encoding), + } + + routing := iothub.Properties.Routing + + if routing == nil { + routing = &devices.RoutingProperties{} + } + + if routing.Endpoints == nil { + routing.Endpoints = &devices.RoutingEndpoints{} + } + + if routing.Endpoints.StorageContainers == nil { + storageContainers := make([]devices.RoutingStorageContainerProperties, 0) + routing.Endpoints.StorageContainers = &storageContainers + } + + endpoints := make([]devices.RoutingStorageContainerProperties, 0) + + alreadyExists := false + for _, existingEndpoint := range *routing.Endpoints.StorageContainers { + if strings.EqualFold(*existingEndpoint.Name, endpointName) { + if d.IsNewResource() && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_endpoint_storage_container", resourceId) + } + endpoints = append(endpoints, storageContainerEndpoint) + alreadyExists = true + + } else { + endpoints = append(endpoints, existingEndpoint) + } + } + + if d.IsNewResource() { + endpoints = append(endpoints, storageContainerEndpoint) + } else if !alreadyExists { + return fmt.Errorf("Unable to find Storage Container Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup) + } + + routing.Endpoints.StorageContainers = &endpoints + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error creating/updating IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for the completion of the creating/updating of IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + d.SetId(resourceId) + + return resourceArmIotHubEndpointStorageContainerRead(d, meta) +} + +func resourceArmIotHubEndpointStorageContainerRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubEndpointId.ResourceGroup + iothubName := parsedIothubEndpointId.Path["IotHubs"] + endpointName := parsedIothubEndpointId.Path["Endpoints"] + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + d.Set("name", endpointName) + d.Set("iothub_name", iothubName) + d.Set("resource_group_name", resourceGroup) + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + + if endpoints := iothub.Properties.Routing.Endpoints.StorageContainers; endpoints != nil { + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + d.Set("connection_string", endpoint.ConnectionString) + d.Set("container_name", endpoint.ContainerName) + d.Set("file_name_format", endpoint.FileNameFormat) + d.Set("batch_frequency_in_seconds", endpoint.BatchFrequencyInSeconds) + d.Set("max_chunk_size_in_bytes", endpoint.MaxChunkSizeInBytes) + d.Set("encoding", endpoint.Encoding) + } + } + } + + return nil +} + +func resourceArmIotHubEndpointStorageContainerDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubEndpointId.ResourceGroup + iothubName := parsedIothubEndpointId.Path["IotHubs"] + endpointName := parsedIothubEndpointId.Path["Endpoints"] + + azureRMLockByName(iothubName, iothubResourceName) + defer azureRMUnlockByName(iothubName, iothubResourceName) + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + endpoints := iothub.Properties.Routing.Endpoints.StorageContainers + + if endpoints == nil { + return nil + } + + updatedEndpoints := make([]devices.RoutingStorageContainerProperties, 0) + for _, endpoint := range *endpoints { + if !strings.EqualFold(*endpoint.Name, endpointName) { + updatedEndpoints = append(updatedEndpoints, endpoint) + } + } + + iothub.Properties.Routing.Endpoints.StorageContainers = &updatedEndpoints + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error updating IotHub %q (Resource Group %q) with Storage Container Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for IotHub %q (Resource Group %q) to finish updating Storage Container Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_iothub_endpoint_storage_container_test.go b/azurerm/resource_arm_iothub_endpoint_storage_container_test.go new file mode 100644 index 000000000000..31fbde0c51db --- /dev/null +++ b/azurerm/resource_arm_iothub_endpoint_storage_container_test.go @@ -0,0 +1,231 @@ +package azurerm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMIotHubEndpointStorageContainer_basic(t *testing.T) { + resourceName := "azurerm_iothub_endpoint_storage_container.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAzureRMIotHubEndpointStorageContainerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubEndpointStorageContainer_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMIotHubEndpointStorageContainerExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "file_name_format", "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}"), + resource.TestCheckResourceAttr(resourceName, "batch_frequency_in_seconds", "60"), + resource.TestCheckResourceAttr(resourceName, "max_chunk_size_in_bytes", "10485760"), + resource.TestCheckResourceAttr(resourceName, "encoding", "JSON"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMIotHubEndpointStorageContainer_requiresImport(t *testing.T) { + if !requireResourcesToBeImported { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_iothub_endpoint_storage_container.test" + rInt := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAzureRMIotHubEndpointStorageContainerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubEndpointStorageContainer_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMIotHubEndpointStorageContainerExists(resourceName), + ), + }, + { + Config: testAccAzureRMIotHubEndpointStorageContainer_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_iothub_endpoint_storage_container"), + }, + }, + }) +} + +func testAccAzureRMIotHubEndpointStorageContainer_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acc%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "test" { + name = "acctestcont" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "private" +} + +resource "azurerm_iothub" "test" { + name = "acctestIoTHub-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + sku { + name = "B1" + tier = "Basic" + capacity = "1" + } + + tags = { + purpose = "testing" + } +} + +resource "azurerm_iothub_endpoint_storage_container" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + name = "acctest" + + container_name = "acctestcont" + connection_string = "${azurerm_storage_account.test.primary_blob_connection_string}" + + file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}" + batch_frequency_in_seconds = 60 + max_chunk_size_in_bytes = 10485760 + encoding = "JSON" +} +`, rInt, location) +} + +func testAccAzureRMIotHubEndpointStorageContainer_requiresImport(rInt int, location string) string { + template := testAccAzureRMIotHubEndpointStorageContainer_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_iothub_endpoint_storage_container" "import" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + name = "acctest" + + container_name = "acctestcont" + connection_string = "${azurerm_storage_account.test.primary_blob_connection_string}" + + file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}" + batch_frequency_in_seconds = 60 + max_chunk_size_in_bytes = 10485760 + encoding = "JSON" +} +`, template) +} + +func testAccAzureRMIotHubEndpointStorageContainerExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + parsedIothubId, err := azure.ParseAzureResourceID(rs.Primary.ID) + if err != nil { + return err + } + iothubName := parsedIothubId.Path["IotHubs"] + endpointName := parsedIothubId.Path["Endpoints"] + resourceGroup := parsedIothubId.ResourceGroup + + client := testAccProvider.Meta().(*ArmClient).iothub.ResourceClient + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return fmt.Errorf("Bad: No endpoint %s defined for IotHub %s", endpointName, iothubName) + } + endpoints := iothub.Properties.Routing.Endpoints.StorageContainers + + if endpoints == nil { + return fmt.Errorf("Bad: No Storage Container endpoint %s defined for IotHub %s", endpointName, iothubName) + } + + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + return nil + } + } + + return fmt.Errorf("Bad: No Storage Container endpoint %s defined for IotHub %s", endpointName, iothubName) + + } +} + +func testAccAzureRMIotHubEndpointStorageContainerDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).iothub.ResourceClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_iothub_endpoint_storage_container" { + continue + } + + endpointName := rs.Primary.Attributes["name"] + iothubName := rs.Primary.Attributes["iothub_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return nil + } + + return fmt.Errorf("Bad: Get on iothubResourceClient: %+v", err) + } + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + endpoints := iothub.Properties.Routing.Endpoints.StorageContainers + + if endpoints == nil { + return nil + } + + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + return fmt.Errorf("Bad: Storage Container endpoint %s still exists on IoTHb %s", endpointName, iothubName) + } + } + } + return nil +} diff --git a/azurerm/resource_arm_storage_container.go b/azurerm/resource_arm_storage_container.go index 1ff39359b55b..282075dfaabf 100644 --- a/azurerm/resource_arm_storage_container.go +++ b/azurerm/resource_arm_storage_container.go @@ -3,12 +3,12 @@ package azurerm import ( "fmt" "log" - "regexp" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" "github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers" @@ -32,7 +32,7 @@ func resourceArmStorageContainer() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validateArmStorageContainerName, + ValidateFunc: validate.StorageContainerName, }, "storage_account_name": { @@ -296,22 +296,3 @@ func flattenStorageContainerAccessLevel(input containers.AccessLevel) string { return string(input) } - -func validateArmStorageContainerName(v interface{}, k string) (warnings []string, errors []error) { - value := v.(string) - - if !regexp.MustCompile(`^\$root$|^\$web$|^[0-9a-z-]+$`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "only lowercase alphanumeric characters and hyphens allowed in %q: %q", - k, value)) - } - if len(value) < 3 || len(value) > 63 { - errors = append(errors, fmt.Errorf( - "%q must be between 3 and 63 characters: %q", k, value)) - } - if regexp.MustCompile(`^-`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "%q cannot begin with a hyphen: %q", k, value)) - } - return warnings, errors -} diff --git a/azurerm/resource_arm_storage_container_test.go b/azurerm/resource_arm_storage_container_test.go index 9fa5062777d4..dd662367a976 100644 --- a/azurerm/resource_arm_storage_container_test.go +++ b/azurerm/resource_arm_storage_container_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -499,7 +500,7 @@ func TestValidateArmStorageContainerName(t *testing.T) { "$web", } for _, v := range validNames { - _, errors := validateArmStorageContainerName(v, "name") + _, errors := validate.StorageContainerName(v, "name") if len(errors) != 0 { t.Fatalf("%q should be a valid Storage Container Name: %q", v, errors) } @@ -516,7 +517,7 @@ func TestValidateArmStorageContainerName(t *testing.T) { strings.Repeat("w", 65), } for _, v := range invalidNames { - _, errors := validateArmStorageContainerName(v, "name") + _, errors := validate.StorageContainerName(v, "name") if len(errors) == 0 { t.Fatalf("%q should be an invalid Storage Container Name", v) } diff --git a/website/docs/r/iothub.html.markdown b/website/docs/r/iothub.html.markdown index b41347637c4c..56fe897904d3 100644 --- a/website/docs/r/iothub.html.markdown +++ b/website/docs/r/iothub.html.markdown @@ -10,6 +10,8 @@ description: |- Manages an IotHub +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` resource in case of Storage Account Endpoints - but the two cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. + ## Example Usage ```hcl diff --git a/website/docs/r/iothub_endpoint_storage_container.html.markdown b/website/docs/r/iothub_endpoint_storage_container.html.markdown new file mode 100644 index 000000000000..7a9fbf92d53d --- /dev/null +++ b/website/docs/r/iothub_endpoint_storage_container.html.markdown @@ -0,0 +1,102 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_iothub_endpoint_storage_container" +sidebar_current: "docs-azurerm-resource-messaging-iothub-endpoint-storage-container-x" +description: |- + Manages an IotHub Storage Container Endpoint +--- + +# azurerm_iothub_endpoint_storage_container + +Manages an IotHub Storage Container Endpoint + +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` resource in case of Storage Account Endpoints - but the two cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. + + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "resourceGroup1" + location = "West US" +} + +resource "azurerm_storage_account" "example" { + name = "example" + resource_group_name = "${azurerm_resource_group.example.name}" + location = "${azurerm_resource_group.example.location}" + account_tier = "Standard" + account_replication_type = "LRS" + } + +resource "azurerm_storage_container" "example" { + name = "acctestcont" + resource_group_name = "${azurerm_resource_group.example.name}" + storage_account_name = "${azurerm_storage_account.example.name}" + container_access_type = "private" +} + +resource "azurerm_iothub" "example" { + name = "example" + resource_group_name = "${azurerm_resource_group.example.name}" + location = "${azurerm_resource_group.example.location}" + + sku { + name = "S1" + tier = "Standard" + capacity = "1" + } +} + +resource "azurerm_iothub_endpoint_storage_container" "example" { + resource_group_name = "${azurerm_resource_group.example.name}" + iothub_name = "${azurerm_iothub.example.name}" + name = "acctest" + + container_name = "acctestcont" + connection_string = "${azurerm_storage_account.example.primary_blob_connection_string}" + + file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}" + batch_frequency_in_seconds = 60 + max_chunk_size_in_bytes = 10485760 + encoding = "JSON" +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the endpoint. The name must be unique across endpoint types. The following names are reserved: `events`, `operationsMonitoringEvents`, `fileNotifications` and `$default`. + +* `resource_group_name` - (Required) The name of the resource group under which the IotHub Storage Container Endpoint resource has to be created. Changing this forces a new resource to be created. + +* `iothub_name` - (Required) The name of the IoTHub to which this Storage Container Endpoint belongs. Changing this forces a new resource to be created. + + +* `connection_string` - (Required) The connection string for the endpoint. + +* `batch_frequency_in_seconds` - (Optional) Time interval at which blobs are written to storage. Value should be between 60 and 720 seconds. Default value is 300 seconds. + +* `max_chunk_size_in_bytes` - (Optional) Maximum number of bytes for each blob written to storage. Value should be between 10485760(10MB) and 524288000(500MB). Default value is 314572800(300MB). + +* `container_name` - (Required) The name of storage container in the storage account. +* +* `encoding` - (Optional) Encoding that is used to serialize messages to blobs. Supported values are 'avro' and 'avrodeflate'. Default value is 'avro'. + +* `file_name_format` - (Optional) File name format for the blob. Default format is ``{iothub}/{partition}/{YYYY}/{MM}/{DD}/{HH}/{mm}``. All parameters are mandatory but can be reordered. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the IoTHub Storage Container Endpoint. + +## Import + +IoTHub Storage Container Endpoint can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_iothub_endpoint_storage_container.storage_container1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Devices/IotHubs/hub1/Endpoints/storage_container_endpoint1 +``` From c429478a4237c72b6fc51efbb2e2886dc36c4ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maksymilian=20Bogu=C5=84?= Date: Fri, 14 Jun 2019 23:05:15 +0200 Subject: [PATCH 2/6] azurerm_iothub_endpoint_eventhub --- azurerm/provider.go | 1 + .../resource_arm_iothub_endpoint_eventhub.go | 238 ++++++++++++++++++ ...ource_arm_iothub_endpoint_eventhub_test.go | 226 +++++++++++++++++ website/docs/r/iothub.html.markdown | 2 +- .../r/iothub_endpoint_eventhub.html.markdown | 96 +++++++ ...b_endpoint_storage_container.html.markdown | 3 +- 6 files changed, 563 insertions(+), 3 deletions(-) create mode 100644 azurerm/resource_arm_iothub_endpoint_eventhub.go create mode 100644 azurerm/resource_arm_iothub_endpoint_eventhub_test.go create mode 100644 website/docs/r/iothub_endpoint_eventhub.html.markdown diff --git a/azurerm/provider.go b/azurerm/provider.go index ba512002d652..c7f571e2bd2c 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -248,6 +248,7 @@ func Provider() terraform.ResourceProvider { "azurerm_iot_dps_certificate": resourceArmIotDPSCertificate(), "azurerm_iothub_consumer_group": resourceArmIotHubConsumerGroup(), "azurerm_iothub": resourceArmIotHub(), + "azurerm_iothub_endpoint_eventhub": resourceArmIotHubEndpointEventHub(), "azurerm_iothub_endpoint_storage_container": resourceArmIotHubEndpointStorageContainer(), "azurerm_iothub_shared_access_policy": resourceArmIotHubSharedAccessPolicy(), "azurerm_key_vault_access_policy": resourceArmKeyVaultAccessPolicy(), diff --git a/azurerm/resource_arm_iothub_endpoint_eventhub.go b/azurerm/resource_arm_iothub_endpoint_eventhub.go new file mode 100644 index 000000000000..8fd632d21da8 --- /dev/null +++ b/azurerm/resource_arm_iothub_endpoint_eventhub.go @@ -0,0 +1,238 @@ +package azurerm + +import ( + "fmt" + "regexp" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmIotHubEndpointEventHub() *schema.Resource { + return &schema.Resource{ + Create: resourceArmIotHubEndpointEventHubCreateUpdate, + Read: resourceArmIotHubEndpointEventHubRead, + Update: resourceArmIotHubEndpointEventHubCreateUpdate, + Delete: resourceArmIotHubEndpointEventHubDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubEndpointName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "iothub_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubName, + }, + + "connection_string": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + sharedAccessKeyRegex := regexp.MustCompile("SharedAccessKey=[^;]+") + sbProtocolRegex := regexp.MustCompile("sb://([^:]+)(:5671)?/;") + + maskedNew := sbProtocolRegex.ReplaceAllString(new, "sb://$1:5671/;") + maskedNew = sharedAccessKeyRegex.ReplaceAllString(maskedNew, "SharedAccessKey=****") + return (new == d.Get(k).(string)) && (maskedNew == old) + }, + Sensitive: true, + }, + }, + } +} + +func resourceArmIotHubEndpointEventHubCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + subscriptionID := meta.(*ArmClient).subscriptionId + + iothubName := d.Get("iothub_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + azureRMLockByName(iothubName, iothubResourceName) + defer azureRMUnlockByName(iothubName, iothubResourceName) + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + endpointName := d.Get("name").(string) + + resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName) + + connectionStr := d.Get("connection_string").(string) + + eventhubEndpoint := devices.RoutingEventHubProperties{ + ConnectionString: &connectionStr, + Name: &endpointName, + SubscriptionID: &subscriptionID, + ResourceGroup: &resourceGroup, + } + + routing := iothub.Properties.Routing + + if routing == nil { + routing = &devices.RoutingProperties{} + } + + if routing.Endpoints == nil { + routing.Endpoints = &devices.RoutingEndpoints{} + } + + if routing.Endpoints.EventHubs == nil { + eventHubs := make([]devices.RoutingEventHubProperties, 0) + routing.Endpoints.EventHubs = &eventHubs + } + + endpoints := make([]devices.RoutingEventHubProperties, 0) + + alreadyExists := false + for _, existingEndpoint := range *routing.Endpoints.EventHubs { + if strings.EqualFold(*existingEndpoint.Name, endpointName) { + if d.IsNewResource() && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_endpoint_eventhub", resourceId) + } + endpoints = append(endpoints, eventhubEndpoint) + alreadyExists = true + + } else { + endpoints = append(endpoints, existingEndpoint) + } + } + + if d.IsNewResource() { + endpoints = append(endpoints, eventhubEndpoint) + } else if !alreadyExists { + return fmt.Errorf("Unable to find EventHub Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup) + } + + routing.Endpoints.EventHubs = &endpoints + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error creating/updating IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for the completion of the creating/updating of IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + d.SetId(resourceId) + + return resourceArmIotHubEndpointEventHubRead(d, meta) +} + +func resourceArmIotHubEndpointEventHubRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubEndpointId.ResourceGroup + iothubName := parsedIothubEndpointId.Path["IotHubs"] + endpointName := parsedIothubEndpointId.Path["Endpoints"] + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + d.Set("name", endpointName) + d.Set("iothub_name", iothubName) + d.Set("resource_group_name", resourceGroup) + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + + if endpoints := iothub.Properties.Routing.Endpoints.EventHubs; endpoints != nil { + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + d.Set("connection_string", endpoint.ConnectionString) + } + } + } + + return nil +} + +func resourceArmIotHubEndpointEventHubDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubEndpointId.ResourceGroup + iothubName := parsedIothubEndpointId.Path["IotHubs"] + endpointName := parsedIothubEndpointId.Path["Endpoints"] + + azureRMLockByName(iothubName, iothubResourceName) + defer azureRMUnlockByName(iothubName, iothubResourceName) + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + endpoints := iothub.Properties.Routing.Endpoints.EventHubs + + if endpoints == nil { + return nil + } + + updatedEndpoints := make([]devices.RoutingEventHubProperties, 0) + for _, endpoint := range *endpoints { + if !strings.EqualFold(*endpoint.Name, endpointName) { + updatedEndpoints = append(updatedEndpoints, endpoint) + } + } + + iothub.Properties.Routing.Endpoints.EventHubs = &updatedEndpoints + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error updating IotHub %q (Resource Group %q) with EventHub Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for IotHub %q (Resource Group %q) to finish updating EventHub Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_iothub_endpoint_eventhub_test.go b/azurerm/resource_arm_iothub_endpoint_eventhub_test.go new file mode 100644 index 000000000000..22c748214412 --- /dev/null +++ b/azurerm/resource_arm_iothub_endpoint_eventhub_test.go @@ -0,0 +1,226 @@ +package azurerm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMIotHubEndpointEventHub_basic(t *testing.T) { + resourceName := "azurerm_iothub_endpoint_eventhub.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAzureRMIotHubEndpointStorageContainerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubEndpointEventHub_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMIotHubEndpointEventHubExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMIotHubEndpointEventHub_requiresImport(t *testing.T) { + if !requireResourcesToBeImported { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_iothub_endpoint_eventhub.test" + rInt := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAzureRMIotHubEndpointEventHubDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubEndpointEventHub_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMIotHubEndpointEventHubExists(resourceName), + ), + }, + { + Config: testAccAzureRMIotHubEndpointEventHub_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_iothub_endpoint_eventhub"), + }, + }, + }) +} + +func testAccAzureRMIotHubEndpointEventHub_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_eventhub_namespace" "test" { + name = "acctesteventhubnamespace-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Basic" +} + +resource "azurerm_eventhub" "test" { + name = "acctesteventhub-%[1]d" + namespace_name = "${azurerm_eventhub_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + partition_count = 2 + message_retention = 1 +} + +resource "azurerm_eventhub_authorization_rule" "test" { + name = "acctest-%[1]d" + namespace_name = "${azurerm_eventhub_namespace.test.name}" + eventhub_name = "${azurerm_eventhub.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + listen = false + send = true + manage = false +} + +resource "azurerm_iothub" "test" { + name = "acctestIoTHub-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + sku { + name = "B1" + tier = "Basic" + capacity = "1" + } + + tags = { + purpose = "testing" + } +} + +resource "azurerm_iothub_endpoint_eventhub" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + name = "acctest" + + connection_string = "${azurerm_eventhub_authorization_rule.test.primary_connection_string}" +} +`, rInt, location) +} + +func testAccAzureRMIotHubEndpointEventHub_requiresImport(rInt int, location string) string { + template := testAccAzureRMIotHubEndpointEventHub_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_iothub_endpoint_eventhub" "import" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + name = "acctest" + + connection_string = "${azurerm_eventhub_authorization_rule.test.primary_connection_string}" +} +`, template) +} + +func testAccAzureRMIotHubEndpointEventHubExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + parsedIothubId, err := azure.ParseAzureResourceID(rs.Primary.ID) + if err != nil { + return err + } + iothubName := parsedIothubId.Path["IotHubs"] + endpointName := parsedIothubId.Path["Endpoints"] + resourceGroup := parsedIothubId.ResourceGroup + + client := testAccProvider.Meta().(*ArmClient).iothub.ResourceClient + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return fmt.Errorf("Bad: No endpoint %s defined for IotHub %s", endpointName, iothubName) + } + endpoints := iothub.Properties.Routing.Endpoints.EventHubs + + if endpoints == nil { + return fmt.Errorf("Bad: No EventHub endpoint %s defined for IotHub %s", endpointName, iothubName) + } + + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + return nil + } + } + + return fmt.Errorf("Bad: No EventHub endpoint %s defined for IotHub %s", endpointName, iothubName) + + } +} + +func testAccAzureRMIotHubEndpointEventHubDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).iothub.ResourceClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_iothub_endpoint_eventhub" { + continue + } + + endpointName := rs.Primary.Attributes["name"] + iothubName := rs.Primary.Attributes["iothub_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return nil + } + + return fmt.Errorf("Bad: Get on iothubResourceClient: %+v", err) + } + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + endpoints := iothub.Properties.Routing.Endpoints.EventHubs + + if endpoints == nil { + return nil + } + + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + return fmt.Errorf("Bad: EventHub endpoint %s still exists on IoTHb %s", endpointName, iothubName) + } + } + } + return nil +} diff --git a/website/docs/r/iothub.html.markdown b/website/docs/r/iothub.html.markdown index 56fe897904d3..8348bd3df707 100644 --- a/website/docs/r/iothub.html.markdown +++ b/website/docs/r/iothub.html.markdown @@ -10,7 +10,7 @@ description: |- Manages an IotHub -~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` resource in case of Storage Account Endpoints - but the two cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` and `azurerm_iothub_endpoint_eventhub` resources - but the two ways of definig the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` or `azurerm_iothub_endpoint_eventhub` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. ## Example Usage diff --git a/website/docs/r/iothub_endpoint_eventhub.html.markdown b/website/docs/r/iothub_endpoint_eventhub.html.markdown new file mode 100644 index 000000000000..d9748ef54564 --- /dev/null +++ b/website/docs/r/iothub_endpoint_eventhub.html.markdown @@ -0,0 +1,96 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_iothub_endpoint_eventhub" +sidebar_current: "docs-azurerm-resource-messaging-iothub-endpoint-eventhub-x" +description: |- + Manages an IotHub EventHub Endpoint +--- + +# azurerm_iothub_endpoint_eventhub + +Manages an IotHub EventHub Endpoint + +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` and `azurerm_iothub_endpoint_eventhub` resources - but the two ways of definig the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` or `azurerm_iothub_endpoint_eventhub` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. + + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example" + location = "East US" +} + +resource "azurerm_eventhub_namespace" "example" { + name = "exampleEventHubNamespace" + location = "${azurerm_resource_group.example.location}" + resource_group_name = "${azurerm_resource_group.example.name}" + sku = "Basic" +} + +resource "azurerm_eventhub" "example" { + name = "exampleEventHub" + namespace_name = "${azurerm_eventhub_namespace.example.name}" + resource_group_name = "${azurerm_resource_group.example.name}" + partition_count = 2 + message_retention = 1 +} + +resource "azurerm_eventhub_authorization_rule" "example" { + name = "exampleRule" + namespace_name = "${azurerm_eventhub_namespace.example.name}" + eventhub_name = "${azurerm_eventhub.example.name}" + resource_group_name = "${azurerm_resource_group.example.name}" + + listen = false + send = true + manage = false +} + +resource "azurerm_iothub" "example" { + name = "exampleIothub" + resource_group_name = "${azurerm_resource_group.example.name}" + location = "${azurerm_resource_group.example.location}" + + sku { + name = "B1" + tier = "Basic" + capacity = "1" + } + + tags = { + purpose = "example" + } +} + +resource "azurerm_iothub_endpoint_eventhub" "example" { + resource_group_name = "${azurerm_resource_group.example.name}" + iothub_name = "${azurerm_iothub.example.name}" + name = "example" + + connection_string = "${azurerm_eventhub_authorization_rule.example.primary_connection_string}" +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the endpoint. The name must be unique across endpoint types. The following names are reserved: `events`, `operationsMonitoringEvents`, `fileNotifications` and `$default`. + +* `connection_string` - (Required) The connection string for the endpoint. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the IoTHub EventHub Endpoint. + +## Import + +IoTHub EventHub Endpoint can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_iothub_endpoint_eventhub.eventhub1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Devices/IotHubs/hub1/Endpoints/eventhub_endpoint1 +``` diff --git a/website/docs/r/iothub_endpoint_storage_container.html.markdown b/website/docs/r/iothub_endpoint_storage_container.html.markdown index 7a9fbf92d53d..83af7dc99964 100644 --- a/website/docs/r/iothub_endpoint_storage_container.html.markdown +++ b/website/docs/r/iothub_endpoint_storage_container.html.markdown @@ -10,8 +10,7 @@ description: |- Manages an IotHub Storage Container Endpoint -~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` resource in case of Storage Account Endpoints - but the two cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. - +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` and `azurerm_iothub_endpoint_eventhub` resources - but the two ways of definig the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` or `azurerm_iothub_endpoint_eventhub` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. ## Example Usage From 3fcf7366bf13766ae13d120c85f8bb39fed11b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maksymilian=20Bogu=C5=84?= Date: Sat, 15 Jun 2019 00:12:04 +0200 Subject: [PATCH 3/6] azurerm_iothub_endpoint_servicebus_queue and azurerm_iothub_endpoint_servicebus_topic --- azurerm/provider.go | 2 + ...ce_arm_iothub_endpoint_servicebus_queue.go | 238 ++++++++++++++++++ ...m_iothub_endpoint_servicebus_queue_test.go | 227 +++++++++++++++++ ...ce_arm_iothub_endpoint_servicebus_topic.go | 238 ++++++++++++++++++ ...m_iothub_endpoint_servicebus_topic_test.go | 225 +++++++++++++++++ website/docs/r/iothub.html.markdown | 2 +- .../r/iothub_endpoint_eventhub.html.markdown | 3 +- ...ub_endpoint_servicebus_queue.html.markdown | 95 +++++++ ...ub_endpoint_servicebus_topic.html.markdown | 93 +++++++ ...b_endpoint_storage_container.html.markdown | 2 +- 10 files changed, 1121 insertions(+), 4 deletions(-) create mode 100644 azurerm/resource_arm_iothub_endpoint_servicebus_queue.go create mode 100644 azurerm/resource_arm_iothub_endpoint_servicebus_queue_test.go create mode 100644 azurerm/resource_arm_iothub_endpoint_servicebus_topic.go create mode 100644 azurerm/resource_arm_iothub_endpoint_servicebus_topic_test.go create mode 100644 website/docs/r/iothub_endpoint_servicebus_queue.html.markdown create mode 100644 website/docs/r/iothub_endpoint_servicebus_topic.html.markdown diff --git a/azurerm/provider.go b/azurerm/provider.go index c7f571e2bd2c..0520d1573864 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -249,6 +249,8 @@ func Provider() terraform.ResourceProvider { "azurerm_iothub_consumer_group": resourceArmIotHubConsumerGroup(), "azurerm_iothub": resourceArmIotHub(), "azurerm_iothub_endpoint_eventhub": resourceArmIotHubEndpointEventHub(), + "azurerm_iothub_endpoint_servicebus_queue": resourceArmIotHubEndpointServiceBusQueue(), + "azurerm_iothub_endpoint_servicebus_topic": resourceArmIotHubEndpointServiceBusTopic(), "azurerm_iothub_endpoint_storage_container": resourceArmIotHubEndpointStorageContainer(), "azurerm_iothub_shared_access_policy": resourceArmIotHubSharedAccessPolicy(), "azurerm_key_vault_access_policy": resourceArmKeyVaultAccessPolicy(), diff --git a/azurerm/resource_arm_iothub_endpoint_servicebus_queue.go b/azurerm/resource_arm_iothub_endpoint_servicebus_queue.go new file mode 100644 index 000000000000..d2fc45223540 --- /dev/null +++ b/azurerm/resource_arm_iothub_endpoint_servicebus_queue.go @@ -0,0 +1,238 @@ +package azurerm + +import ( + "fmt" + "regexp" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmIotHubEndpointServiceBusQueue() *schema.Resource { + return &schema.Resource{ + Create: resourceArmIotHubEndpointServiceBusQueueCreateUpdate, + Read: resourceArmIotHubEndpointServiceBusQueueRead, + Update: resourceArmIotHubEndpointServiceBusQueueCreateUpdate, + Delete: resourceArmIotHubEndpointServiceBusQueueDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubEndpointName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "iothub_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubName, + }, + + "connection_string": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + sharedAccessKeyRegex := regexp.MustCompile("SharedAccessKey=[^;]+") + sbProtocolRegex := regexp.MustCompile("sb://([^:]+)(:5671)?/;") + + maskedNew := sbProtocolRegex.ReplaceAllString(new, "sb://$1:5671/;") + maskedNew = sharedAccessKeyRegex.ReplaceAllString(maskedNew, "SharedAccessKey=****") + return (new == d.Get(k).(string)) && (maskedNew == old) + }, + Sensitive: true, + }, + }, + } +} + +func resourceArmIotHubEndpointServiceBusQueueCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + subscriptionID := meta.(*ArmClient).subscriptionId + + iothubName := d.Get("iothub_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + azureRMLockByName(iothubName, iothubResourceName) + defer azureRMUnlockByName(iothubName, iothubResourceName) + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + endpointName := d.Get("name").(string) + + resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName) + + connectionStr := d.Get("connection_string").(string) + + queueEndpoint := devices.RoutingServiceBusQueueEndpointProperties{ + ConnectionString: &connectionStr, + Name: &endpointName, + SubscriptionID: &subscriptionID, + ResourceGroup: &resourceGroup, + } + + routing := iothub.Properties.Routing + + if routing == nil { + routing = &devices.RoutingProperties{} + } + + if routing.Endpoints == nil { + routing.Endpoints = &devices.RoutingEndpoints{} + } + + if routing.Endpoints.EventHubs == nil { + queues := make([]devices.RoutingServiceBusQueueEndpointProperties, 0) + routing.Endpoints.ServiceBusQueues = &queues + } + + endpoints := make([]devices.RoutingServiceBusQueueEndpointProperties, 0) + + alreadyExists := false + for _, existingEndpoint := range *routing.Endpoints.ServiceBusQueues { + if strings.EqualFold(*existingEndpoint.Name, endpointName) { + if d.IsNewResource() && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_endpoint_servicebus_queue", resourceId) + } + endpoints = append(endpoints, queueEndpoint) + alreadyExists = true + + } else { + endpoints = append(endpoints, existingEndpoint) + } + } + + if d.IsNewResource() { + endpoints = append(endpoints, queueEndpoint) + } else if !alreadyExists { + return fmt.Errorf("Unable to find ServiceBus Queue Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup) + } + + routing.Endpoints.ServiceBusQueues = &endpoints + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error creating/updating IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for the completion of the creating/updating of IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + d.SetId(resourceId) + + return resourceArmIotHubEndpointServiceBusQueueRead(d, meta) +} + +func resourceArmIotHubEndpointServiceBusQueueRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubEndpointId.ResourceGroup + iothubName := parsedIothubEndpointId.Path["IotHubs"] + endpointName := parsedIothubEndpointId.Path["Endpoints"] + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + d.Set("name", endpointName) + d.Set("iothub_name", iothubName) + d.Set("resource_group_name", resourceGroup) + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + + if endpoints := iothub.Properties.Routing.Endpoints.ServiceBusQueues; endpoints != nil { + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + d.Set("connection_string", endpoint.ConnectionString) + } + } + } + + return nil +} + +func resourceArmIotHubEndpointServiceBusQueueDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubEndpointId.ResourceGroup + iothubName := parsedIothubEndpointId.Path["IotHubs"] + endpointName := parsedIothubEndpointId.Path["Endpoints"] + + azureRMLockByName(iothubName, iothubResourceName) + defer azureRMUnlockByName(iothubName, iothubResourceName) + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + endpoints := iothub.Properties.Routing.Endpoints.ServiceBusQueues + + if endpoints == nil { + return nil + } + + updatedEndpoints := make([]devices.RoutingServiceBusQueueEndpointProperties, 0) + for _, endpoint := range *endpoints { + if !strings.EqualFold(*endpoint.Name, endpointName) { + updatedEndpoints = append(updatedEndpoints, endpoint) + } + } + + iothub.Properties.Routing.Endpoints.ServiceBusQueues = &updatedEndpoints + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error updating IotHub %q (Resource Group %q) with ServiceBus Queue Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for IotHub %q (Resource Group %q) to finish updating ServiceBus Queue Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_iothub_endpoint_servicebus_queue_test.go b/azurerm/resource_arm_iothub_endpoint_servicebus_queue_test.go new file mode 100644 index 000000000000..5a7c78f8b809 --- /dev/null +++ b/azurerm/resource_arm_iothub_endpoint_servicebus_queue_test.go @@ -0,0 +1,227 @@ +package azurerm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMIotHubEndpointServiceBusQueue_basic(t *testing.T) { + resourceName := "azurerm_iothub_endpoint_servicebus_queue.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAzureRMIotHubEndpointStorageContainerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubEndpointServiceBusQueue_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMIotHubEndpointServiceBusQueueExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMIotHubEndpointServiceBusQueue_requiresImport(t *testing.T) { + if !requireResourcesToBeImported { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_iothub_endpoint_servicebus_queue.test" + rInt := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAzureRMIotHubEndpointServiceBusQueueDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubEndpointServiceBusQueue_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMIotHubEndpointServiceBusQueueExists(resourceName), + ), + }, + { + Config: testAccAzureRMIotHubEndpointServiceBusQueue_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_iothub_endpoint_servicebus_queue"), + }, + }, + }) +} + +func testAccAzureRMIotHubEndpointServiceBusQueue_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + + +resource "azurerm_servicebus_namespace" "test" { + name = "acctest-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} + +resource "azurerm_servicebus_queue" "test" { + name = "acctest-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + + enable_partitioning = true +} + +resource "azurerm_servicebus_queue_authorization_rule" "test" { + name = "acctest-%[1]d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + queue_name = "${azurerm_servicebus_queue.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + listen = false + send = true + manage = false +} + +resource "azurerm_iothub" "test" { + name = "acctestIoTHub-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + sku { + name = "B1" + tier = "Basic" + capacity = "1" + } + + tags = { + purpose = "testing" + } +} + +resource "azurerm_iothub_endpoint_servicebus_queue" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + name = "acctest" + + connection_string = "${azurerm_servicebus_queue_authorization_rule.test.primary_connection_string}" +} +`, rInt, location) +} + +func testAccAzureRMIotHubEndpointServiceBusQueue_requiresImport(rInt int, location string) string { + template := testAccAzureRMIotHubEndpointServiceBusQueue_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_iothub_endpoint_servicebus_queue" "import" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + name = "acctest" + + connection_string = "${azurerm_servicebus_queue_authorization_rule.test.primary_connection_string}" +} +`, template) +} + +func testAccAzureRMIotHubEndpointServiceBusQueueExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + parsedIothubId, err := azure.ParseAzureResourceID(rs.Primary.ID) + if err != nil { + return err + } + iothubName := parsedIothubId.Path["IotHubs"] + endpointName := parsedIothubId.Path["Endpoints"] + resourceGroup := parsedIothubId.ResourceGroup + + client := testAccProvider.Meta().(*ArmClient).iothub.ResourceClient + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return fmt.Errorf("Bad: No endpoint %s defined for IotHub %s", endpointName, iothubName) + } + endpoints := iothub.Properties.Routing.Endpoints.ServiceBusQueues + + if endpoints == nil { + return fmt.Errorf("Bad: No ServiceBus Queue endpoint %s defined for IotHub %s", endpointName, iothubName) + } + + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + return nil + } + } + + return fmt.Errorf("Bad: No ServiceBus Queue endpoint %s defined for IotHub %s", endpointName, iothubName) + + } +} + +func testAccAzureRMIotHubEndpointServiceBusQueueDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).iothub.ResourceClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_iothub_endpoint_servicebus_queue" { + continue + } + + endpointName := rs.Primary.Attributes["name"] + iothubName := rs.Primary.Attributes["iothub_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return nil + } + + return fmt.Errorf("Bad: Get on iothubResourceClient: %+v", err) + } + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + endpoints := iothub.Properties.Routing.Endpoints.ServiceBusQueues + + if endpoints == nil { + return nil + } + + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + return fmt.Errorf("Bad: ServiceBus Queue endpoint %s still exists on IoTHb %s", endpointName, iothubName) + } + } + } + return nil +} diff --git a/azurerm/resource_arm_iothub_endpoint_servicebus_topic.go b/azurerm/resource_arm_iothub_endpoint_servicebus_topic.go new file mode 100644 index 000000000000..7ba3b1ec8998 --- /dev/null +++ b/azurerm/resource_arm_iothub_endpoint_servicebus_topic.go @@ -0,0 +1,238 @@ +package azurerm + +import ( + "fmt" + "regexp" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmIotHubEndpointServiceBusTopic() *schema.Resource { + return &schema.Resource{ + Create: resourceArmIotHubEndpointServiceBusTopicCreateUpdate, + Read: resourceArmIotHubEndpointServiceBusTopicRead, + Update: resourceArmIotHubEndpointServiceBusTopicCreateUpdate, + Delete: resourceArmIotHubEndpointServiceBusTopicDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubEndpointName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "iothub_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubName, + }, + + "connection_string": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + sharedAccessKeyRegex := regexp.MustCompile("SharedAccessKey=[^;]+") + sbProtocolRegex := regexp.MustCompile("sb://([^:]+)(:5671)?/;") + + maskedNew := sbProtocolRegex.ReplaceAllString(new, "sb://$1:5671/;") + maskedNew = sharedAccessKeyRegex.ReplaceAllString(maskedNew, "SharedAccessKey=****") + return (new == d.Get(k).(string)) && (maskedNew == old) + }, + Sensitive: true, + }, + }, + } +} + +func resourceArmIotHubEndpointServiceBusTopicCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + subscriptionID := meta.(*ArmClient).subscriptionId + + iothubName := d.Get("iothub_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + azureRMLockByName(iothubName, iothubResourceName) + defer azureRMUnlockByName(iothubName, iothubResourceName) + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + endpointName := d.Get("name").(string) + + resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName) + + connectionStr := d.Get("connection_string").(string) + + topicEndpoint := devices.RoutingServiceBusTopicEndpointProperties{ + ConnectionString: &connectionStr, + Name: &endpointName, + SubscriptionID: &subscriptionID, + ResourceGroup: &resourceGroup, + } + + routing := iothub.Properties.Routing + + if routing == nil { + routing = &devices.RoutingProperties{} + } + + if routing.Endpoints == nil { + routing.Endpoints = &devices.RoutingEndpoints{} + } + + if routing.Endpoints.EventHubs == nil { + topics := make([]devices.RoutingServiceBusTopicEndpointProperties, 0) + routing.Endpoints.ServiceBusTopics = &topics + } + + endpoints := make([]devices.RoutingServiceBusTopicEndpointProperties, 0) + + alreadyExists := false + for _, existingEndpoint := range *routing.Endpoints.ServiceBusTopics { + if strings.EqualFold(*existingEndpoint.Name, endpointName) { + if d.IsNewResource() && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_endpoint_servicebus_topic", resourceId) + } + endpoints = append(endpoints, topicEndpoint) + alreadyExists = true + + } else { + endpoints = append(endpoints, existingEndpoint) + } + } + + if d.IsNewResource() { + endpoints = append(endpoints, topicEndpoint) + } else if !alreadyExists { + return fmt.Errorf("Unable to find ServiceBus Queue Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup) + } + + routing.Endpoints.ServiceBusTopics = &endpoints + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error creating/updating IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for the completion of the creating/updating of IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + d.SetId(resourceId) + + return resourceArmIotHubEndpointServiceBusTopicRead(d, meta) +} + +func resourceArmIotHubEndpointServiceBusTopicRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubEndpointId.ResourceGroup + iothubName := parsedIothubEndpointId.Path["IotHubs"] + endpointName := parsedIothubEndpointId.Path["Endpoints"] + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + d.Set("name", endpointName) + d.Set("iothub_name", iothubName) + d.Set("resource_group_name", resourceGroup) + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + + if endpoints := iothub.Properties.Routing.Endpoints.ServiceBusTopics; endpoints != nil { + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + d.Set("connection_string", endpoint.ConnectionString) + } + } + } + + return nil +} + +func resourceArmIotHubEndpointServiceBusTopicDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubEndpointId.ResourceGroup + iothubName := parsedIothubEndpointId.Path["IotHubs"] + endpointName := parsedIothubEndpointId.Path["Endpoints"] + + azureRMLockByName(iothubName, iothubResourceName) + defer azureRMUnlockByName(iothubName, iothubResourceName) + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + endpoints := iothub.Properties.Routing.Endpoints.ServiceBusTopics + + if endpoints == nil { + return nil + } + + updatedEndpoints := make([]devices.RoutingServiceBusTopicEndpointProperties, 0) + for _, endpoint := range *endpoints { + if !strings.EqualFold(*endpoint.Name, endpointName) { + updatedEndpoints = append(updatedEndpoints, endpoint) + } + } + + iothub.Properties.Routing.Endpoints.ServiceBusTopics = &updatedEndpoints + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error updating IotHub %q (Resource Group %q) with ServiceBus Queue Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for IotHub %q (Resource Group %q) to finish updating ServiceBus Queue Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_iothub_endpoint_servicebus_topic_test.go b/azurerm/resource_arm_iothub_endpoint_servicebus_topic_test.go new file mode 100644 index 000000000000..2be9dc025dbe --- /dev/null +++ b/azurerm/resource_arm_iothub_endpoint_servicebus_topic_test.go @@ -0,0 +1,225 @@ +package azurerm + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMIotHubEndpointServiceBusTopic_basic(t *testing.T) { + resourceName := "azurerm_iothub_endpoint_servicebus_topic.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAzureRMIotHubEndpointStorageContainerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubEndpointServiceBusTopic_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMIotHubEndpointServiceBusTopicExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMIotHubEndpointServiceBusTopic_requiresImport(t *testing.T) { + if !requireResourcesToBeImported { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_iothub_endpoint_servicebus_topic.test" + rInt := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAzureRMIotHubEndpointServiceBusTopicDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubEndpointServiceBusTopic_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testAccAzureRMIotHubEndpointServiceBusTopicExists(resourceName), + ), + }, + { + Config: testAccAzureRMIotHubEndpointServiceBusTopic_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_iothub_endpoint_servicebus_topic"), + }, + }, + }) +} + +func testAccAzureRMIotHubEndpointServiceBusTopic_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + + +resource "azurerm_servicebus_namespace" "test" { + name = "acctest-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} + +resource "azurerm_servicebus_topic" "test" { + name = "acctestservicebustopic-%[1]d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_servicebus_topic_authorization_rule" "test" { + name = "acctest-%[1]d" + namespace_name = "${azurerm_servicebus_namespace.test.name}" + topic_name = "${azurerm_servicebus_topic.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + + listen = false + send = true + manage = false +} + +resource "azurerm_iothub" "test" { + name = "acctestIoTHub-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + sku { + name = "B1" + tier = "Basic" + capacity = "1" + } + + tags = { + purpose = "testing" + } +} + +resource "azurerm_iothub_endpoint_servicebus_topic" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + name = "acctest" + + connection_string = "${azurerm_servicebus_topic_authorization_rule.test.primary_connection_string}" +} +`, rInt, location) +} + +func testAccAzureRMIotHubEndpointServiceBusTopic_requiresImport(rInt int, location string) string { + template := testAccAzureRMIotHubEndpointServiceBusTopic_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_iothub_endpoint_servicebus_topic" "import" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + name = "acctest" + + connection_string = "${azurerm_servicebus_topic_authorization_rule.test.primary_connection_string}" +} +`, template) +} + +func testAccAzureRMIotHubEndpointServiceBusTopicExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + parsedIothubId, err := azure.ParseAzureResourceID(rs.Primary.ID) + if err != nil { + return err + } + iothubName := parsedIothubId.Path["IotHubs"] + endpointName := parsedIothubId.Path["Endpoints"] + resourceGroup := parsedIothubId.ResourceGroup + + client := testAccProvider.Meta().(*ArmClient).iothub.ResourceClient + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) + } + + return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) + } + + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return fmt.Errorf("Bad: No endpoint %s defined for IotHub %s", endpointName, iothubName) + } + endpoints := iothub.Properties.Routing.Endpoints.ServiceBusTopics + + if endpoints == nil { + return fmt.Errorf("Bad: No ServiceBus Topic endpoint %s defined for IotHub %s", endpointName, iothubName) + } + + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + return nil + } + } + + return fmt.Errorf("Bad: No ServiceBus Topic endpoint %s defined for IotHub %s", endpointName, iothubName) + + } +} + +func testAccAzureRMIotHubEndpointServiceBusTopicDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).iothub.ResourceClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_iothub_endpoint_servicebus_topic" { + continue + } + + endpointName := rs.Primary.Attributes["name"] + iothubName := rs.Primary.Attributes["iothub_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + iothub, err := client.Get(ctx, resourceGroup, iothubName) + if err != nil { + if utils.ResponseWasNotFound(iothub.Response) { + return nil + } + + return fmt.Errorf("Bad: Get on iothubResourceClient: %+v", err) + } + if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { + return nil + } + endpoints := iothub.Properties.Routing.Endpoints.ServiceBusTopics + + if endpoints == nil { + return nil + } + + for _, endpoint := range *endpoints { + if strings.EqualFold(*endpoint.Name, endpointName) { + return fmt.Errorf("Bad: ServiceBus Topic endpoint %s still exists on IoTHb %s", endpointName, iothubName) + } + } + } + return nil +} diff --git a/website/docs/r/iothub.html.markdown b/website/docs/r/iothub.html.markdown index 8348bd3df707..e5f5ac40114d 100644 --- a/website/docs/r/iothub.html.markdown +++ b/website/docs/r/iothub.html.markdown @@ -10,7 +10,7 @@ description: |- Manages an IotHub -~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` and `azurerm_iothub_endpoint_eventhub` resources - but the two ways of definig the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` or `azurerm_iothub_endpoint_eventhub` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_*` resources - but the two ways of defining the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_*` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. ## Example Usage diff --git a/website/docs/r/iothub_endpoint_eventhub.html.markdown b/website/docs/r/iothub_endpoint_eventhub.html.markdown index d9748ef54564..7a4453932ca9 100644 --- a/website/docs/r/iothub_endpoint_eventhub.html.markdown +++ b/website/docs/r/iothub_endpoint_eventhub.html.markdown @@ -10,8 +10,7 @@ description: |- Manages an IotHub EventHub Endpoint -~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` and `azurerm_iothub_endpoint_eventhub` resources - but the two ways of definig the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` or `azurerm_iothub_endpoint_eventhub` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. - +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_*` resources - but the two ways of defining the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_*` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. ## Example Usage diff --git a/website/docs/r/iothub_endpoint_servicebus_queue.html.markdown b/website/docs/r/iothub_endpoint_servicebus_queue.html.markdown new file mode 100644 index 000000000000..b337e3411366 --- /dev/null +++ b/website/docs/r/iothub_endpoint_servicebus_queue.html.markdown @@ -0,0 +1,95 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_iothub_endpoint_servicebus_queue" +sidebar_current: "docs-azurerm-resource-messaging-iothub-servicebus-queue-x" +description: |- + Manages an IotHub ServiceBus Queue Endpoint +--- + +# azurerm_iothub_endpoint_eventhub + +Manages an IotHub ServiceBus Queue Endpoint + +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_*` resources - but the two ways of defining the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_*` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example" + location = "East US" +} + +resource "azurerm_servicebus_namespace" "example" { + name = "exampleNamespace" + location = "${azurerm_resource_group.example.location}" + resource_group_name = "${azurerm_resource_group.example.name}" + sku = "Standard" +} + +resource "azurerm_servicebus_queue" "example" { + name = "exampleQueue" + resource_group_name = "${azurerm_resource_group.example.name}" + namespace_name = "${azurerm_servicebus_namespace.example.name}" + + enable_partitioning = true +} + +resource "azurerm_servicebus_queue_authorization_rule" "example" { + name = "exampleRule" + namespace_name = "${azurerm_servicebus_namespace.example.name}" + queue_name = "${azurerm_servicebus_queue.example.name}" + resource_group_name = "${azurerm_resource_group.example.name}" + + listen = false + send = true + manage = false +} + +resource "azurerm_iothub" "example" { + name = "exampleIothub" + resource_group_name = "${azurerm_resource_group.example.name}" + location = "${azurerm_resource_group.example.location}" + + sku { + name = "B1" + tier = "Basic" + capacity = "1" + } + + tags = { + purpose = "example" + } +} + +resource "azurerm_iothub_endpoint_servicebus_queue" "example" { + resource_group_name = "${azurerm_resource_group.example.name}" + iothub_name = "${azurerm_iothub.example.name}" + name = "example" + + connection_string = "${azurerm_servicebus_queue_authorization_rule.example.primary_connection_string}" +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the endpoint. The name must be unique across endpoint types. The following names are reserved: `events`, `operationsMonitoringEvents`, `fileNotifications` and `$default`. + +* `connection_string` - (Required) The connection string for the endpoint. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the IoTHub ServiceBus Queue Endpoint. + +## Import + +IoTHub ServiceBus Queue Endpoint can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_iothub_endpoint_servicebus_queue.servicebus_queue1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Devices/IotHubs/hub1/Endpoints/servicebusqueue_endpoint1 +``` diff --git a/website/docs/r/iothub_endpoint_servicebus_topic.html.markdown b/website/docs/r/iothub_endpoint_servicebus_topic.html.markdown new file mode 100644 index 000000000000..15569b0ab013 --- /dev/null +++ b/website/docs/r/iothub_endpoint_servicebus_topic.html.markdown @@ -0,0 +1,93 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_iothub_endpoint_servicebus_topic" +sidebar_current: "docs-azurerm-resource-messaging-iothub-servicebus-topic-x" +description: |- + Manages an IotHub ServiceBus Topic Endpoint +--- + +# azurerm_iothub_endpoint_eventhub + +Manages an IotHub ServiceBus Topic Endpoint + +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_*` resources - but the two ways of defining the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_*` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example" + location = "East US" +} + +resource "azurerm_servicebus_namespace" "example" { + name = "exampleNamespace" + location = "${azurerm_resource_group.example.location}" + resource_group_name = "${azurerm_resource_group.example.name}" + sku = "Standard" +} + +resource "azurerm_servicebus_topic" "example" { + name = "exampleTopic" + resource_group_name = "${azurerm_resource_group.example.name}" + namespace_name = "${azurerm_servicebus_namespace.example.name}" +} + +resource "azurerm_servicebus_topic_authorization_rule" "example" { + name = "exampleRule" + namespace_name = "${azurerm_servicebus_namespace.example.name}" + topic_name = "${azurerm_servicebus_topic.example.name}" + resource_group_name = "${azurerm_resource_group.example.name}" + + listen = false + send = true + manage = false +} + +resource "azurerm_iothub" "example" { + name = "exampleIothub" + resource_group_name = "${azurerm_resource_group.example.name}" + location = "${azurerm_resource_group.example.location}" + + sku { + name = "B1" + tier = "Basic" + capacity = "1" + } + + tags = { + purpose = "example" + } +} + +resource "azurerm_iothub_endpoint_servicebus_topic" "example" { + resource_group_name = "${azurerm_resource_group.example.name}" + iothub_name = "${azurerm_iothub.example.name}" + name = "example" + + connection_string = "${azurerm_servicebus_topic_authorization_rule.example.primary_connection_string}" +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the endpoint. The name must be unique across endpoint types. The following names are reserved: `events`, `operationsMonitoringEvents`, `fileNotifications` and `$default`. + +* `connection_string` - (Required) The connection string for the endpoint. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the IoTHub ServiceBus Topic Endpoint. + +## Import + +IoTHub ServiceBus Topic Endpoint can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_iothub_endpoint_servicebus_topic.servicebus_topic1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Devices/IotHubs/hub1/Endpoints/servicebustopic_endpoint1 +``` diff --git a/website/docs/r/iothub_endpoint_storage_container.html.markdown b/website/docs/r/iothub_endpoint_storage_container.html.markdown index 83af7dc99964..80405aaa8a8e 100644 --- a/website/docs/r/iothub_endpoint_storage_container.html.markdown +++ b/website/docs/r/iothub_endpoint_storage_container.html.markdown @@ -10,7 +10,7 @@ description: |- Manages an IotHub Storage Container Endpoint -~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_storage_container` and `azurerm_iothub_endpoint_eventhub` resources - but the two ways of definig the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_storage_container` or `azurerm_iothub_endpoint_eventhub` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. +~> **NOTE:** Endpoints can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_endpoint_*` resources - but the two ways of defining the endpoints cannot be used together. If both are used against the same IoTHub, spurious changes will occur. Also, defining a `azurerm_iothub_endpoint_*` resource and another endpoint of a different type directly on the `azurerm_iothub` resource is not supported. ## Example Usage From 3414beb7b37d3d0ea215cd16806a0efa00a12196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maksymilian=20Bogu=C5=84?= Date: Mon, 17 Jun 2019 09:03:15 +0200 Subject: [PATCH 4/6] deprecate the azurerm_iothub.endpoint property --- azurerm/resource_arm_iothub.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index d67aa632a427..c6d797ce2a51 100755 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -222,9 +222,10 @@ func resourceArmIotHub() *schema.Resource { }, "endpoint": { - Type: schema.TypeList, - Optional: true, - Computed: true, + Type: schema.TypeList, + Optional: true, + Computed: true, + Deprecated: "Use one of the `azurerm_iothub_endpoint_storage_container`, `azurerm_iothub_endpoint_eventhub`, `azurerm_iothub_endpoint_servicebus_queue`, `azurerm_iothub_endpoint_servicebus_topic` resources instead.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": { From c2aeb9a1e5added1ac056b4822763835c1f47b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maksymilian=20Bogu=C5=84?= Date: Mon, 19 Aug 2019 14:58:56 +0200 Subject: [PATCH 5/6] gofmt --- azurerm/provider.go | 8 ++++---- azurerm/resource_arm_iothub.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/azurerm/provider.go b/azurerm/provider.go index 0520d1573864..f63176c31996 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -248,10 +248,10 @@ func Provider() terraform.ResourceProvider { "azurerm_iot_dps_certificate": resourceArmIotDPSCertificate(), "azurerm_iothub_consumer_group": resourceArmIotHubConsumerGroup(), "azurerm_iothub": resourceArmIotHub(), - "azurerm_iothub_endpoint_eventhub": resourceArmIotHubEndpointEventHub(), - "azurerm_iothub_endpoint_servicebus_queue": resourceArmIotHubEndpointServiceBusQueue(), - "azurerm_iothub_endpoint_servicebus_topic": resourceArmIotHubEndpointServiceBusTopic(), - "azurerm_iothub_endpoint_storage_container": resourceArmIotHubEndpointStorageContainer(), + "azurerm_iothub_endpoint_eventhub": resourceArmIotHubEndpointEventHub(), + "azurerm_iothub_endpoint_servicebus_queue": resourceArmIotHubEndpointServiceBusQueue(), + "azurerm_iothub_endpoint_servicebus_topic": resourceArmIotHubEndpointServiceBusTopic(), + "azurerm_iothub_endpoint_storage_container": resourceArmIotHubEndpointStorageContainer(), "azurerm_iothub_shared_access_policy": resourceArmIotHubSharedAccessPolicy(), "azurerm_key_vault_access_policy": resourceArmKeyVaultAccessPolicy(), "azurerm_key_vault_certificate": resourceArmKeyVaultCertificate(), diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index c6d797ce2a51..9f01606213c1 100755 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -491,8 +491,8 @@ func resourceArmIotHubCreateUpdate(d *schema.ResourceData, meta interface{}) err Location: utils.String(location), Sku: skuInfo, Properties: &devices.IotHubProperties{ - IPFilterRules: ipFilterRules, - Routing: &routingProperties, + IPFilterRules: ipFilterRules, + Routing: &routingProperties, StorageEndpoints: storageEndpoints, MessagingEndpoints: messagingEndpoints, EnableFileUploadNotifications: &enableFileUploadNotifications, From eae958873d07083a0e8872c658062f73cb4af4d7 Mon Sep 17 00:00:00 2001 From: Matthew Frahry Date: Wed, 6 Nov 2019 16:09:01 -0800 Subject: [PATCH 6/6] Updating iothub endpoints with latest provider features --- azurerm/resource_arm_iothub.go | 8 +-- .../resource_arm_iothub_endpoint_eventhub.go | 62 ++++++++++-------- ...ource_arm_iothub_endpoint_eventhub_test.go | 11 ++-- ...ce_arm_iothub_endpoint_servicebus_queue.go | 59 ++++++++++------- ...m_iothub_endpoint_servicebus_queue_test.go | 11 ++-- ...ce_arm_iothub_endpoint_servicebus_topic.go | 58 ++++++++++------- ...m_iothub_endpoint_servicebus_topic_test.go | 18 +++--- ...e_arm_iothub_endpoint_storage_container.go | 64 +++++++++++-------- ..._iothub_endpoint_storage_container_test.go | 24 +++---- website/azurerm.erb | 16 +++++ .../r/iothub_endpoint_eventhub.html.markdown | 3 +- ...ub_endpoint_servicebus_queue.html.markdown | 3 +- ...ub_endpoint_servicebus_topic.html.markdown | 3 +- ...b_endpoint_storage_container.html.markdown | 4 +- 14 files changed, 198 insertions(+), 146 deletions(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 79f20d5eab1f..4fdecff4c5ec 100644 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -230,10 +230,9 @@ func resourceArmIotHub() *schema.Resource { }, "endpoint": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Deprecated: "Use one of the `azurerm_iothub_endpoint_storage_container`, `azurerm_iothub_endpoint_eventhub`, `azurerm_iothub_endpoint_servicebus_queue`, `azurerm_iothub_endpoint_servicebus_topic` resources instead.", + Type: schema.TypeList, + Optional: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": { @@ -473,7 +472,6 @@ func resourceArmIotHubCreateUpdate(d *schema.ResourceData, meta interface{}) err fallbackRoute := expandIoTHubFallbackRoute(d) routes := expandIoTHubRoutes(d) - routingProperties := devices.RoutingProperties{ Routes: routes, FallbackRoute: fallbackRoute, diff --git a/azurerm/resource_arm_iothub_endpoint_eventhub.go b/azurerm/resource_arm_iothub_endpoint_eventhub.go index 81ffa2a8b9b5..f53f97fcf102 100644 --- a/azurerm/resource_arm_iothub_endpoint_eventhub.go +++ b/azurerm/resource_arm_iothub_endpoint_eventhub.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -11,6 +12,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -24,6 +26,13 @@ func resourceArmIotHubEndpointEventHub() *schema.Resource { State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -60,8 +69,8 @@ func resourceArmIotHubEndpointEventHub() *schema.Resource { func resourceArmIotHubEndpointEventHubCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext - subscriptionID := meta.(*ArmClient).subscriptionId + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() iothubName := d.Get("iothub_name").(string) resourceGroup := d.Get("resource_group_name").(string) @@ -79,20 +88,16 @@ func resourceArmIotHubEndpointEventHubCreateUpdate(d *schema.ResourceData, meta } endpointName := d.Get("name").(string) - resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName) - connectionStr := d.Get("connection_string").(string) - eventhubEndpoint := devices.RoutingEventHubProperties{ - ConnectionString: &connectionStr, - Name: &endpointName, - SubscriptionID: &subscriptionID, - ResourceGroup: &resourceGroup, + ConnectionString: utils.String(d.Get("connection_string").(string)), + Name: utils.String(endpointName), + SubscriptionID: utils.String(meta.(*ArmClient).subscriptionId), + ResourceGroup: utils.String(resourceGroup), } routing := iothub.Properties.Routing - if routing == nil { routing = &devices.RoutingProperties{} } @@ -110,13 +115,14 @@ func resourceArmIotHubEndpointEventHubCreateUpdate(d *schema.ResourceData, meta alreadyExists := false for _, existingEndpoint := range *routing.Endpoints.EventHubs { - if strings.EqualFold(*existingEndpoint.Name, endpointName) { - if d.IsNewResource() && requireResourcesToBeImported { - return tf.ImportAsExistsError("azurerm_iothub_endpoint_eventhub", resourceId) + if existingEndpointName := existingEndpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + if d.IsNewResource() && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_endpoint_eventhub", resourceId) + } + endpoints = append(endpoints, eventhubEndpoint) + alreadyExists = true } - endpoints = append(endpoints, eventhubEndpoint) - alreadyExists = true - } else { endpoints = append(endpoints, existingEndpoint) } @@ -127,7 +133,6 @@ func resourceArmIotHubEndpointEventHubCreateUpdate(d *schema.ResourceData, meta } else if !alreadyExists { return fmt.Errorf("Unable to find EventHub Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup) } - routing.Endpoints.EventHubs = &endpoints future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") @@ -140,16 +145,15 @@ func resourceArmIotHubEndpointEventHubCreateUpdate(d *schema.ResourceData, meta } d.SetId(resourceId) - return resourceArmIotHubEndpointEventHubRead(d, meta) } func resourceArmIotHubEndpointEventHubRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) - if err != nil { return err } @@ -173,8 +177,10 @@ func resourceArmIotHubEndpointEventHubRead(d *schema.ResourceData, meta interfac if endpoints := iothub.Properties.Routing.Endpoints.EventHubs; endpoints != nil { for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - d.Set("connection_string", endpoint.ConnectionString) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + d.Set("connection_string", endpoint.ConnectionString) + } } } } @@ -184,10 +190,10 @@ func resourceArmIotHubEndpointEventHubRead(d *schema.ResourceData, meta interfac func resourceArmIotHubEndpointEventHubDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) - if err != nil { return err } @@ -204,7 +210,6 @@ func resourceArmIotHubEndpointEventHubDelete(d *schema.ResourceData, meta interf if utils.ResponseWasNotFound(iothub.Response) { return fmt.Errorf("IotHub %q (Resource Group %q) was not found", iothubName, resourceGroup) } - return fmt.Errorf("Error loading IotHub %q (Resource Group %q): %+v", iothubName, resourceGroup, err) } @@ -219,11 +224,12 @@ func resourceArmIotHubEndpointEventHubDelete(d *schema.ResourceData, meta interf updatedEndpoints := make([]devices.RoutingEventHubProperties, 0) for _, endpoint := range *endpoints { - if !strings.EqualFold(*endpoint.Name, endpointName) { - updatedEndpoints = append(updatedEndpoints, endpoint) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if !strings.EqualFold(*existingEndpointName, endpointName) { + updatedEndpoints = append(updatedEndpoints, endpoint) + } } } - iothub.Properties.Routing.Endpoints.EventHubs = &updatedEndpoints future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") diff --git a/azurerm/resource_arm_iothub_endpoint_eventhub_test.go b/azurerm/resource_arm_iothub_endpoint_eventhub_test.go index 44fca28168a2..90833a34aae1 100644 --- a/azurerm/resource_arm_iothub_endpoint_eventhub_test.go +++ b/azurerm/resource_arm_iothub_endpoint_eventhub_test.go @@ -41,7 +41,6 @@ func TestAccAzureRMIotHubEndpointEventHub_requiresImport(t *testing.T) { t.Skip("Skipping since resources aren't required to be imported") return } - resourceName := "azurerm_iothub_endpoint_eventhub.test" rInt := tf.AccRandTimeInt() location := testLocation() @@ -68,7 +67,7 @@ func TestAccAzureRMIotHubEndpointEventHub_requiresImport(t *testing.T) { func testAccAzureRMIotHubEndpointEventHub_basic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" + name = "acctestRG-iothub-%[1]d" location = "%[2]s" } @@ -182,7 +181,6 @@ func testAccAzureRMIotHubEndpointEventHubExists(resourceName string) resource.Te } return fmt.Errorf("Bad: No EventHub endpoint %s defined for IotHub %s", endpointName, iothubName) - } } @@ -194,7 +192,6 @@ func testAccAzureRMIotHubEndpointEventHubDestroy(s *terraform.State) error { if rs.Type != "azurerm_iothub_endpoint_eventhub" { continue } - endpointName := rs.Primary.Attributes["name"] iothubName := rs.Primary.Attributes["iothub_name"] resourceGroup := rs.Primary.Attributes["resource_group_name"] @@ -217,8 +214,10 @@ func testAccAzureRMIotHubEndpointEventHubDestroy(s *terraform.State) error { } for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - return fmt.Errorf("Bad: EventHub endpoint %s still exists on IoTHb %s", endpointName, iothubName) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + return fmt.Errorf("Bad: EventHub endpoint %s still exists on IoTHb %s", endpointName, iothubName) + } } } } diff --git a/azurerm/resource_arm_iothub_endpoint_servicebus_queue.go b/azurerm/resource_arm_iothub_endpoint_servicebus_queue.go index edc8d5d5a9fe..300edd75f4cc 100644 --- a/azurerm/resource_arm_iothub_endpoint_servicebus_queue.go +++ b/azurerm/resource_arm_iothub_endpoint_servicebus_queue.go @@ -2,15 +2,17 @@ package azurerm import ( "fmt" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -24,6 +26,13 @@ func resourceArmIotHubEndpointServiceBusQueue() *schema.Resource { State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -60,7 +69,8 @@ func resourceArmIotHubEndpointServiceBusQueue() *schema.Resource { func resourceArmIotHubEndpointServiceBusQueueCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() subscriptionID := meta.(*ArmClient).subscriptionId iothubName := d.Get("iothub_name").(string) @@ -79,20 +89,16 @@ func resourceArmIotHubEndpointServiceBusQueueCreateUpdate(d *schema.ResourceData } endpointName := d.Get("name").(string) - resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName) - connectionStr := d.Get("connection_string").(string) - queueEndpoint := devices.RoutingServiceBusQueueEndpointProperties{ - ConnectionString: &connectionStr, - Name: &endpointName, - SubscriptionID: &subscriptionID, - ResourceGroup: &resourceGroup, + ConnectionString: utils.String(d.Get("connection_string").(string)), + Name: utils.String(endpointName), + SubscriptionID: utils.String(subscriptionID), + ResourceGroup: utils.String(resourceGroup), } routing := iothub.Properties.Routing - if routing == nil { routing = &devices.RoutingProperties{} } @@ -105,18 +111,18 @@ func resourceArmIotHubEndpointServiceBusQueueCreateUpdate(d *schema.ResourceData queues := make([]devices.RoutingServiceBusQueueEndpointProperties, 0) routing.Endpoints.ServiceBusQueues = &queues } - endpoints := make([]devices.RoutingServiceBusQueueEndpointProperties, 0) alreadyExists := false for _, existingEndpoint := range *routing.Endpoints.ServiceBusQueues { - if strings.EqualFold(*existingEndpoint.Name, endpointName) { - if d.IsNewResource() && requireResourcesToBeImported { - return tf.ImportAsExistsError("azurerm_iothub_endpoint_servicebus_queue", resourceId) + if existingEndpointName := existingEndpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + if d.IsNewResource() && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_endpoint_servicebus_queue", resourceId) + } + endpoints = append(endpoints, queueEndpoint) + alreadyExists = true } - endpoints = append(endpoints, queueEndpoint) - alreadyExists = true - } else { endpoints = append(endpoints, existingEndpoint) } @@ -127,7 +133,6 @@ func resourceArmIotHubEndpointServiceBusQueueCreateUpdate(d *schema.ResourceData } else if !alreadyExists { return fmt.Errorf("Unable to find ServiceBus Queue Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup) } - routing.Endpoints.ServiceBusQueues = &endpoints future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") @@ -146,7 +151,8 @@ func resourceArmIotHubEndpointServiceBusQueueCreateUpdate(d *schema.ResourceData func resourceArmIotHubEndpointServiceBusQueueRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) @@ -173,8 +179,10 @@ func resourceArmIotHubEndpointServiceBusQueueRead(d *schema.ResourceData, meta i if endpoints := iothub.Properties.Routing.Endpoints.ServiceBusQueues; endpoints != nil { for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - d.Set("connection_string", endpoint.ConnectionString) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + d.Set("connection_string", endpoint.ConnectionString) + } } } } @@ -184,7 +192,8 @@ func resourceArmIotHubEndpointServiceBusQueueRead(d *schema.ResourceData, meta i func resourceArmIotHubEndpointServiceBusQueueDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) @@ -219,8 +228,10 @@ func resourceArmIotHubEndpointServiceBusQueueDelete(d *schema.ResourceData, meta updatedEndpoints := make([]devices.RoutingServiceBusQueueEndpointProperties, 0) for _, endpoint := range *endpoints { - if !strings.EqualFold(*endpoint.Name, endpointName) { - updatedEndpoints = append(updatedEndpoints, endpoint) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if !strings.EqualFold(*existingEndpointName, endpointName) { + updatedEndpoints = append(updatedEndpoints, endpoint) + } } } diff --git a/azurerm/resource_arm_iothub_endpoint_servicebus_queue_test.go b/azurerm/resource_arm_iothub_endpoint_servicebus_queue_test.go index e196db2666db..0040c326fd1e 100644 --- a/azurerm/resource_arm_iothub_endpoint_servicebus_queue_test.go +++ b/azurerm/resource_arm_iothub_endpoint_servicebus_queue_test.go @@ -41,7 +41,6 @@ func TestAccAzureRMIotHubEndpointServiceBusQueue_requiresImport(t *testing.T) { t.Skip("Skipping since resources aren't required to be imported") return } - resourceName := "azurerm_iothub_endpoint_servicebus_queue.test" rInt := tf.AccRandTimeInt() location := testLocation() @@ -68,7 +67,7 @@ func TestAccAzureRMIotHubEndpointServiceBusQueue_requiresImport(t *testing.T) { func testAccAzureRMIotHubEndpointServiceBusQueue_basic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" + name = "acctestRG-iothub-%[1]d" location = "%[2]s" } @@ -183,7 +182,6 @@ func testAccAzureRMIotHubEndpointServiceBusQueueExists(resourceName string) reso } return fmt.Errorf("Bad: No ServiceBus Queue endpoint %s defined for IotHub %s", endpointName, iothubName) - } } @@ -195,7 +193,6 @@ func testAccAzureRMIotHubEndpointServiceBusQueueDestroy(s *terraform.State) erro if rs.Type != "azurerm_iothub_endpoint_servicebus_queue" { continue } - endpointName := rs.Primary.Attributes["name"] iothubName := rs.Primary.Attributes["iothub_name"] resourceGroup := rs.Primary.Attributes["resource_group_name"] @@ -218,8 +215,10 @@ func testAccAzureRMIotHubEndpointServiceBusQueueDestroy(s *terraform.State) erro } for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - return fmt.Errorf("Bad: ServiceBus Queue endpoint %s still exists on IoTHb %s", endpointName, iothubName) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + return fmt.Errorf("Bad: ServiceBus Queue endpoint %s still exists on IoTHb %s", endpointName, iothubName) + } } } } diff --git a/azurerm/resource_arm_iothub_endpoint_servicebus_topic.go b/azurerm/resource_arm_iothub_endpoint_servicebus_topic.go index 9a5b9e01f071..232b2af3c0d0 100644 --- a/azurerm/resource_arm_iothub_endpoint_servicebus_topic.go +++ b/azurerm/resource_arm_iothub_endpoint_servicebus_topic.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -11,6 +12,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -24,6 +26,13 @@ func resourceArmIotHubEndpointServiceBusTopic() *schema.Resource { State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -60,7 +69,8 @@ func resourceArmIotHubEndpointServiceBusTopic() *schema.Resource { func resourceArmIotHubEndpointServiceBusTopicCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() subscriptionID := meta.(*ArmClient).subscriptionId iothubName := d.Get("iothub_name").(string) @@ -79,20 +89,16 @@ func resourceArmIotHubEndpointServiceBusTopicCreateUpdate(d *schema.ResourceData } endpointName := d.Get("name").(string) - resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName) - connectionStr := d.Get("connection_string").(string) - topicEndpoint := devices.RoutingServiceBusTopicEndpointProperties{ - ConnectionString: &connectionStr, - Name: &endpointName, - SubscriptionID: &subscriptionID, - ResourceGroup: &resourceGroup, + ConnectionString: utils.String(d.Get("connection_string").(string)), + Name: utils.String(endpointName), + SubscriptionID: utils.String(subscriptionID), + ResourceGroup: utils.String(resourceGroup), } routing := iothub.Properties.Routing - if routing == nil { routing = &devices.RoutingProperties{} } @@ -105,18 +111,18 @@ func resourceArmIotHubEndpointServiceBusTopicCreateUpdate(d *schema.ResourceData topics := make([]devices.RoutingServiceBusTopicEndpointProperties, 0) routing.Endpoints.ServiceBusTopics = &topics } - endpoints := make([]devices.RoutingServiceBusTopicEndpointProperties, 0) alreadyExists := false for _, existingEndpoint := range *routing.Endpoints.ServiceBusTopics { - if strings.EqualFold(*existingEndpoint.Name, endpointName) { - if d.IsNewResource() && requireResourcesToBeImported { - return tf.ImportAsExistsError("azurerm_iothub_endpoint_servicebus_topic", resourceId) + if existingEndpointName := existingEndpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + if d.IsNewResource() && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_endpoint_servicebus_topic", resourceId) + } + endpoints = append(endpoints, topicEndpoint) + alreadyExists = true } - endpoints = append(endpoints, topicEndpoint) - alreadyExists = true - } else { endpoints = append(endpoints, existingEndpoint) } @@ -127,7 +133,6 @@ func resourceArmIotHubEndpointServiceBusTopicCreateUpdate(d *schema.ResourceData } else if !alreadyExists { return fmt.Errorf("Unable to find ServiceBus Queue Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup) } - routing.Endpoints.ServiceBusTopics = &endpoints future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") @@ -146,7 +151,8 @@ func resourceArmIotHubEndpointServiceBusTopicCreateUpdate(d *schema.ResourceData func resourceArmIotHubEndpointServiceBusTopicRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) @@ -173,8 +179,10 @@ func resourceArmIotHubEndpointServiceBusTopicRead(d *schema.ResourceData, meta i if endpoints := iothub.Properties.Routing.Endpoints.ServiceBusTopics; endpoints != nil { for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - d.Set("connection_string", endpoint.ConnectionString) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + d.Set("connection_string", endpoint.ConnectionString) + } } } } @@ -184,7 +192,8 @@ func resourceArmIotHubEndpointServiceBusTopicRead(d *schema.ResourceData, meta i func resourceArmIotHubEndpointServiceBusTopicDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) @@ -219,11 +228,12 @@ func resourceArmIotHubEndpointServiceBusTopicDelete(d *schema.ResourceData, meta updatedEndpoints := make([]devices.RoutingServiceBusTopicEndpointProperties, 0) for _, endpoint := range *endpoints { - if !strings.EqualFold(*endpoint.Name, endpointName) { - updatedEndpoints = append(updatedEndpoints, endpoint) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if !strings.EqualFold(*existingEndpointName, endpointName) { + updatedEndpoints = append(updatedEndpoints, endpoint) + } } } - iothub.Properties.Routing.Endpoints.ServiceBusTopics = &updatedEndpoints future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") diff --git a/azurerm/resource_arm_iothub_endpoint_servicebus_topic_test.go b/azurerm/resource_arm_iothub_endpoint_servicebus_topic_test.go index f629fe2428f7..4c6495f4bcb0 100644 --- a/azurerm/resource_arm_iothub_endpoint_servicebus_topic_test.go +++ b/azurerm/resource_arm_iothub_endpoint_servicebus_topic_test.go @@ -68,11 +68,10 @@ func TestAccAzureRMIotHubEndpointServiceBusTopic_requiresImport(t *testing.T) { func testAccAzureRMIotHubEndpointServiceBusTopic_basic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" + name = "acctestRG-iothub-%[1]d" location = "%[2]s" } - resource "azurerm_servicebus_namespace" "test" { name = "acctest-%[1]d" location = "${azurerm_resource_group.test.location}" @@ -175,13 +174,13 @@ func testAccAzureRMIotHubEndpointServiceBusTopicExists(resourceName string) reso } for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - return nil + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + return nil + } } } - return fmt.Errorf("Bad: No ServiceBus Topic endpoint %s defined for IotHub %s", endpointName, iothubName) - } } @@ -197,7 +196,6 @@ func testAccAzureRMIotHubEndpointServiceBusTopicDestroy(s *terraform.State) erro endpointName := rs.Primary.Attributes["name"] iothubName := rs.Primary.Attributes["iothub_name"] resourceGroup := rs.Primary.Attributes["resource_group_name"] - iothub, err := client.Get(ctx, resourceGroup, iothubName) if err != nil { if utils.ResponseWasNotFound(iothub.Response) { @@ -216,8 +214,10 @@ func testAccAzureRMIotHubEndpointServiceBusTopicDestroy(s *terraform.State) erro } for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - return fmt.Errorf("Bad: ServiceBus Topic endpoint %s still exists on IoTHb %s", endpointName, iothubName) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + return fmt.Errorf("Bad: ServiceBus Topic endpoint %s still exists on IoTHb %s", endpointName, iothubName) + } } } } diff --git a/azurerm/resource_arm_iothub_endpoint_storage_container.go b/azurerm/resource_arm_iothub_endpoint_storage_container.go index d40822bd8a9c..3d0733b8f40e 100644 --- a/azurerm/resource_arm_iothub_endpoint_storage_container.go +++ b/azurerm/resource_arm_iothub_endpoint_storage_container.go @@ -4,15 +4,16 @@ import ( "fmt" "regexp" "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -26,6 +27,13 @@ func resourceArmIotHubEndpointStorageContainer() *schema.Resource { State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -82,9 +90,8 @@ func resourceArmIotHubEndpointStorageContainer() *schema.Resource { }, "encoding": { - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: suppress.CaseDifference, + Type: schema.TypeString, + Optional: true, ValidateFunc: validation.StringInSlice([]string{ string(devices.Avro), string(devices.AvroDeflate), @@ -97,7 +104,8 @@ func resourceArmIotHubEndpointStorageContainer() *schema.Resource { func resourceArmIotHubEndpointStorageContainerCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() subscriptionID := meta.(*ArmClient).subscriptionId iothubName := d.Get("iothub_name").(string) @@ -116,7 +124,6 @@ func resourceArmIotHubEndpointStorageContainerCreateUpdate(d *schema.ResourceDat } endpointName := d.Get("name").(string) - resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName) connectionStr := d.Get("connection_string").(string) @@ -157,13 +164,14 @@ func resourceArmIotHubEndpointStorageContainerCreateUpdate(d *schema.ResourceDat alreadyExists := false for _, existingEndpoint := range *routing.Endpoints.StorageContainers { - if strings.EqualFold(*existingEndpoint.Name, endpointName) { - if d.IsNewResource() && requireResourcesToBeImported { - return tf.ImportAsExistsError("azurerm_iothub_endpoint_storage_container", resourceId) + if existingEndpointName := existingEndpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + if d.IsNewResource() && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_endpoint_storage_container", resourceId) + } + endpoints = append(endpoints, storageContainerEndpoint) + alreadyExists = true } - endpoints = append(endpoints, storageContainerEndpoint) - alreadyExists = true - } else { endpoints = append(endpoints, existingEndpoint) } @@ -174,7 +182,6 @@ func resourceArmIotHubEndpointStorageContainerCreateUpdate(d *schema.ResourceDat } else if !alreadyExists { return fmt.Errorf("Unable to find Storage Container Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup) } - routing.Endpoints.StorageContainers = &endpoints future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") @@ -193,10 +200,10 @@ func resourceArmIotHubEndpointStorageContainerCreateUpdate(d *schema.ResourceDat func resourceArmIotHubEndpointStorageContainerRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) - if err != nil { return err } @@ -220,13 +227,15 @@ func resourceArmIotHubEndpointStorageContainerRead(d *schema.ResourceData, meta if endpoints := iothub.Properties.Routing.Endpoints.StorageContainers; endpoints != nil { for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - d.Set("connection_string", endpoint.ConnectionString) - d.Set("container_name", endpoint.ContainerName) - d.Set("file_name_format", endpoint.FileNameFormat) - d.Set("batch_frequency_in_seconds", endpoint.BatchFrequencyInSeconds) - d.Set("max_chunk_size_in_bytes", endpoint.MaxChunkSizeInBytes) - d.Set("encoding", endpoint.Encoding) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + d.Set("connection_string", endpoint.ConnectionString) + d.Set("container_name", endpoint.ContainerName) + d.Set("file_name_format", endpoint.FileNameFormat) + d.Set("batch_frequency_in_seconds", endpoint.BatchFrequencyInSeconds) + d.Set("max_chunk_size_in_bytes", endpoint.MaxChunkSizeInBytes) + d.Set("encoding", endpoint.Encoding) + } } } } @@ -236,10 +245,10 @@ func resourceArmIotHubEndpointStorageContainerRead(d *schema.ResourceData, meta func resourceArmIotHubEndpointStorageContainerDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).IoTHub.ResourceClient - ctx := meta.(*ArmClient).StopContext + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() parsedIothubEndpointId, err := parseAzureResourceID(d.Id()) - if err != nil { return err } @@ -271,11 +280,12 @@ func resourceArmIotHubEndpointStorageContainerDelete(d *schema.ResourceData, met updatedEndpoints := make([]devices.RoutingStorageContainerProperties, 0) for _, endpoint := range *endpoints { - if !strings.EqualFold(*endpoint.Name, endpointName) { - updatedEndpoints = append(updatedEndpoints, endpoint) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if !strings.EqualFold(*existingEndpointName, endpointName) { + updatedEndpoints = append(updatedEndpoints, endpoint) + } } } - iothub.Properties.Routing.Endpoints.StorageContainers = &updatedEndpoints future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") diff --git a/azurerm/resource_arm_iothub_endpoint_storage_container_test.go b/azurerm/resource_arm_iothub_endpoint_storage_container_test.go index 484d35e66b74..f92bff056210 100644 --- a/azurerm/resource_arm_iothub_endpoint_storage_container_test.go +++ b/azurerm/resource_arm_iothub_endpoint_storage_container_test.go @@ -45,7 +45,6 @@ func TestAccAzureRMIotHubEndpointStorageContainer_requiresImport(t *testing.T) { t.Skip("Skipping since resources aren't required to be imported") return } - resourceName := "azurerm_iothub_endpoint_storage_container.test" rInt := tf.AccRandTimeInt() location := testLocation() @@ -72,7 +71,7 @@ func TestAccAzureRMIotHubEndpointStorageContainer_requiresImport(t *testing.T) { func testAccAzureRMIotHubEndpointStorageContainer_basic(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" + name = "acctestRG-iothub-%[1]d" location = "%[2]s" } @@ -152,16 +151,16 @@ func testAccAzureRMIotHubEndpointStorageContainerExists(resourceName string) res if !ok { return fmt.Errorf("Not found: %s", resourceName) } + parsedIothubId, err := azure.ParseAzureResourceID(rs.Primary.ID) if err != nil { return err } + iothubName := parsedIothubId.Path["IotHubs"] endpointName := parsedIothubId.Path["Endpoints"] resourceGroup := parsedIothubId.ResourceGroup - client := testAccProvider.Meta().(*ArmClient).IoTHub.ResourceClient - iothub, err := client.Get(ctx, resourceGroup, iothubName) if err != nil { if utils.ResponseWasNotFound(iothub.Response) { @@ -181,13 +180,14 @@ func testAccAzureRMIotHubEndpointStorageContainerExists(resourceName string) res } for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - return nil + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + return nil + } } } return fmt.Errorf("Bad: No Storage Container endpoint %s defined for IotHub %s", endpointName, iothubName) - } } @@ -203,27 +203,27 @@ func testAccAzureRMIotHubEndpointStorageContainerDestroy(s *terraform.State) err endpointName := rs.Primary.Attributes["name"] iothubName := rs.Primary.Attributes["iothub_name"] resourceGroup := rs.Primary.Attributes["resource_group_name"] - iothub, err := client.Get(ctx, resourceGroup, iothubName) if err != nil { if utils.ResponseWasNotFound(iothub.Response) { return nil } - return fmt.Errorf("Bad: Get on iothubResourceClient: %+v", err) } if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil { return nil } - endpoints := iothub.Properties.Routing.Endpoints.StorageContainers + endpoints := iothub.Properties.Routing.Endpoints.StorageContainers if endpoints == nil { return nil } for _, endpoint := range *endpoints { - if strings.EqualFold(*endpoint.Name, endpointName) { - return fmt.Errorf("Bad: Storage Container endpoint %s still exists on IoTHb %s", endpointName, iothubName) + if existingEndpointName := endpoint.Name; existingEndpointName != nil { + if strings.EqualFold(*existingEndpointName, endpointName) { + return fmt.Errorf("Bad: Storage Container endpoint %s still exists on IoTHb %s", endpointName, iothubName) + } } } } diff --git a/website/azurerm.erb b/website/azurerm.erb index dce2bf913a38..82b78f08d448 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -1353,6 +1353,22 @@ azurerm_iothub_consumer_group +
  • + azurerm_iothub_endpoint_eventhub +
  • + +
  • + azurerm_iothub_endpoint_servicebus_queue +
  • + +
  • + azurerm_iothub_endpoint_servicebus_topic +
  • + +
  • + azurerm_iothub_endpoint_storage_container +
  • +
  • azurerm_iothub_shared_access_policy
  • diff --git a/website/docs/r/iothub_endpoint_eventhub.html.markdown b/website/docs/r/iothub_endpoint_eventhub.html.markdown index 7a4453932ca9..f2e3630ed535 100644 --- a/website/docs/r/iothub_endpoint_eventhub.html.markdown +++ b/website/docs/r/iothub_endpoint_eventhub.html.markdown @@ -1,7 +1,8 @@ --- +subcategory: "Messaging" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_iothub_endpoint_eventhub" -sidebar_current: "docs-azurerm-resource-messaging-iothub-endpoint-eventhub-x" +sidebar_current: "docs-azurerm-resource-messaging-iothub-endpoint-eventhub" description: |- Manages an IotHub EventHub Endpoint --- diff --git a/website/docs/r/iothub_endpoint_servicebus_queue.html.markdown b/website/docs/r/iothub_endpoint_servicebus_queue.html.markdown index b337e3411366..fa1529c5f835 100644 --- a/website/docs/r/iothub_endpoint_servicebus_queue.html.markdown +++ b/website/docs/r/iothub_endpoint_servicebus_queue.html.markdown @@ -1,7 +1,8 @@ --- +subcategory: "Messaging" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_iothub_endpoint_servicebus_queue" -sidebar_current: "docs-azurerm-resource-messaging-iothub-servicebus-queue-x" +sidebar_current: "docs-azurerm-resource-messaging-iothub-servicebus-queue" description: |- Manages an IotHub ServiceBus Queue Endpoint --- diff --git a/website/docs/r/iothub_endpoint_servicebus_topic.html.markdown b/website/docs/r/iothub_endpoint_servicebus_topic.html.markdown index 15569b0ab013..677b35135407 100644 --- a/website/docs/r/iothub_endpoint_servicebus_topic.html.markdown +++ b/website/docs/r/iothub_endpoint_servicebus_topic.html.markdown @@ -1,7 +1,8 @@ --- +subcategory: "Messaging" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_iothub_endpoint_servicebus_topic" -sidebar_current: "docs-azurerm-resource-messaging-iothub-servicebus-topic-x" +sidebar_current: "docs-azurerm-resource-messaging-iothub-servicebus-topic" description: |- Manages an IotHub ServiceBus Topic Endpoint --- diff --git a/website/docs/r/iothub_endpoint_storage_container.html.markdown b/website/docs/r/iothub_endpoint_storage_container.html.markdown index 80405aaa8a8e..7d5e5c8fdc8b 100644 --- a/website/docs/r/iothub_endpoint_storage_container.html.markdown +++ b/website/docs/r/iothub_endpoint_storage_container.html.markdown @@ -1,7 +1,8 @@ --- +subcategory: "Messaging" layout: "azurerm" page_title: "Azure Resource Manager: azurerm_iothub_endpoint_storage_container" -sidebar_current: "docs-azurerm-resource-messaging-iothub-endpoint-storage-container-x" +sidebar_current: "docs-azurerm-resource-messaging-iothub-endpoint-storage-container" description: |- Manages an IotHub Storage Container Endpoint --- @@ -73,7 +74,6 @@ The following arguments are supported: * `iothub_name` - (Required) The name of the IoTHub to which this Storage Container Endpoint belongs. Changing this forces a new resource to be created. - * `connection_string` - (Required) The connection string for the endpoint. * `batch_frequency_in_seconds` - (Optional) Time interval at which blobs are written to storage. Value should be between 60 and 720 seconds. Default value is 300 seconds.