From bd70ef555d58647816d226955e4e6b6496317fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maksymilian=20Bogu=C5=84?= Date: Mon, 17 Jun 2019 10:27:50 +0200 Subject: [PATCH] iot_fallback_route resource --- azurerm/resource_arm_iothub.go | 17 +- azurerm/resource_arm_iothub_fallback_route.go | 207 +++++++++++++++++ ...resource_arm_iothub_fallback_route_test.go | 210 ++++++++++++++++++ .../r/iothub_fallback_route.html.markdown | 108 +++++++++ 4 files changed, 535 insertions(+), 7 deletions(-) create mode 100644 azurerm/resource_arm_iothub_fallback_route.go create mode 100644 azurerm/resource_arm_iothub_fallback_route_test.go create mode 100644 website/docs/r/iothub_fallback_route.html.markdown diff --git a/azurerm/resource_arm_iothub.go b/azurerm/resource_arm_iothub.go index 39fa7f7342831..f0c5682373ff2 100755 --- a/azurerm/resource_arm_iothub.go +++ b/azurerm/resource_arm_iothub.go @@ -287,10 +287,11 @@ func resourceArmIotHub() *schema.Resource { }, "fallback_route": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Computed: true, + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Computed: true, + Deprecated: "Use the `azurerm_iothub_fallback_route` resource instead.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "source": { @@ -405,11 +406,13 @@ func resourceArmIotHubCreateUpdate(d *schema.ResourceData, meta interface{}) err skuInfo := expandIoTHubSku(d) tags := d.Get("tags").(map[string]interface{}) - routingProperties := devices.RoutingProperties{ - FallbackRoute: expandIoTHubFallbackRoute(d), - } + routingProperties := devices.RoutingProperties{} if _, ok := d.GetOk("route"); ok { + routingProperties.FallbackRoute = expandIoTHubFallbackRoute(d) + } + + if _, ok := d.GetOk("fallback_route"); ok { routingProperties.Routes = expandIoTHubRoutes(d) } diff --git a/azurerm/resource_arm_iothub_fallback_route.go b/azurerm/resource_arm_iothub_fallback_route.go new file mode 100644 index 0000000000000..135ff3c4b6d12 --- /dev/null +++ b/azurerm/resource_arm_iothub_fallback_route.go @@ -0,0 +1,207 @@ +package azurerm + +import ( + "fmt" + + "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/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmIotHubFallbackRoute() *schema.Resource { + return &schema.Resource{ + Create: resourceArmIotHubFallbackRouteCreateUpdate, + Read: resourceArmIotHubFallbackRouteRead, + Update: resourceArmIotHubFallbackRouteCreateUpdate, + Delete: resourceArmIotHubFallbackRouteDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "resource_group_name": azure.SchemaResourceGroupName(), + + "iothub_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubName, + }, + + "source": { + Type: schema.TypeString, + Optional: true, + Default: "DeviceMessages", + ValidateFunc: validation.StringInSlice([]string{ + "DeviceJobLifecycleEvents", + "DeviceLifecycleEvents", + "DeviceMessages", + "Invalid", + "TwinChangeEvents", + }, 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", + }, + "endpoint_names": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(0, 64), + }, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceArmIotHubFallbackRouteCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + 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) + } + + routing := iothub.Properties.Routing + + if routing == nil { + routing = &devices.RoutingProperties{} + } + + resourceId := fmt.Sprintf("%s/FallbackRoute/defined", *iothub.ID) + + if d.IsNewResource() && routing.FallbackRoute != nil && requireResourcesToBeImported { + return tf.ImportAsExistsError("azurerm_iothub_fallback_route", resourceId) + } + + source := d.Get("source").(string) + condition := d.Get("condition").(string) + endpointNamesRaw := d.Get("endpoint_names").([]interface{}) + isEnabled := d.Get("enabled").(bool) + + fallbackRoute := devices.FallbackRouteProperties{ + Source: &source, + Condition: &condition, + EndpointNames: utils.ExpandStringSlice(endpointNamesRaw), + IsEnabled: &isEnabled, + } + + routing.FallbackRoute = &fallbackRoute + + 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 resourceArmIotHubFallbackRouteRead(d, meta) +} + +func resourceArmIotHubFallbackRouteRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubRouteId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubRouteId.ResourceGroup + iothubName := parsedIothubRouteId.Path["IotHubs"] + + 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("iothub_name", iothubName) + d.Set("resource_group_name", resourceGroup) + + if iothub.Properties == nil || iothub.Properties.Routing == nil { + return nil + } + + route := iothub.Properties.Routing.FallbackRoute + + d.Set("source", route.Source) + d.Set("condition", route.Condition) + d.Set("enabled", route.IsEnabled) + d.Set("endpoint_names", route.EndpointNames) + + return nil +} + +func resourceArmIotHubFallbackRouteDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).iothub.ResourceClient + ctx := meta.(*ArmClient).StopContext + + parsedIothubRouteId, err := parseAzureResourceID(d.Id()) + + if err != nil { + return err + } + + resourceGroup := parsedIothubRouteId.ResourceGroup + iothubName := parsedIothubRouteId.Path["IotHubs"] + + 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 { + return nil + } + + iothub.Properties.Routing.FallbackRoute = nil + + future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "") + if err != nil { + return fmt.Errorf("Error updating IotHub %q (Resource Group %q) with Fallback Route: %+v", iothubName, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for IotHub %q (Resource Group %q) to finish updating Fallback Route: %+v", iothubName, resourceGroup, err) + } + + return nil +} diff --git a/azurerm/resource_arm_iothub_fallback_route_test.go b/azurerm/resource_arm_iothub_fallback_route_test.go new file mode 100644 index 0000000000000..c665623c5a522 --- /dev/null +++ b/azurerm/resource_arm_iothub_fallback_route_test.go @@ -0,0 +1,210 @@ +package azurerm + +import ( + "fmt" + "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 TestAccAzureRMIotHubFallbackRoute_basic(t *testing.T) { + resourceName := "azurerm_iothub_fallback_route.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubFallbackRoute_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubFallbackRouteExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMIotHubFallbackRoute_requiresImport(t *testing.T) { + if !requireResourcesToBeImported { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_iothub_fallback_route.test" + rInt := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubFallbackRoute_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubFallbackRouteExists(resourceName), + ), + }, + { + Config: testAccAzureRMIotHubFallbackRoute_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_iothub_fallback_route"), + }, + }, + }) +} + +func testCheckAzureRMIotHubFallbackRouteDestroy(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_fallback_route" { + continue + } + + 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 { + return nil + } + if iothub.Properties.Routing.FallbackRoute != nil { + return fmt.Errorf("Bad: fallback route still exists on IoTHb %s", iothubName) + } + } + return nil +} + +func testCheckAzureRMIotHubFallbackRouteExists(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"] + 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.FallbackRoute == nil { + return fmt.Errorf("Bad: No fallbackroute defined for IotHub %s", iothubName) + } + + return nil + } +} + +func testAccAzureRMIotHubFallbackRoute_requiresImport(rInt int, location string) string { + template := testAccAzureRMIotHub_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_iothub_fallback_route" "import" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + + source = "DeviceMessages" + condition = "true" + endpoint_names = ["${azurerm_iothub_endpoint_storage_container.test.name}"] + enabled = true +} +`, template) +} + +func testAccAzureRMIotHubFallbackRoute_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 = "acctestsa%[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 = "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 = "acctestIoTHub-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + sku { + name = "S1" + tier = "Standard" + 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" + + connection_string = "${azurerm_storage_account.test.primary_blob_connection_string}" + batch_frequency_in_seconds = 60 + max_chunk_size_in_bytes = 10485760 + container_name = "${azurerm_storage_container.test.name}" + encoding = "Avro" + file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}" +} + +resource "azurerm_iothub_fallback_route" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + iothub_name = "${azurerm_iothub.test.name}" + + source = "DeviceMessages" + condition = "true" + endpoint_names = ["${azurerm_iothub_endpoint_storage_container.test.name}"] + enabled = true +} + +`, rInt, location) +} diff --git a/website/docs/r/iothub_fallback_route.html.markdown b/website/docs/r/iothub_fallback_route.html.markdown new file mode 100644 index 0000000000000..8f90d654606b6 --- /dev/null +++ b/website/docs/r/iothub_fallback_route.html.markdown @@ -0,0 +1,108 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_iothub_fallback_route" +sidebar_current: "docs-azurerm-resource-messaging-iothub-fallback-route-x" +description: |- + Manages an IotHub Fallback Route +--- + +# azurerm_iothub_fallback_route + +Manages an IotHub Fallback Route + +~> **NOTE:** Fallback route can be defined either directly on the `azurerm_iothub` resource, or using the `azurerm_iothub_fallback_route` resource - but the two cannot be used together. If both are used against the same IoTHub, spurious changes will occur. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example" + location = "West US" +} + +resource "azurerm_storage_account" "example" { + name = "examplestorageaccount" + 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 = "example" + 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 = "exampleIothub" + resource_group_name = "${azurerm_resource_group.example.name}" + location = "${azurerm_resource_group.example.location}" + + sku { + name = "S1" + tier = "Standard" + capacity = "1" + } + + tags = { + purpose = "testing" + } +} + +resource "azurerm_iothub_endpoint_storage_container" "example" { + resource_group_name = "${azurerm_resource_group.example.name}" + iothub_name = "${azurerm_iothub.example.name}" + name = "example" + + connection_string = "${azurerm_storage_account.example.primary_blob_connection_string}" + batch_frequency_in_seconds = 60 + max_chunk_size_in_bytes = 10485760 + container_name = "${azurerm_storage_container.example.name}" + encoding = "Avro" + file_name_format = "{iothub}/{partition}_{YYYY}_{MM}_{DD}_{HH}_{mm}" +} + +resource "azurerm_iothub_fallback_route" "example" { + resource_group_name = "${azurerm_resource_group.example.name}" + iothub_name = "${azurerm_iothub.example.name}" + + source = "DeviceMessages" + condition = "true" + endpoint_names = ["${azurerm_iothub_endpoint_storage_container.example.name}"] + enabled = true +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `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 Fallback Route belongs. Changing this forces a new resource to be created. + +* `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 endpoints to which messages that satisfy the condition are routed. Currently only 1 endpoint is allowed. + +* `enabled` - (Required) Used to specify whether the fallback route is enabled. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the IoTHub FallbackRoute. + +## Import + +IoTHub Fallback Route can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_iothub_route.route1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Devices/IotHubs/hub1/FallbackRoute/default +``` +~> **NOTE:** As there may only be a single fallback route per IoTHub, the id always ends with `/FallbackRoute/default`.