diff --git a/azurerm/resource_arm_iot_dps.go b/azurerm/resource_arm_iot_dps.go index 01a8be21af1b..083329b1407d 100644 --- a/azurerm/resource_arm_iot_dps.go +++ b/azurerm/resource_arm_iot_dps.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "regexp" "strconv" "time" @@ -84,6 +85,50 @@ func resourceArmIotDPS() *schema.Resource { }, }, + "linked_hub": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "connection_string": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + ForceNew: true, + // Azure returns the key as ****. We'll suppress that here. + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + secretKeyRegex := regexp.MustCompile("(SharedAccessKey)=[^;]+") + maskedNew := secretKeyRegex.ReplaceAllString(new, "$1=****") + return (new == d.Get(k).(string)) && (maskedNew == old) + }, + Sensitive: true, + }, + "location": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + StateFunc: azure.NormalizeLocation, + ForceNew: true, + }, + "apply_allocation_policy": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "allocation_weight": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntBetween(0, 1000), + }, + "hostname": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "tags": tagsSchema(), }, } @@ -110,11 +155,13 @@ func resourceArmIotDPSCreateOrUpdate(d *schema.ResourceData, meta interface{}) e } iotdps := iothub.ProvisioningServiceDescription{ - Location: utils.String(d.Get("location").(string)), - Name: utils.String(name), - Sku: expandIoTDPSSku(d), - Properties: &iothub.IotDpsPropertiesDescription{}, - Tags: expandTags(d.Get("tags").(map[string]interface{})), + Location: utils.String(d.Get("location").(string)), + Name: utils.String(name), + Sku: expandIoTDPSSku(d), + Properties: &iothub.IotDpsPropertiesDescription{ + IotHubs: expandIoTDPSIoTHubs(d.Get("linked_hub").([]interface{})), + }, + Tags: expandTags(d.Get("tags").(map[string]interface{})), } future, err := client.CreateOrUpdate(ctx, resourceGroup, name, iotdps) @@ -170,6 +217,13 @@ func resourceArmIotDPSRead(d *schema.ResourceData, meta interface{}) error { if err := d.Set("sku", sku); err != nil { return fmt.Errorf("Error setting `sku`: %+v", err) } + + if props := resp.Properties; props != nil { + if err := d.Set("linked_hub", flattenIoTDPSLinkedHub(props.IotHubs)); err != nil { + return fmt.Errorf("Error setting `linked_hub`: %+v", err) + } + } + flattenAndSetTags(d, resp.Tags) return nil @@ -244,6 +298,24 @@ func expandIoTDPSSku(d *schema.ResourceData) *iothub.IotDpsSkuInfo { } } +func expandIoTDPSIoTHubs(input []interface{}) *[]iothub.DefinitionDescription { + linkedHubs := make([]iothub.DefinitionDescription, 0) + + for _, attr := range input { + linkedHubConfig := attr.(map[string]interface{}) + linkedHub := iothub.DefinitionDescription{ + ConnectionString: utils.String(linkedHubConfig["connection_string"].(string)), + AllocationWeight: utils.Int32(int32(linkedHubConfig["allocation_weight"].(int))), + ApplyAllocationPolicy: utils.Bool((linkedHubConfig["apply_allocation_policy"].(bool))), + Location: utils.String(linkedHubConfig["location"].(string)), + } + + linkedHubs = append(linkedHubs, linkedHub) + } + + return &linkedHubs +} + func flattenIoTDPSSku(input *iothub.IotDpsSkuInfo) []interface{} { output := make(map[string]interface{}) @@ -255,3 +327,34 @@ func flattenIoTDPSSku(input *iothub.IotDpsSkuInfo) []interface{} { return []interface{}{output} } + +func flattenIoTDPSLinkedHub(input *[]iothub.DefinitionDescription) []interface{} { + linkedHubs := make([]interface{}, 0) + if input == nil { + return linkedHubs + } + + for _, attr := range *input { + linkedHub := make(map[string]interface{}) + + if attr.Name != nil { + linkedHub["hostname"] = *attr.Name + } + if attr.ApplyAllocationPolicy != nil { + linkedHub["apply_allocation_policy"] = *attr.ApplyAllocationPolicy + } + if attr.AllocationWeight != nil { + linkedHub["allocation_weight"] = *attr.AllocationWeight + } + if attr.ConnectionString != nil { + linkedHub["connection_string"] = *attr.ConnectionString + } + if attr.Location != nil { + linkedHub["location"] = *attr.Location + } + + linkedHubs = append(linkedHubs, linkedHub) + } + + return linkedHubs +} diff --git a/azurerm/resource_arm_iot_dps_test.go b/azurerm/resource_arm_iot_dps_test.go index 6315b89aee14..55ed954c35f8 100644 --- a/azurerm/resource_arm_iot_dps_test.go +++ b/azurerm/resource_arm_iot_dps_test.go @@ -98,6 +98,41 @@ func TestAccAzureRMIotDPS_update(t *testing.T) { }) } +func TestAccAzureRMIotDPS_linkedHubs(t *testing.T) { + resourceName := "azurerm_iot_dps.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotDPSDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotDPS_linkedHubs(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotDPSExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAzureRMIotDPS_linkedHubsUpdated(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotDPSExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testCheckAzureRMIotDPSDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*ArmClient).iothub.DPSResourceClient ctx := testAccProvider.Meta().(*ArmClient).StopContext @@ -216,3 +251,63 @@ resource "azurerm_iot_dps" "test" { } `, rInt, location, rInt) } + +func testAccAzureRMIotDPS_linkedHubs(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_iot_dps" "test" { + name = "acctestIoTDPS-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + sku { + name = "S1" + tier = "Standard" + capacity = "1" + } + + linked_hub { + connection_string = "HostName=test.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=booo" + location = "${azurerm_resource_group.test.location}" + allocation_weight = 15 + apply_allocation_policy = true + } + + linked_hub { + connection_string = "HostName=test2.azure-devices.net;SharedAccessKeyName=iothubowner2;SharedAccessKey=key2" + location = "${azurerm_resource_group.test.location}" + } +} +`, rInt, location, rInt) +} + +func testAccAzureRMIotDPS_linkedHubsUpdated(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_iot_dps" "test" { + name = "acctestIoTDPS-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + sku { + name = "S1" + tier = "Standard" + capacity = "1" + } + + linked_hub { + connection_string = "HostName=test.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=booo" + location = "${azurerm_resource_group.test.location}" + allocation_weight = 150 + } +} +`, rInt, location, rInt) +} diff --git a/website/docs/r/iot_dps.html.markdown b/website/docs/r/iot_dps.html.markdown index 0b013c5f263b..6be2e507ba2b 100644 --- a/website/docs/r/iot_dps.html.markdown +++ b/website/docs/r/iot_dps.html.markdown @@ -43,6 +43,8 @@ The following arguments are supported: * `sku` - (Required) A `sku` block as defined below. +* `linked_hub` - (Optional) A `linked_hub` block as defined below. + * `tags` - (Optional) A mapping of tags to assign to the resource. --- @@ -55,6 +57,20 @@ A `sku` block supports the following: * `capacity` - (Required) The number of provisioned IoT Device Provisioning Service units. +--- + +A `linked_hub` block supports the following: + +* `connection_string` - (Required) The connection string to connect to the IoT Hub. Changing this forces a new resource. + +* `location` - (Required) The location of the IoT hub. Changing this forces a new resource. + +* `apply_application_policy` - (Optional) Determines whether to apply application policies to the IoT Hub. Defaults to false. + +* `allocation_weight` - (Optional) The weight applied to the IoT Hub. Defaults to 0. + +* `hostname` - (Computed) The IoT Hub hostname. + ## Attributes Reference The following attributes are exported: