diff --git a/azurerm/provider.go b/azurerm/provider.go index 8da231adbe2b..3c3cf332565c 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -301,6 +301,8 @@ func Provider() terraform.ResourceProvider { "azurerm_image": resourceArmImage(), "azurerm_iot_dps": resourceArmIotDPS(), "azurerm_iot_dps_certificate": resourceArmIotDPSCertificate(), + "azurerm_iothub_dps": resourceArmIotHubDPS(), + "azurerm_iothub_dps_certificate": resourceArmIotHubDPSCertificate(), "azurerm_iothub_consumer_group": resourceArmIotHubConsumerGroup(), "azurerm_iothub": resourceArmIotHub(), "azurerm_iothub_endpoint_eventhub": resourceArmIotHubEndpointEventHub(), diff --git a/azurerm/resource_arm_iot_dps.go b/azurerm/resource_arm_iot_dps.go index 25740c2db5cb..eef5db2e6260 100644 --- a/azurerm/resource_arm_iot_dps.go +++ b/azurerm/resource_arm_iot_dps.go @@ -34,6 +34,12 @@ func resourceArmIotDPS() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + DeprecationMessage: `The 'azurerm_iot_dps' resource is deprecated in favour of the renamed version 'azurerm_iothub_dps'. + +Information on migrating to the renamed resource can be found here: https://terraform.io/docs/providers/azurerm/guides/migrating-between-renamed-resources.html + +As such the existing 'azurerm_iot_dps' resource is deprecated and will be removed in the next major version of the AzureRM Provider (2.0). +`, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), diff --git a/azurerm/resource_arm_iot_dps_cerificate.go b/azurerm/resource_arm_iot_dps_certificate.go similarity index 93% rename from azurerm/resource_arm_iot_dps_cerificate.go rename to azurerm/resource_arm_iot_dps_certificate.go index 5a7444c1a5b3..a22ac872b5c9 100644 --- a/azurerm/resource_arm_iot_dps_cerificate.go +++ b/azurerm/resource_arm_iot_dps_certificate.go @@ -24,6 +24,12 @@ func resourceArmIotDPSCertificate() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + DeprecationMessage: `The 'azurerm_iot_dps_certificate' resource is deprecated in favour of the renamed version 'azurerm_iothub_dps_certificate'. + +Information on migrating to the renamed resource can be found here: https://terraform.io/docs/providers/azurerm/guides/migrating-between-renamed-resources.html + +As such the existing 'azurerm_iot_dps_certificate' resource is deprecated and will be removed in the next major version of the AzureRM Provider (2.0). +`, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), diff --git a/azurerm/resource_arm_iothub_dps.go b/azurerm/resource_arm_iothub_dps.go new file mode 100644 index 000000000000..411e4a5a08e6 --- /dev/null +++ b/azurerm/resource_arm_iothub_dps.go @@ -0,0 +1,402 @@ +package azurerm + +import ( + "context" + "fmt" + "log" + "regexp" + "strconv" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices" + "github.com/Azure/azure-sdk-for-go/services/provisioningservices/mgmt/2018-01-22/iothub" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "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/response" + "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/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmIotHubDPS() *schema.Resource { + return &schema.Resource{ + Create: resourceArmIotHubDPSCreateUpdate, + Read: resourceArmIotHubDPSRead, + Update: resourceArmIotHubDPSCreateUpdate, + Delete: resourceArmIotHubDPSDelete, + + Importer: &schema.ResourceImporter{ + 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, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), // azure.SchemaResourceGroupNameDiffSuppress(), + + "location": azure.SchemaLocation(), + + "sku": { + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(devices.B1), + string(devices.B2), + string(devices.B3), + string(devices.F1), + string(devices.S1), + string(devices.S2), + string(devices.S3), + }, true), + }, + + "tier": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(devices.Basic), + string(devices.Free), + string(devices.Standard), + }, true), + }, + + "capacity": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + + "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, + }, + }, + }, + }, + + "allocation_policy": { + Type: schema.TypeString, + Computed: true, + }, + + "device_provisioning_host_name": { + Type: schema.TypeString, + Computed: true, + }, + + "id_scope": { + Type: schema.TypeString, + Computed: true, + }, + + "service_operations_host_name": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceArmIotHubDPSCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).IoTHub.DPSResourceClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, name, resourceGroup) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing IoT Device Provisioning Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_iothub_dps", *existing.ID) + } + } + + iotdps := iothub.ProvisioningServiceDescription{ + Location: utils.String(d.Get("location").(string)), + Name: utils.String(name), + Sku: expandIoTHubDPSSku(d), + Properties: &iothub.IotDpsPropertiesDescription{ + IotHubs: expandIoTHubDPSIoTHubs(d.Get("linked_hub").([]interface{})), + }, + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, name, iotdps) + if err != nil { + return fmt.Errorf("Error creating/updating IoT Device Provisioning Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for the completion of the creating/updating of IoT Device Provisioning Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + resp, err := client.Get(ctx, name, resourceGroup) + if err != nil { + return fmt.Errorf("Error retrieving IoT Device Provisioning Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if resp.ID == nil { + return fmt.Errorf("Cannot read IoT Device Provisioning Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.SetId(*resp.ID) + + return resourceArmIotHubDPSRead(d, meta) +} + +func resourceArmIotHubDPSRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).IoTHub.DPSResourceClient + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["provisioningServices"] + + resp, err := client.Get(ctx, name, resourceGroup) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + + return fmt.Errorf("Error retrieving IoT Device Provisioning Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + sku := flattenIoTHubDPSSku(resp.Sku) + 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", flattenIoTHubDPSLinkedHub(props.IotHubs)); err != nil { + return fmt.Errorf("Error setting `linked_hub`: %+v", err) + } + + d.Set("service_operations_host_name", props.ServiceOperationsHostName) + d.Set("device_provisioning_host_name", props.DeviceProvisioningHostName) + d.Set("id_scope", props.IDScope) + d.Set("allocation_policy", props.AllocationPolicy) + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmIotHubDPSDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).IoTHub.DPSResourceClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["provisioningServices"] + + future, err := client.Delete(ctx, name, resourceGroup) + if err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error deleting IoT Device Provisioning Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + return waitForIotHubDPSToBeDeleted(ctx, client, resourceGroup, name, d) +} + +func waitForIotHubDPSToBeDeleted(ctx context.Context, client *iothub.IotDpsResourceClient, resourceGroup, name string, d *schema.ResourceData) error { + // we can't use the Waiter here since the API returns a 404 once it's deleted which is considered a polling status code.. + log.Printf("[DEBUG] Waiting for IoT Device Provisioning Service %q (Resource Group %q) to be deleted", name, resourceGroup) + stateConf := &resource.StateChangeConf{ + Pending: []string{"200"}, + Target: []string{"404"}, + Refresh: iothubdpsStateStatusCodeRefreshFunc(ctx, client, resourceGroup, name), + } + + if features.SupportsCustomTimeouts() { + stateConf.Timeout = d.Timeout(schema.TimeoutDelete) + } else { + stateConf.Timeout = 40 * time.Minute + } + + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for IoT Device Provisioning Service %q (Resource Group %q) to be deleted: %+v", name, resourceGroup, err) + } + + return nil +} + +func iothubdpsStateStatusCodeRefreshFunc(ctx context.Context, client *iothub.IotDpsResourceClient, resourceGroup, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.Get(ctx, name, resourceGroup) + + log.Printf("Retrieving IoT Device Provisioning Service %q (Resource Group %q) returned Status %d", resourceGroup, name, res.StatusCode) + + if err != nil { + if utils.ResponseWasNotFound(res.Response) { + return res, strconv.Itoa(res.StatusCode), nil + } + return nil, "", fmt.Errorf("Error polling for the status of the IoT Device Provisioning Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + return res, strconv.Itoa(res.StatusCode), nil + } +} + +func expandIoTHubDPSSku(d *schema.ResourceData) *iothub.IotDpsSkuInfo { + skuList := d.Get("sku").([]interface{}) + skuMap := skuList[0].(map[string]interface{}) + capacity := int64(skuMap["capacity"].(int)) + + name := skuMap["name"].(string) + tier := skuMap["tier"].(string) + + return &iothub.IotDpsSkuInfo{ + Name: iothub.IotDpsSku(name), + Tier: utils.String(tier), + Capacity: utils.Int64(capacity), + } +} + +func expandIoTHubDPSIoTHubs(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 flattenIoTHubDPSSku(input *iothub.IotDpsSkuInfo) []interface{} { + output := make(map[string]interface{}) + + output["name"] = string(input.Name) + output["tier"] = input.Tier + if capacity := input.Capacity; capacity != nil { + output["capacity"] = int(*capacity) + } + + return []interface{}{output} +} + +func flattenIoTHubDPSLinkedHub(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_iothub_dps_certificate.go b/azurerm/resource_arm_iothub_dps_certificate.go new file mode 100644 index 000000000000..fc3f72ebc298 --- /dev/null +++ b/azurerm/resource_arm_iothub_dps_certificate.go @@ -0,0 +1,166 @@ +package azurerm + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/provisioningservices/mgmt/2018-01-22/iothub" + "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/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmIotHubDPSCertificate() *schema.Resource { + return &schema.Resource{ + Create: resourceArmIotHubDPSCertificateCreateUpdate, + Read: resourceArmIotHubDPSCertificateRead, + Update: resourceArmIotHubDPSCertificateCreateUpdate, + Delete: resourceArmIotHubDPSCertificateDelete, + + Importer: &schema.ResourceImporter{ + 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, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "iot_dps_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.IoTHubName, + }, + + "certificate_content": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + Sensitive: true, + }, + }, + } +} + +func resourceArmIotHubDPSCertificateCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).IoTHub.DPSCertificateClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + iotDPSName := d.Get("iot_dps_name").(string) + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, name, resourceGroup, iotDPSName, "") + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q): %+v", name, iotDPSName, resourceGroup, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_iothub_dps_certificate", *existing.ID) + } + } + + certificate := iothub.CertificateBodyDescription{ + Certificate: utils.String(d.Get("certificate_content").(string)), + } + + if _, err := client.CreateOrUpdate(ctx, resourceGroup, iotDPSName, name, certificate, ""); err != nil { + return fmt.Errorf("Error creating/updating IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q): %+v", name, iotDPSName, resourceGroup, err) + } + + resp, err := client.Get(ctx, name, resourceGroup, iotDPSName, "") + if err != nil { + return fmt.Errorf("Error retrieving IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q): %+v", name, iotDPSName, resourceGroup, err) + } + + if resp.ID == nil { + return fmt.Errorf("Cannot read IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q): %+v", name, iotDPSName, resourceGroup, err) + } + + d.SetId(*resp.ID) + + return resourceArmIotHubDPSCertificateRead(d, meta) +} + +func resourceArmIotHubDPSCertificateRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).IoTHub.DPSCertificateClient + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + iotDPSName := id.Path["provisioningServices"] + name := id.Path["certificates"] + + resp, err := client.Get(ctx, name, resourceGroup, iotDPSName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q): %+v", name, iotDPSName, resourceGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resourceGroup) + d.Set("iot_dps_name", iotDPSName) + // We are unable to set `certificate_content` since it is not returned from the API + + return nil +} + +func resourceArmIotHubDPSCertificateDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).IoTHub.DPSCertificateClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + iotDPSName := id.Path["provisioningServices"] + name := id.Path["certificates"] + + resp, err := client.Get(ctx, name, resourceGroup, iotDPSName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + return fmt.Errorf("Error retrieving IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q): %+v", name, iotDPSName, resourceGroup, err) + } + + if resp.Etag == nil { + return fmt.Errorf("Error deleting IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q) because Etag is nil", name, iotDPSName, resourceGroup) + } + + // TODO address this delete call if https://github.com/Azure/azure-rest-api-specs/pull/6311 get's merged + if _, err := client.Delete(ctx, resourceGroup, *resp.Etag, iotDPSName, name, "", nil, nil, iothub.ServerAuthentication, nil, nil, nil, ""); err != nil { + return fmt.Errorf("Error deleting IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q): %+v", name, iotDPSName, resourceGroup, err) + } + return nil +} diff --git a/azurerm/resource_arm_iothub_dps_certificate_test.go b/azurerm/resource_arm_iothub_dps_certificate_test.go new file mode 100644 index 000000000000..2675e2fcb846 --- /dev/null +++ b/azurerm/resource_arm_iothub_dps_certificate_test.go @@ -0,0 +1,242 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" +) + +func TestAccAzureRMIotHubDPSCertificate_basic(t *testing.T) { + resourceName := "azurerm_iothub_dps_certificate.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDPSCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubDPSCertificate_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSCertificateExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "certificate_content", + }, + }, + }, + }) +} + +func TestAccAzureRMIotHubDPSCertificate_requiresImport(t *testing.T) { + if !features.ShouldResourcesBeImported() { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_iothub_dps_certificate.test" + rInt := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDPSCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubDPSCertificate_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSCertificateExists(resourceName), + ), + }, + { + Config: testAccAzureRMIotHubDPSCertificate_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_iothubdps"), + }, + }, + }) +} + +func TestAccAzureRMIotHubDPSCertificate_update(t *testing.T) { + resourceName := "azurerm_iothub_dps_certificate.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDPSCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubDPSCertificate_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSCertificateExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "certificate_content", + }, + }, + { + Config: testAccAzureRMIotHubDPSCertificate_update(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSCertificateExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "certificate_content", + }, + }, + }, + }) +} + +func testCheckAzureRMIotHubDPSCertificateDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).IoTHub.DPSCertificateClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_iothub_dps_certificate" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + iotDPSName := rs.Primary.Attributes["iot_dps_name"] + + resp, err := client.Get(ctx, name, resourceGroup, iotDPSName, "") + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("IoT Device Provisioning Service Certificate %s still exists in (device provisioning service %s / resource group %s)", name, iotDPSName, resourceGroup) + } + } + return nil +} + +func testCheckAzureRMIotHubDPSCertificateExists(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) + } + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + iotDPSName := rs.Primary.Attributes["iot_dps_name"] + + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for IoT Device Provisioning Service Certificate: %s", name) + } + + client := testAccProvider.Meta().(*ArmClient).IoTHub.DPSCertificateClient + resp, err := client.Get(ctx, name, resourceGroup, iotDPSName, "") + if err != nil { + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: IoT Device Provisioning Service Certificate %q (Device Provisioning Service %q / Resource Group %q) does not exist", name, iotDPSName, resourceGroup) + } + + return fmt.Errorf("Bad: Get on iothubDPSCertificateClient: %+v", err) + } + + return nil + } +} + +func testAccAzureRMIotHubDPSCertificate_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_iothub_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" + } +} + +resource "azurerm_iothub_dps_certificate" "test" { + name = "acctestIoTDPSCertificate-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + iot_dps_name = "${azurerm_iothub_dps.test.name}" + + certificate_content = "${filebase64("testdata/batch_certificate.cer")}" +} +`, rInt, location, rInt, rInt) +} + +func testAccAzureRMIotHubDPSCertificate_requiresImport(rInt int, location string) string { + template := testAccAzureRMIotHubDPS_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_iothub_dps_certificate" "test" { + name = "${azurerm_iothub_dps_certificate.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + iot_dps_name = "${azurerm_iothub_dps.test.name}" + + certificate_content = "${filebase64("testdata/batch_certificate.cer")}" +} +`, template) +} + +func testAccAzureRMIotHubDPSCertificate_update(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_iothub_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" + } + + tags = { + purpose = "testing" + } +} + +resource "azurerm_iothub_dps_certificate" "test" { + name = "acctestIoTDPSCertificate-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + iot_dps_name = "${azurerm_iothub_dps.test.name}" + + certificate_content = "${filebase64("testdata/application_gateway_test.cer")}" +} +`, rInt, location, rInt, rInt) +} diff --git a/azurerm/resource_arm_iothub_dps_test.go b/azurerm/resource_arm_iothub_dps_test.go new file mode 100644 index 000000000000..794514ae9982 --- /dev/null +++ b/azurerm/resource_arm_iothub_dps_test.go @@ -0,0 +1,317 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" +) + +func TestAccAzureRMIotHubDPS_basic(t *testing.T) { + resourceName := "azurerm_iothub_dps.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDPSDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubDPS_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "allocation_policy"), + resource.TestCheckResourceAttrSet(resourceName, "device_provisioning_host_name"), + resource.TestCheckResourceAttrSet(resourceName, "id_scope"), + resource.TestCheckResourceAttrSet(resourceName, "service_operations_host_name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMIotHubDPS_requiresImport(t *testing.T) { + if !features.ShouldResourcesBeImported() { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_iothub_dps.test" + rInt := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotDPSDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubDPS_basic(rInt, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSExists(resourceName), + ), + }, + { + Config: testAccAzureRMIotHubDPS_requiresImport(rInt, location), + ExpectError: testRequiresImportError("azurerm_iothubdps"), + }, + }, + }) +} + +func TestAccAzureRMIotHubDPS_update(t *testing.T) { + resourceName := "azurerm_iothub_dps.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotDPSDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubDPS_basic(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAzureRMIotHubDPS_update(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMIotHubDPS_linkedHubs(t *testing.T) { + resourceName := "azurerm_iothub_dps.test" + rInt := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMIotHubDPSDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMIotHubDPS_linkedHubs(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAzureRMIotHubDPS_linkedHubsUpdated(rInt, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMIotHubDPSExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMIotHubDPSDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).IoTHub.DPSResourceClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_iothubdps" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := client.Get(ctx, resourceGroup, name) + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("IoT Device Provisioning Service %s still exists in resource group %s", name, resourceGroup) + } + } + return nil +} + +func testCheckAzureRMIotHubDPSExists(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) + } + iotdpsName := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for IoT Device Provisioning Service: %s", iotdpsName) + } + + client := testAccProvider.Meta().(*ArmClient).IoTHub.DPSResourceClient + resp, err := client.Get(ctx, iotdpsName, resourceGroup) + if err != nil { + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: IoT Device Provisioning Service %q (Resource Group %q) does not exist", iotdpsName, resourceGroup) + } + + return fmt.Errorf("Bad: Get on iothubDPSResourceClient: %+v", err) + } + + return nil + } +} + +func testAccAzureRMIotHubDPS_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_iothub_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" + } +} +`, rInt, location, rInt) +} + +func testAccAzureRMIotHubDPS_requiresImport(rInt int, location string) string { + template := testAccAzureRMIotHubDPS_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_iothub_dps" "import" { + name = "${azurerm_iothub_dps.test.name}" + resource_group_name = "${azurerm_iothub_dps.test.name}" + location = "${azurerm_iothub_dps.test.location}" + + sku { + name = "S1" + tier = "Standard" + capacity = "1" + } +} +`, template) +} + +func testAccAzureRMIotHubDPS_update(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_iothub_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" + } + + tags = { + purpose = "testing" + } +} +`, rInt, location, rInt) +} + +func testAccAzureRMIotHubDPS_linkedHubs(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_iothub_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 testAccAzureRMIotHubDPS_linkedHubsUpdated(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_iothub_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/azurerm.erb b/website/azurerm.erb index a725c344ce83..b49c77f0780b 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -1401,11 +1401,11 @@