From 4d0e48df4b971a5b76adcf8557b5c7e6e741fed2 Mon Sep 17 00:00:00 2001 From: Andrey Moor Date: Tue, 31 Jul 2018 12:32:51 -0700 Subject: [PATCH 1/9] Add endpoint and route attributes to an IoTHub resource --- azurerm/resource_arm_iothub.go | 377 ++++++++++++++++++++++++++++ azurerm/resource_arm_iothub_test.go | 36 ++- website/docs/r/iothub.html.markdown | 78 +++++- 3 files changed, 487 insertions(+), 4 deletions(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index aa3bc77af594..33659a8c6ef3 100644 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -7,12 +7,14 @@ import ( "strconv" "time" + "github.com/Azure/azure-sdk-for-go/services/eventhub/mgmt/2017-04-01/eventhub" "github.com/Azure/azure-sdk-for-go/services/iothub/mgmt/2017-07-01/devices" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" + "strings" ) func resourceArmIotHub() *schema.Resource { @@ -110,6 +112,105 @@ func resourceArmIotHub() *schema.Resource { }, }, + "endpoint": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "AzureIotHub.StorageContainer", + "AzureIotHub.ServiceBusQueue", + "AzureIotHub.ServiceBusTopic", + "AzureIotHub.EventHub", + }, false), + }, + "connection_string": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateIoTHubEndpointName, + }, + "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), + }, + "container_name": { + Type: schema.TypeString, + Optional: true, + }, + "encoding": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + string(eventhub.Avro), + string(eventhub.AvroDeflate), + }, true), + }, + "file_name_format": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateIoTHubFileNameFormat, + }, + }, + }, + }, + + "route": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(0, 64), + }, + "source": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "DeviceJobLifecycleEvents", + "DeviceLifecycleEvents", + "DeviceMessages", + "Invalid", + "TwinChangeEvents", + }, false), + }, + "condition": { + Type: schema.TypeString, + Optional: true, + Default: "true", + }, + "endpoint_names": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + }, + "is_enabled": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, + "tags": tagsSchema(), }, } @@ -142,6 +243,18 @@ func resourceArmIotHubCreateAndUpdate(d *schema.ResourceData, meta interface{}) skuInfo := expandIoTHubSku(d) tags := d.Get("tags").(map[string]interface{}) + endpoints, err := expandIoTHubEndpoints(d, subscriptionID) + routes := expandIoTHubRoutes(d) + + routingProperties := devices.RoutingProperties{ + Endpoints: endpoints, + Routes: routes, + } + + iotHubProperties := devices.IotHubProperties{ + Routing: &routingProperties, + } + properties := devices.IotHubDescription{ Name: utils.String(name), Location: utils.String(location), @@ -149,6 +262,7 @@ func resourceArmIotHubCreateAndUpdate(d *schema.ResourceData, meta interface{}) Subscriptionid: utils.String(subscriptionID), Sku: &skuInfo, Tags: expandTags(tags), + Properties: &iotHubProperties, } future, err := client.CreateOrUpdate(ctx, resourceGroup, name, properties, "") @@ -206,6 +320,16 @@ func resourceArmIotHubRead(d *schema.ResourceData, meta interface{}) error { if properties := hub.Properties; properties != nil { d.Set("hostname", properties.HostName) + + endpoints := flattenIoTHubEndpoint(properties.Routing) + if err := d.Set("endpoint", endpoints); err != nil { + return fmt.Errorf("Error flattening `endpoint` in IoTHub %q: %+v", name, err) + } + + routes := flattenIoTHubRoute(properties.Routing) + if err := d.Set("route", routes); err != nil { + return fmt.Errorf("Error flattening `routes` in IoTHub %q: %+v", name, err) + } } d.Set("name", name) @@ -279,6 +403,114 @@ func iothubStateStatusCodeRefreshFunc(ctx context.Context, client devices.IotHub } } +func expandIoTHubRoutes(d *schema.ResourceData) *[]devices.RouteProperties { + routeList := d.Get("route").([]interface{}) + + routeProperties := make([]devices.RouteProperties, 0) + + for _, routeRaw := range routeList { + route := routeRaw.(map[string]interface{}) + + name := route["name"].(string) + source := devices.RoutingSource(route["source"].(string)) + condition := route["condition"].(string) + + endpointNamesRaw := route["endpoint_names"].([]interface{}) + endpointsNames := make([]string, 0, len(endpointNamesRaw)) + for _, n := range endpointNamesRaw { + endpointsNames = append(endpointsNames, n.(string)) + } + + isEnabled := route["is_enabled"].(bool) + + routeProperties = append(routeProperties, devices.RouteProperties{ + Name: &name, + Source: source, + Condition: &condition, + EndpointNames: &endpointsNames, + IsEnabled: &isEnabled, + }) + } + + return &routeProperties +} + +func expandIoTHubEndpoints(d *schema.ResourceData, subscriptionId string) (*devices.RoutingEndpoints, error) { + routeEndpointList := d.Get("endpoint").([]interface{}) + + serviceBusQueueEndpointProperties := make([]devices.RoutingServiceBusQueueEndpointProperties, 0) + serviceBusTopicEndpointProperties := make([]devices.RoutingServiceBusTopicEndpointProperties, 0) + eventHubProperties := make([]devices.RoutingEventHubProperties, 0) + storageContainerProperties := make([]devices.RoutingStorageContainerProperties, 0) + + for _, endpointRaw := range routeEndpointList { + endpoint := endpointRaw.(map[string]interface{}) + + t := endpoint["type"] + connectionStr := endpoint["connection_string"].(string) + name := endpoint["name"].(string) + subscriptionID := subscriptionId + resourceGroup := d.Get("resource_group_name").(string) + + switch t { + case "AzureIotHub.StorageContainer": + containerName := endpoint["container_name"].(string) + fileNameFormat := endpoint["file_name_format"].(string) + batchFrequencyInSeconds := int32(endpoint["batch_frequency_in_seconds"].(int)) + maxChunkSizeInBytes := int32(endpoint["max_chunk_size_in_bytes"].(int)) + encoding := endpoint["encoding"].(string) + + storageContainer := devices.RoutingStorageContainerProperties{ + ConnectionString: &connectionStr, + Name: &name, + SubscriptionID: &subscriptionID, + ResourceGroup: &resourceGroup, + ContainerName: &containerName, + FileNameFormat: &fileNameFormat, + BatchFrequencyInSeconds: &batchFrequencyInSeconds, + MaxChunkSizeInBytes: &maxChunkSizeInBytes, + Encoding: &encoding, + } + storageContainerProperties = append(storageContainerProperties, storageContainer) + break + case "AzureIotHub.ServiceBusQueue": + sbQueue := devices.RoutingServiceBusQueueEndpointProperties{ + ConnectionString: &connectionStr, + Name: &name, + SubscriptionID: &subscriptionID, + ResourceGroup: &resourceGroup, + } + serviceBusQueueEndpointProperties = append(serviceBusQueueEndpointProperties, sbQueue) + break + case "AzureIotHub.ServiceBusTopic": + sbTopic := devices.RoutingServiceBusTopicEndpointProperties{ + ConnectionString: &connectionStr, + Name: &name, + SubscriptionID: &subscriptionID, + ResourceGroup: &resourceGroup, + } + serviceBusTopicEndpointProperties = append(serviceBusTopicEndpointProperties, sbTopic) + break + case "AzureIotHub.EventHub": + eventHub := devices.RoutingEventHubProperties{ + ConnectionString: &connectionStr, + Name: &name, + SubscriptionID: &subscriptionID, + ResourceGroup: &resourceGroup, + } + eventHubProperties = append(eventHubProperties, eventHub) + break + } + } + + return &devices.RoutingEndpoints{ + ServiceBusQueues: &serviceBusQueueEndpointProperties, + ServiceBusTopics: &serviceBusTopicEndpointProperties, + EventHubs: &eventHubProperties, + StorageContainers: &storageContainerProperties, + }, nil +} + func expandIoTHubSku(d *schema.ResourceData) devices.IotHubSkuInfo { skuList := d.Get("sku").([]interface{}) skuMap := skuList[0].(map[string]interface{}) @@ -332,3 +564,148 @@ func flattenIoTHubSharedAccessPolicy(input *[]devices.SharedAccessSignatureAutho return results } + +func flattenIoTHubEndpoint(input *devices.RoutingProperties) []interface{} { + results := make([]interface{}, 0) + + if input != nil && input.Endpoints != nil { + + for _, container := range *input.Endpoints.StorageContainers { + output := make(map[string]interface{}, 0) + + if connString := container.ConnectionString; connString != nil { + output["connection_string"] = *connString + } + if name := container.Name; name != nil { + output["name"] = *name + } + if containerName := container.ContainerName; containerName != nil { + output["container_name"] = *containerName + } + if fileNameFmt := container.FileNameFormat; fileNameFmt != nil { + output["file_name_format"] = *fileNameFmt + } + if batchFreq := container.BatchFrequencyInSeconds; batchFreq != nil { + output["batch_frequency_in_seconds"] = *batchFreq + } + if chunkSize := container.MaxChunkSizeInBytes; chunkSize != nil { + output["max_chunk_size_in_bytes"] = *chunkSize + } + if encoding := container.Encoding; encoding != nil { + output["encoding"] = *encoding + } + + results = append(results, output) + } + + for _, queue := range *input.Endpoints.ServiceBusQueues { + output := make(map[string]interface{}, 0) + + if connString := queue.ConnectionString; connString != nil { + output["connection_string"] = *connString + } + if name := queue.Name; name != nil { + output["name"] = *name + } + + results = append(results, output) + } + + for _, topic := range *input.Endpoints.ServiceBusTopics { + output := make(map[string]interface{}, 0) + + if connString := topic.ConnectionString; connString != nil { + output["connection_string"] = *connString + } + if name := topic.Name; name != nil { + output["name"] = *name + } + + results = append(results, output) + } + + for _, eventHub := range *input.Endpoints.EventHubs { + output := make(map[string]interface{}, 0) + + if connString := eventHub.ConnectionString; connString != nil { + output["connection_string"] = *connString + } + if name := eventHub.Name; name != nil { + output["name"] = *name + } + + results = append(results, output) + } + } + + return results +} + +func flattenIoTHubRoute(input *devices.RoutingProperties) []interface{} { + results := make([]interface{}, 0) + + if input != nil && input.Routes != nil { + for _, route := range *input.Routes { + output := make(map[string]interface{}, 0) + + if name := route.Name; name != nil { + output["name"] = *name + } + if condition := route.Condition; condition != nil { + output["condition"] = *condition + } + if endpointNames := route.EndpointNames; endpointNames != nil { + output["endpoint_names"] = *endpointNames + } + if isEnabled := route.IsEnabled; isEnabled != nil { + output["is_enabled"] = *isEnabled + } + output["source"] = route.Source + + results = append(results, output) + } + } + + return results +} + +func validateIoTHubEndpointName(v interface{}, k string) (ws []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 +} + +func validateIoTHubFileNameFormat(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + requiredComponents := []string{ + "{iothub}", + "{partition}", + "{YYYY}", + "{MM}", + "{DD}", + "{HH}", + "{mm}", + } + + for _, component := range requiredComponents { + if !strings.Contains(value, component) { + errors = append(errors, fmt.Errorf("%s needs to contain %q", k, component)) + } + } + + return +} diff --git a/azurerm/resource_arm_iothub_test.go b/azurerm/resource_arm_iothub_test.go index 40569d1b893f..3a97fc0a5fbd 100644 --- a/azurerm/resource_arm_iothub_test.go +++ b/azurerm/resource_arm_iothub_test.go @@ -90,6 +90,21 @@ resource "azurerm_resource_group" "foo" { location = "%s" } +resource "azurerm_storage_account" "test" { + name = "acctestRG%dsta" + resource_group_name = "${azurerm_resource_group.foo.name}" + location = "${azurerm_resource_group.foo.location}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "test" { + name = "test" + resource_group_name = "${azurerm_resource_group.foo.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "private" +} + resource "azurerm_iothub" "test" { name = "acctestIoTHub-%d" resource_group_name = "${azurerm_resource_group.foo.name}" @@ -100,9 +115,28 @@ resource "azurerm_iothub" "test" { capacity = "1" } + endpoint { + type = "AzureIotHub.StorageContainer" + connection_string = "${azurerm_storage_account.test.primary_blob_connection_string}" + name = "export" + batch_frequency_in_seconds = 60 + max_chunk_size_in_bytes = 10485760 + container_name = "test" + encoding = "Avro" + file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}" + } + + route { + name = "export" + source = "DeviceMessages" + condition = "true" + endpoint_names = ["export"] + is_enabled = true + } + tags { "purpose" = "testing" } } -`, rInt, location, rInt) +`, rInt, location, rInt, rInt) } diff --git a/website/docs/r/iothub.html.markdown b/website/docs/r/iothub.html.markdown index 4a680df86e6d..79a9c74bfd43 100644 --- a/website/docs/r/iothub.html.markdown +++ b/website/docs/r/iothub.html.markdown @@ -3,7 +3,7 @@ layout: "azurerm" page_title: "Azure Resource Manager: azurerm_iothub" sidebar_current: "docs-azurerm-resource-messaging-iothub" description: |- - Manages a IotHub resource + Manages a IotHub resource --- # azurerm_iothub @@ -18,6 +18,21 @@ resource "azurerm_resource_group" "test" { location = "West US" } +resource "azurerm_storage_account" "test" { + name = "teststa" + 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 = "test" + 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 = "test" resource_group_name = "${azurerm_resource_group.test.name}" @@ -28,6 +43,25 @@ resource "azurerm_iothub" "test" { capacity = "1" } + endpoint { + type = "AzureIotHub.StorageContainers" + connection_string = "${azurerm_storage_account.test.primary_blob_connection_string}" + name = "export" + batch_frequency_in_seconds = 60 + max_chunk_size_in_bytes = 10485760 + container_name = "test" + encoding = "Avro" + file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}" + } + + route { + name = "export" + source = "DeviceMessages" + condition = "true" + endpoint_names = ["export"] + is_enabled = true + } + tags { "purpose" = "testing" } @@ -44,7 +78,11 @@ The following arguments are supported: * `location` - (Required) Specifies the supported Azure location where the resource has to be createc. Changing this forces a new resource to be created. -* `sku` - (Required) A `sku` block as defined below. +* `sku` - (Required) A `sku` block as defined below. + +* `endpoint` - (Optional) An `endpoint` block as defined below. + +* `route` - (Optional) A `route` block as defined below. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -56,7 +94,41 @@ A `sku` block supports the following: * `tier` - (Required) The billing tier for the IoT Hub. Possible values are `Free` or `Standard`. -* `capacity` - (Required) The number of provisioned IoT Hub units. +* `capacity` - (Required) The number of provisioned IoT Hub units. + +--- + +An `endpoint` block supports the following: + +* `type` - (Required) The type of the endpoint. Possible values are `AzureIotHub.StorageContainer`, `AzureIotHub.ServiceBusQueue`, `AzureIotHub.ServiceBusTopic` or `AzureIotHub.EventHub`. + +* `connection_string` - (Required) The connection string for the endpoint. + +* `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`. + +* `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. This attribute is mandatory for endpoint type `AzureIotHub.StorageContainer`. + +* `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). This attribute is mandatory for endpoint type `AzureIotHub.StorageContainer`. + +* `container_name` - (Optional) The name of storage container in the storage account. This attribute is mandatory for endpoint type `AzureIotHub.StorageContainer`. + +* `encoding` - (Optional) Encoding that is used to serialize messages to blobs. Supported values are 'avro' and 'avrodeflate'. Default value is 'avro'. This attribute is mandatory for endpoint type `AzureIotHub.StorageContainer`. + +* `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. This attribute is mandatory for endpoint type `AzureIotHub.StorageContainer`. + +--- + +A `route` block supports the following: + +* `name` - (Required) The name of the route. The name can only include alphanumeric characters, periods, underscores, hyphens, has a maximum length of 64 characters, and must be unique. + +* `source` - (Required) The source that the routing rule is to be applied to, such as `DeviceMessages`. Possible values include: `RoutingSourceInvalid`, `RoutingSourceDeviceMessages`, `RoutingSourceTwinChangeEvents`, `RoutingSourceDeviceLifecycleEvents`, `RoutingSourceDeviceJobLifecycleEvents`. + +* `condition` - (Optional) The condition that is evaluated to apply the routing rule. If no condition is provided, it evaluates to true by default. For grammar, see: https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-query-language. + +* `endpoint_names` - (Required) The list of endpoints to which messages that satisfy the condition are routed. + +* `is_enabled` - (Required) Used to specify whether a route is enabled. ## Attributes Reference From 67bb11463be73311b6cca5dedbebbdd1ebf45da3 Mon Sep 17 00:00:00 2001 From: Andrey Moor Date: Tue, 31 Jul 2018 12:48:03 -0700 Subject: [PATCH 2/9] Fix route flattening error description --- azurerm/resource_arm_iothub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 33659a8c6ef3..17efe2f40e15 100644 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -328,7 +328,7 @@ func resourceArmIotHubRead(d *schema.ResourceData, meta interface{}) error { routes := flattenIoTHubRoute(properties.Routing) if err := d.Set("route", routes); err != nil { - return fmt.Errorf("Error flattening `routes` in IoTHub %q: %+v", name, err) + return fmt.Errorf("Error flattening `route` in IoTHub %q: %+v", name, err) } } From 2bada262061759093e4025ac0f44ec2b5f66ac14 Mon Sep 17 00:00:00 2001 From: Andrey Moor Date: Wed, 1 Aug 2018 11:00:11 -0700 Subject: [PATCH 3/9] Address PR comments --- azurerm/resource_arm_iothub.go | 124 ++++++++++++++++------------ azurerm/resource_arm_iothub_test.go | 41 +++++++++ 2 files changed, 112 insertions(+), 53 deletions(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 17efe2f40e15..4e643e368e0f 100644 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -192,6 +192,8 @@ func resourceArmIotHub() *schema.Resource { }, false), }, "condition": { + // The condition is a string value representing device-to-cloud message routes query expression + // https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-query-language#device-to-cloud-message-routes-query-expressions Type: schema.TypeString, Optional: true, Default: "true", @@ -244,6 +246,10 @@ func resourceArmIotHubCreateAndUpdate(d *schema.ResourceData, meta interface{}) tags := d.Get("tags").(map[string]interface{}) endpoints, err := expandIoTHubEndpoints(d, subscriptionID) + if err != nil { + return fmt.Errorf("Error expanding `endpoints`: %+v", err) + } + routes := expandIoTHubRoutes(d) routingProperties := devices.RoutingProperties{ @@ -416,7 +422,7 @@ func expandIoTHubRoutes(d *schema.ResourceData) *[]devices.RouteProperties { condition := route["condition"].(string) endpointNamesRaw := route["endpoint_names"].([]interface{}) - endpointsNames := make([]string, 0, len(endpointNamesRaw)) + endpointsNames := make([]string, 0) for _, n := range endpointNamesRaw { endpointsNames = append(endpointsNames, n.(string)) } @@ -570,71 +576,83 @@ func flattenIoTHubEndpoint(input *devices.RoutingProperties) []interface{} { if input != nil && input.Endpoints != nil { - for _, container := range *input.Endpoints.StorageContainers { - output := make(map[string]interface{}, 0) - - if connString := container.ConnectionString; connString != nil { - output["connection_string"] = *connString - } - if name := container.Name; name != nil { - output["name"] = *name - } - if containerName := container.ContainerName; containerName != nil { - output["container_name"] = *containerName + storageContainers := *input.Endpoints.StorageContainers + if storageContainers != nil { + for _, container := range storageContainers { + output := make(map[string]interface{}, 0) + + if connString := container.ConnectionString; connString != nil { + output["connection_string"] = *connString + } + if name := container.Name; name != nil { + output["name"] = *name + } + if containerName := container.ContainerName; containerName != nil { + output["container_name"] = *containerName + } + if fileNameFmt := container.FileNameFormat; fileNameFmt != nil { + output["file_name_format"] = *fileNameFmt + } + if batchFreq := container.BatchFrequencyInSeconds; batchFreq != nil { + output["batch_frequency_in_seconds"] = *batchFreq + } + if chunkSize := container.MaxChunkSizeInBytes; chunkSize != nil { + output["max_chunk_size_in_bytes"] = *chunkSize + } + if encoding := container.Encoding; encoding != nil { + output["encoding"] = *encoding + } + + results = append(results, output) } - if fileNameFmt := container.FileNameFormat; fileNameFmt != nil { - output["file_name_format"] = *fileNameFmt - } - if batchFreq := container.BatchFrequencyInSeconds; batchFreq != nil { - output["batch_frequency_in_seconds"] = *batchFreq - } - if chunkSize := container.MaxChunkSizeInBytes; chunkSize != nil { - output["max_chunk_size_in_bytes"] = *chunkSize - } - if encoding := container.Encoding; encoding != nil { - output["encoding"] = *encoding - } - - results = append(results, output) } - for _, queue := range *input.Endpoints.ServiceBusQueues { - output := make(map[string]interface{}, 0) + sbQueues := *input.Endpoints.ServiceBusQueues + if sbQueues != nil { + for _, queue := range sbQueues { + output := make(map[string]interface{}, 0) - if connString := queue.ConnectionString; connString != nil { - output["connection_string"] = *connString - } - if name := queue.Name; name != nil { - output["name"] = *name - } + if connString := queue.ConnectionString; connString != nil { + output["connection_string"] = *connString + } + if name := queue.Name; name != nil { + output["name"] = *name + } - results = append(results, output) + results = append(results, output) + } } - for _, topic := range *input.Endpoints.ServiceBusTopics { - output := make(map[string]interface{}, 0) + sbTopics := *input.Endpoints.ServiceBusTopics + if sbTopics != nil { + for _, topic := range sbTopics { + output := make(map[string]interface{}, 0) - if connString := topic.ConnectionString; connString != nil { - output["connection_string"] = *connString - } - if name := topic.Name; name != nil { - output["name"] = *name - } + if connString := topic.ConnectionString; connString != nil { + output["connection_string"] = *connString + } + if name := topic.Name; name != nil { + output["name"] = *name + } - results = append(results, output) + results = append(results, output) + } } - for _, eventHub := range *input.Endpoints.EventHubs { - output := make(map[string]interface{}, 0) + eventHubs := *input.Endpoints.EventHubs + if eventHubs != nil { + for _, eventHub := range eventHubs { + output := make(map[string]interface{}, 0) - if connString := eventHub.ConnectionString; connString != nil { - output["connection_string"] = *connString - } - if name := eventHub.Name; name != nil { - output["name"] = *name - } + if connString := eventHub.ConnectionString; connString != nil { + output["connection_string"] = *connString + } + if name := eventHub.Name; name != nil { + output["name"] = *name + } - results = append(results, output) + results = append(results, output) + } } } diff --git a/azurerm/resource_arm_iothub_test.go b/azurerm/resource_arm_iothub_test.go index 3a97fc0a5fbd..fdde3db47068 100644 --- a/azurerm/resource_arm_iothub_test.go +++ b/azurerm/resource_arm_iothub_test.go @@ -26,7 +26,24 @@ func TestAccAzureRMIotHub_basicStandard(t *testing.T) { }, }, }) +} + +func TestAccAzureRMIotHub_customRoutes(t *testing.T) { + rInt := acctest.RandInt() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHub_customRoutes(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubExists("azurerm_iothub.test"), + ), + }, + }, + }) } func testCheckAzureRMIotHubDestroy(s *terraform.State) error { @@ -90,6 +107,30 @@ resource "azurerm_resource_group" "foo" { location = "%s" } +resource "azurerm_iothub" "test" { + name = "acctestIoTHub-%d" + resource_group_name = "${azurerm_resource_group.foo.name}" + location = "${azurerm_resource_group.foo.location}" + sku { + name = "S1" + tier = "Standard" + capacity = "1" + } + + tags { + "purpose" = "testing" + } +} +`, rInt, location, rInt) +} + +func testAccAzureRMIotHub_customRoutes(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "foo" { + name = "acctestRG-%d" + location = "%s" +} + resource "azurerm_storage_account" "test" { name = "acctestRG%dsta" resource_group_name = "${azurerm_resource_group.foo.name}" From f8456ddabf1a44a03ef720b58e0c79aa91c4a7e2 Mon Sep 17 00:00:00 2001 From: Andrey Moor Date: Wed, 1 Aug 2018 11:03:07 -0700 Subject: [PATCH 4/9] Fix error message description --- azurerm/resource_arm_iothub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 4e643e368e0f..3f6e6a3fdf1d 100644 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -247,7 +247,7 @@ func resourceArmIotHubCreateAndUpdate(d *schema.ResourceData, meta interface{}) endpoints, err := expandIoTHubEndpoints(d, subscriptionID) if err != nil { - return fmt.Errorf("Error expanding `endpoints`: %+v", err) + return fmt.Errorf("Error expanding `endpoint`: %+v", err) } routes := expandIoTHubRoutes(d) From b41754131f9046a834b3a0b8f5f714aa31348531 Mon Sep 17 00:00:00 2001 From: Andrey Moor Date: Thu, 2 Aug 2018 10:39:20 -0700 Subject: [PATCH 5/9] Fix acceptance tests --- azurerm/resource_arm_iothub.go | 35 ++++++++++++++++------------- azurerm/resource_arm_iothub_test.go | 11 ++++----- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 3f6e6a3fdf1d..87e06472ce93 100644 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -130,6 +130,14 @@ func resourceArmIotHub() *schema.Resource { "connection_string": { Type: schema.TypeString, Required: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // As Azure API masks the connection string key suppress diff for this property + if old != "" && strings.HasSuffix(old, "****"){ + return true + } + + return false + }, }, "name": { Type: schema.TypeString, @@ -205,7 +213,7 @@ func resourceArmIotHub() *schema.Resource { }, Required: true, }, - "is_enabled": { + "enabled": { Type: schema.TypeBool, Required: true, }, @@ -427,7 +435,7 @@ func expandIoTHubRoutes(d *schema.ResourceData) *[]devices.RouteProperties { endpointsNames = append(endpointsNames, n.(string)) } - isEnabled := route["is_enabled"].(bool) + isEnabled := route["enabled"].(bool) routeProperties = append(routeProperties, devices.RouteProperties{ Name: &name, @@ -576,9 +584,8 @@ func flattenIoTHubEndpoint(input *devices.RoutingProperties) []interface{} { if input != nil && input.Endpoints != nil { - storageContainers := *input.Endpoints.StorageContainers - if storageContainers != nil { - for _, container := range storageContainers { + if containers := input.Endpoints.StorageContainers; containers != nil { + for _, container := range *containers { output := make(map[string]interface{}, 0) if connString := container.ConnectionString; connString != nil { @@ -602,14 +609,14 @@ func flattenIoTHubEndpoint(input *devices.RoutingProperties) []interface{} { if encoding := container.Encoding; encoding != nil { output["encoding"] = *encoding } + output["type"] = "AzureIotHub.StorageContainer" results = append(results, output) } } - sbQueues := *input.Endpoints.ServiceBusQueues - if sbQueues != nil { - for _, queue := range sbQueues { + if queues := input.Endpoints.ServiceBusQueues; queues != nil { + for _, queue := range *queues { output := make(map[string]interface{}, 0) if connString := queue.ConnectionString; connString != nil { @@ -623,9 +630,8 @@ func flattenIoTHubEndpoint(input *devices.RoutingProperties) []interface{} { } } - sbTopics := *input.Endpoints.ServiceBusTopics - if sbTopics != nil { - for _, topic := range sbTopics { + if topics := input.Endpoints.ServiceBusTopics; topics != nil { + for _, topic := range *topics { output := make(map[string]interface{}, 0) if connString := topic.ConnectionString; connString != nil { @@ -639,9 +645,8 @@ func flattenIoTHubEndpoint(input *devices.RoutingProperties) []interface{} { } } - eventHubs := *input.Endpoints.EventHubs - if eventHubs != nil { - for _, eventHub := range eventHubs { + if eventHubs := input.Endpoints.EventHubs; eventHubs != nil { + for _, eventHub := range *eventHubs { output := make(map[string]interface{}, 0) if connString := eventHub.ConnectionString; connString != nil { @@ -676,7 +681,7 @@ func flattenIoTHubRoute(input *devices.RoutingProperties) []interface{} { output["endpoint_names"] = *endpointNames } if isEnabled := route.IsEnabled; isEnabled != nil { - output["is_enabled"] = *isEnabled + output["enabled"] = *isEnabled } output["source"] = route.Source diff --git a/azurerm/resource_arm_iothub_test.go b/azurerm/resource_arm_iothub_test.go index fdde3db47068..3be1168e862c 100644 --- a/azurerm/resource_arm_iothub_test.go +++ b/azurerm/resource_arm_iothub_test.go @@ -30,6 +30,7 @@ func TestAccAzureRMIotHub_basicStandard(t *testing.T) { func TestAccAzureRMIotHub_customRoutes(t *testing.T) { rInt := acctest.RandInt() + rStr := acctest.RandString(5) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -37,7 +38,7 @@ func TestAccAzureRMIotHub_customRoutes(t *testing.T) { CheckDestroy: testCheckAzureRMIotHubDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMIotHub_customRoutes(rInt, testLocation()), + Config: testAccAzureRMIotHub_customRoutes(rInt, rStr, testLocation()), Check: resource.ComposeTestCheckFunc( testCheckAzureRMIotHubExists("azurerm_iothub.test"), ), @@ -124,7 +125,7 @@ resource "azurerm_iothub" "test" { `, rInt, location, rInt) } -func testAccAzureRMIotHub_customRoutes(rInt int, location string) string { +func testAccAzureRMIotHub_customRoutes(rInt int, rStr string, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "foo" { name = "acctestRG-%d" @@ -132,7 +133,7 @@ resource "azurerm_resource_group" "foo" { } resource "azurerm_storage_account" "test" { - name = "acctestRG%dsta" + name = "acctestsa%s" resource_group_name = "${azurerm_resource_group.foo.name}" location = "${azurerm_resource_group.foo.location}" account_tier = "Standard" @@ -172,12 +173,12 @@ resource "azurerm_iothub" "test" { source = "DeviceMessages" condition = "true" endpoint_names = ["export"] - is_enabled = true + enabled = true } tags { "purpose" = "testing" } } -`, rInt, location, rInt, rInt) +`, rInt, location, rStr, rInt) } From 0bd62d03ce52547bca59c633c597aeee320f4d62 Mon Sep 17 00:00:00 2001 From: Andrey Moor Date: Thu, 2 Aug 2018 10:46:47 -0700 Subject: [PATCH 6/9] Format resource_arm_iothub.go properly --- azurerm/resource_arm_iothub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 87e06472ce93..f94c86c5a020 100644 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -132,7 +132,7 @@ func resourceArmIotHub() *schema.Resource { Required: true, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { // As Azure API masks the connection string key suppress diff for this property - if old != "" && strings.HasSuffix(old, "****"){ + if old != "" && strings.HasSuffix(old, "****") { return true } From df6427eb11f048fbc7004cb3cd5bbb6c640950bb Mon Sep 17 00:00:00 2001 From: Andrey Moor Date: Fri, 3 Aug 2018 09:58:54 -0700 Subject: [PATCH 7/9] Fix formatting --- azurerm/resource_arm_iothub.go | 12 +++++++----- azurerm/resource_arm_iothub_test.go | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index a216ebe368c4..414c913379f5 100644 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -7,6 +7,7 @@ import ( "strconv" "time" + "github.com/Azure/azure-sdk-for-go/services/eventhub/mgmt/2017-04-01/eventhub" "github.com/Azure/azure-sdk-for-go/services/iothub/mgmt/2018-04-01/devices" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" @@ -233,6 +234,7 @@ func resourceArmIotHub() *schema.Resource { func resourceArmIotHubCreateAndUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ArmClient).iothubResourceClient ctx := meta.(*ArmClient).StopContext + subscriptionID := meta.(*ArmClient).subscriptionId name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) @@ -272,11 +274,11 @@ func resourceArmIotHubCreateAndUpdate(d *schema.ResourceData, meta interface{}) } properties := devices.IotHubDescription{ - Name: utils.String(name), - Location: utils.String(location), - Sku: &skuInfo, - Tags: expandTags(tags), - Properties: &iotHubProperties, + Name: utils.String(name), + Location: utils.String(location), + Sku: &skuInfo, + Tags: expandTags(tags), + Properties: &iotHubProperties, } future, err := client.CreateOrUpdate(ctx, resourceGroup, name, properties, "") diff --git a/azurerm/resource_arm_iothub_test.go b/azurerm/resource_arm_iothub_test.go index 3116bd4494e4..0792171ddd9c 100644 --- a/azurerm/resource_arm_iothub_test.go +++ b/azurerm/resource_arm_iothub_test.go @@ -59,6 +59,7 @@ func TestAccAzureRMIotHub_customRoutes(t *testing.T) { Config: testAccAzureRMIotHub_customRoutes(rInt, rStr, testLocation()), Check: resource.ComposeTestCheckFunc( testCheckAzureRMIotHubExists("azurerm_iothub.test"), + ), }, }, From 20f435bb6918eb036d609103a893a6ec994632b4 Mon Sep 17 00:00:00 2001 From: Andrey Moor Date: Fri, 3 Aug 2018 10:18:25 -0700 Subject: [PATCH 8/9] Fix formatting --- azurerm/resource_arm_iothub_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/azurerm/resource_arm_iothub_test.go b/azurerm/resource_arm_iothub_test.go index 0792171ddd9c..3116bd4494e4 100644 --- a/azurerm/resource_arm_iothub_test.go +++ b/azurerm/resource_arm_iothub_test.go @@ -59,7 +59,6 @@ func TestAccAzureRMIotHub_customRoutes(t *testing.T) { Config: testAccAzureRMIotHub_customRoutes(rInt, rStr, testLocation()), Check: resource.ComposeTestCheckFunc( testCheckAzureRMIotHubExists("azurerm_iothub.test"), - ), }, }, From abe4990990e2d003b7f0ebeb9568c86500fcf4b2 Mon Sep 17 00:00:00 2001 From: Tom Harvey Date: Wed, 15 Aug 2018 15:01:57 +0200 Subject: [PATCH 9/9] `is_enabled` -> `enabled` --- website/docs/r/iothub.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/iothub.html.markdown b/website/docs/r/iothub.html.markdown index f9a16e124820..d22ccb8ef07a 100644 --- a/website/docs/r/iothub.html.markdown +++ b/website/docs/r/iothub.html.markdown @@ -128,7 +128,7 @@ A `route` block supports the following: * `endpoint_names` - (Required) The list of endpoints to which messages that satisfy the condition are routed. -* `is_enabled` - (Required) Used to specify whether a route is enabled. +* `enabled` - (Required) Used to specify whether a route is enabled. ## Attributes Reference