diff --git a/internal/services/iothub/iothub_resource.go b/internal/services/iothub/iothub_resource.go index fb8b7d1541e2..d3ffb8dcf01f 100644 --- a/internal/services/iothub/iothub_resource.go +++ b/internal/services/iothub/iothub_resource.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" @@ -70,9 +71,9 @@ func suppressWhenAny(fs ...pluginsdk.SchemaDiffSuppressFunc) pluginsdk.SchemaDif func resourceIotHub() *pluginsdk.Resource { return &pluginsdk.Resource{ - Create: resourceIotHubCreateUpdate, + Create: resourceIotHubCreate, Read: resourceIotHubRead, - Update: resourceIotHubCreateUpdate, + Update: resourceIotHubUpdate, Delete: resourceIotHubDelete, SchemaVersion: 1, @@ -661,7 +662,7 @@ func resourceIotHub() *pluginsdk.Resource { } } -func resourceIotHubCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { +func resourceIotHubCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).IoTHub.ResourceClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -683,18 +684,15 @@ func resourceIotHubCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) err if !utils.ResponseWasNotFound(existing.Response) { return tf.ImportAsExistsError("azurerm_iothub", id.ID()) } - } - - res, err := client.CheckNameAvailability(ctx, devices.OperationInputs{ - Name: &id.Name, - }) - if err != nil { - return fmt.Errorf("An error occurred checking if the IoTHub name was unique: %+v", err) - } + res, err := client.CheckNameAvailability(ctx, devices.OperationInputs{Name: &id.Name}) + if err != nil { + return fmt.Errorf("An error occurred checking if the IoTHub name was unique: %+v", err) + } - if !*res.NameAvailable { - if _, err = client.Get(ctx, id.ResourceGroup, id.Name); err != nil { - return fmt.Errorf("An IoTHub already exists with the name %q - please choose an alternate name: %s", id.Name, string(res.Reason)) + if !*res.NameAvailable { + if _, err = client.Get(ctx, id.ResourceGroup, id.Name); err == nil { + return fmt.Errorf("An IoTHub already exists with the name %q - please choose an alternate name: %s", id.Name, string(res.Reason)) + } } } @@ -720,6 +718,7 @@ func resourceIotHubCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) err } if _, ok := d.GetOk("endpoint"); ok { + var err error routingProperties.Endpoints, err = expandIoTHubEndpoints(d, subscriptionId) if err != nil { return fmt.Errorf("expanding `endpoint`: %+v", err) @@ -793,11 +792,183 @@ func resourceIotHubCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) err future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, props, "") if err != nil { - return fmt.Errorf("creating/updating %s: %+v", id, err) + return fmt.Errorf("creating %s: %+v", id, err) } if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for creation/update of %q: %+v", id, err) + return fmt.Errorf("waiting for creation of %q: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceIotHubRead(d, meta) +} + +func resourceIotHubUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).IoTHub.ResourceClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + + id, err := parse.IotHubID(d.Id()) + if err != nil { + return fmt.Errorf("parsing %s: %+v", id, err) + } + + locks.ByName(id.Name, IothubResourceName) + defer locks.UnlockByName(id.Name, IothubResourceName) + + iothub, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("reading %s: %+v", id, err) + } + + if iothub.Properties == nil { + return fmt.Errorf("reading %s: properties was nil", id) + } + + prop := *iothub.Properties + + if d.HasChange("sku") { + iothub.Sku = expandIoTHubSku(d) + } + + if d.HasChange("identity") { + identity, err := expandIotHubIdentity(d.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + iothub.Identity = identity + } + + if d.HasChange("tags") { + iothub.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) + } + + if d.HasChange("route") { + if prop.Routing == nil { + prop.Routing = &devices.RoutingProperties{} + } + prop.Routing.Routes = expandIoTHubRoutes(d) + } + + if d.HasChange("enrichment") { + if prop.Routing == nil { + prop.Routing = &devices.RoutingProperties{} + } + prop.Routing.Enrichments = expandIoTHubEnrichments(d) + } + + if d.HasChange("fallback_route") { + if prop.Routing == nil { + prop.Routing = &devices.RoutingProperties{} + } + if _, ok := d.GetOk("fallback_route"); ok { + prop.Routing.FallbackRoute = expandIoTHubFallbackRoute(d) + } else { + prop.Routing.FallbackRoute = &devices.FallbackRouteProperties{ + Source: utils.String(string(devices.RoutingSourceDeviceMessages)), + Condition: utils.String("true"), + EndpointNames: &[]string{"events"}, + IsEnabled: utils.Bool(true), + } + } + } + + if d.HasChange("endpoint") { + if prop.Routing == nil { + prop.Routing = &devices.RoutingProperties{} + } + prop.Routing.Endpoints, err = expandIoTHubEndpoints(d, subscriptionId) + if err != nil { + return fmt.Errorf("expanding `endpoint`: %+v", err) + } + } + + if d.HasChange("file_upload") { + storageEndpoints, messagingEndpoints, enableFileUploadNotifications, err := expandIoTHubFileUpload(d) + if err != nil { + return fmt.Errorf("expanding `file_upload`: %+v", err) + } + prop.StorageEndpoints = storageEndpoints + prop.MessagingEndpoints = messagingEndpoints + prop.EnableFileUploadNotifications = &enableFileUploadNotifications + } + + if d.HasChange("cloud_to_device") { + cloudToDeviceProperties := &devices.CloudToDeviceProperties{} + if _, ok := d.GetOk("cloud_to_device"); ok { + cloudToDeviceProperties = expandIoTHubCloudToDevice(d) + } + prop.CloudToDevice = cloudToDeviceProperties + } + + if d.HasChange("network_rule_set") { + if _, ok := d.GetOk("network_rule_set"); ok { + prop.NetworkRuleSets = expandNetworkRuleSetProperties(d) + } else { + prop.NetworkRuleSets = &devices.NetworkRuleSetProperties{} + } + } + + if d.HasChange("public_network_access_enabled") { + // nolint staticcheck + if v, ok := d.GetOkExists("public_network_access_enabled"); ok { + enabled := devices.PublicNetworkAccessDisabled + if v.(bool) { + enabled = devices.PublicNetworkAccessEnabled + } + prop.PublicNetworkAccess = enabled + } + } + + if d.HasChange("event_hub_retention_in_days") { + retention, retentionOk := d.GetOk("event_hub_retention_in_days") + if prop.EventHubEndpoints == nil { + prop.EventHubEndpoints = make(map[string]*devices.EventHubProperties) + } + eh, ok := prop.EventHubEndpoints["events"] + if !ok { + prop.EventHubEndpoints["events"] = &devices.EventHubProperties{} + } + eh.RetentionTimeInDays = nil + if retentionOk { + eh.RetentionTimeInDays = pointer.To(int64(retention.(int))) + } + } + + if d.HasChange("event_hub_partition_count") { + partition, partitionOk := d.GetOk("event_hub_partition_count") + if prop.EventHubEndpoints == nil { + prop.EventHubEndpoints = make(map[string]*devices.EventHubProperties) + } + if eh, ok := prop.EventHubEndpoints["events"]; ok { + eh.PartitionCount = nil + if partitionOk { + eh.PartitionCount = pointer.To(int32(partition.(int))) + } + } + } + + if d.HasChange("local_authentication_enabled") { + prop.DisableLocalAuth = pointer.To(!d.Get("local_authentication_enabled").(bool)) + } + + if d.HasChange("min_tls_version") { + prop.MinTLSVersion = nil + if v, ok := d.GetOk("min_tls_version"); ok { + prop.MinTLSVersion = pointer.To(v.(string)) + } + } + + iothub.Properties = &prop + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, iothub, "") + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for update of %q: %+v", id, err) } d.SetId(id.ID()) diff --git a/internal/services/iothub/iothub_resource_test.go b/internal/services/iothub/iothub_resource_test.go index a93cdaf8cb7c..70d4e9ce8d02 100644 --- a/internal/services/iothub/iothub_resource_test.go +++ b/internal/services/iothub/iothub_resource_test.go @@ -521,6 +521,28 @@ func TestAccIotHub_endpointAuthenticationTypeUpdate(t *testing.T) { }) } +func TestAccIotHub_cosmosDBRouteUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_iothub", "test") + r := IotHubResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.updateWithCosmosDBRoute(data, false), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.updateWithCosmosDBRoute(data, true), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t IotHubResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.IotHubID(state.ID) if err != nil { @@ -2082,3 +2104,97 @@ resource "azurerm_role_assignment" "test_azure_event_hubs_data_sender_system" { } `, data.RandomInteger, data.Locations.Primary) } + +func (IotHubResource) updateWithCosmosDBRoute(data acceptance.TestData, update bool) string { + tagsBlock := ` + tags = { + test = "1" + } + ` + if !update { + tagsBlock = "" + } + + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "iothub" { + name = "acctest-iothub-%[1]d" + location = "eastus" +} + +resource "azurerm_resource_group" "endpoint" { + name = "acctest-iothub-db-%[1]d" + location = "eastus" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-ca-%[1]d" + location = azurerm_resource_group.endpoint.location + resource_group_name = azurerm_resource_group.endpoint.name + offer_type = "Standard" + kind = "GlobalDocumentDB" + + consistency_policy { + consistency_level = "Strong" + } + + geo_location { + location = azurerm_resource_group.endpoint.location + failover_priority = 0 + } +} + +resource "azurerm_cosmosdb_sql_database" "test" { + name = "acctest-%[1]d" + resource_group_name = azurerm_cosmosdb_account.test.resource_group_name + account_name = azurerm_cosmosdb_account.test.name +} + +resource "azurerm_cosmosdb_sql_container" "test" { + name = "acctest-%[1]d" + resource_group_name = azurerm_cosmosdb_account.test.resource_group_name + account_name = azurerm_cosmosdb_account.test.name + database_name = azurerm_cosmosdb_sql_database.test.name + partition_key_path = "/definition/id" +} + +resource "azurerm_iothub" "test" { + name = "acc-%[1]d" + resource_group_name = azurerm_resource_group.iothub.name + location = azurerm_resource_group.iothub.location + + sku { + name = "B1" + capacity = "1" + } + + %[2]s +} + +resource "azurerm_iothub_endpoint_cosmosdb_account" "test" { + name = "acct-%[1]d" + resource_group_name = azurerm_resource_group.endpoint.name + iothub_id = azurerm_iothub.test.id + container_name = azurerm_cosmosdb_sql_container.test.name + database_name = azurerm_cosmosdb_sql_database.test.name + endpoint_uri = azurerm_cosmosdb_account.test.endpoint + primary_key = azurerm_cosmosdb_account.test.primary_key + secondary_key = azurerm_cosmosdb_account.test.secondary_key +} + +resource "azurerm_iothub_route" "test" { + resource_group_name = azurerm_resource_group.iothub.name + iothub_name = azurerm_iothub.test.name + name = "acctest-%[1]d" + + source = "DeviceMessages" + condition = "true" + endpoint_names = [azurerm_iothub_endpoint_cosmosdb_account.test.name] + enabled = false + depends_on = [azurerm_iothub_endpoint_cosmosdb_account.test] +} +`, data.RandomInteger, tagsBlock) +}