Skip to content

Commit

Permalink
New resources: iothub_endpoint_storage_container, `iothub_endpoint_…
Browse files Browse the repository at this point in the history
…eventhub`, `iothub_endpoint_servicebus_queue`, `iothub_endpoint_servicebus_topic` (#4823)
  • Loading branch information
mbfrahry committed Nov 18, 2019
1 parent 594b377 commit 7f5ad6b
Show file tree
Hide file tree
Showing 21 changed files with 2,427 additions and 54 deletions.
19 changes: 19 additions & 0 deletions azurerm/helpers/validate/iothub.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,22 @@ func IoTHubConsumerGroupName(v interface{}, k string) (warnings []string, errors

return warnings, errors
}

func IoTHubEndpointName(v interface{}, _ string) (warnings []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 warnings, errors
}
25 changes: 25 additions & 0 deletions azurerm/helpers/validate/storage_container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package validate

import (
"fmt"
"regexp"
)

func StorageContainerName(v interface{}, k string) (warnings []string, errors []error) {
value := v.(string)

if !regexp.MustCompile(`^\$root$|^\$web$|^[0-9a-z-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only lowercase alphanumeric characters and hyphens allowed in %q: %q",
k, value))
}
if len(value) < 3 || len(value) > 63 {
errors = append(errors, fmt.Errorf(
"%q must be between 3 and 63 characters: %q", k, value))
}
if regexp.MustCompile(`^-`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q cannot begin with a hyphen: %q", k, value))
}
return warnings, errors
}
4 changes: 4 additions & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,10 @@ func Provider() terraform.ResourceProvider {
"azurerm_iot_dps_certificate": resourceArmIotDPSCertificate(),
"azurerm_iothub_consumer_group": resourceArmIotHubConsumerGroup(),
"azurerm_iothub": resourceArmIotHub(),
"azurerm_iothub_endpoint_eventhub": resourceArmIotHubEndpointEventHub(),
"azurerm_iothub_endpoint_servicebus_queue": resourceArmIotHubEndpointServiceBusQueue(),
"azurerm_iothub_endpoint_servicebus_topic": resourceArmIotHubEndpointServiceBusTopic(),
"azurerm_iothub_endpoint_storage_container": resourceArmIotHubEndpointStorageContainer(),
"azurerm_iothub_shared_access_policy": resourceArmIotHubSharedAccessPolicy(),
"azurerm_key_vault_access_policy": resourceArmKeyVaultAccessPolicy(),
"azurerm_key_vault_certificate": resourceArmKeyVaultCertificate(),
Expand Down
46 changes: 16 additions & 30 deletions azurerm/resource_arm_iothub.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ func resourceArmIotHub() *schema.Resource {
"endpoint": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Expand Down Expand Up @@ -262,7 +263,7 @@ func resourceArmIotHub() *schema.Resource {
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateIoTHubEndpointName,
ValidateFunc: validate.IoTHubEndpointName,
},
"batch_frequency_in_seconds": {
Type: schema.TypeInt,
Expand Down Expand Up @@ -468,32 +469,36 @@ func resourceArmIotHubCreateUpdate(d *schema.ResourceData, meta interface{}) err
location := azure.NormalizeLocation(d.Get("location").(string))
skuInfo := expandIoTHubSku(d)
t := d.Get("tags").(map[string]interface{})

fallbackRoute := expandIoTHubFallbackRoute(d)
routes := expandIoTHubRoutes(d)
routingProperties := devices.RoutingProperties{
Routes: routes,
FallbackRoute: fallbackRoute,
}

endpoints, err := expandIoTHubEndpoints(d, subscriptionID)
if err != nil {
return fmt.Errorf("Error expanding `endpoint`: %+v", err)
if _, ok := d.GetOk("endpoint"); ok {
endpoints, err := expandIoTHubEndpoints(d, subscriptionID)
if err != nil {
return fmt.Errorf("Error expanding `endpoint`: %+v", err)
}
routingProperties.Endpoints = endpoints
}

storageEndpoints, messagingEndpoints, enableFileUploadNotifications, err := expandIoTHubFileUpload(d)
if err != nil {
return fmt.Errorf("Error expanding `file_upload`: %+v", err)
}

routes := expandIoTHubRoutes(d)
ipFilterRules := expandIPFilterRules(d)

properties := devices.IotHubDescription{
Name: utils.String(name),
Location: utils.String(location),
Sku: skuInfo,
Properties: &devices.IotHubProperties{
IPFilterRules: ipFilterRules,
Routing: &devices.RoutingProperties{
Endpoints: endpoints,
Routes: routes,
FallbackRoute: fallbackRoute,
},
IPFilterRules: ipFilterRules,
Routing: &routingProperties,
StorageEndpoints: storageEndpoints,
MessagingEndpoints: messagingEndpoints,
EnableFileUploadNotifications: &enableFileUploadNotifications,
Expand Down Expand Up @@ -1065,25 +1070,6 @@ func flattenIoTHubFallbackRoute(input *devices.RoutingProperties) []interface{}
return []interface{}{output}
}

func validateIoTHubEndpointName(v interface{}, _ string) (warnings []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 warnings, errors
}

func validateIoTHubFileNameFormat(v interface{}, k string) (warnings []string, errors []error) {
value := v.(string)

Expand Down
245 changes: 245 additions & 0 deletions azurerm/resource_arm_iothub_endpoint_eventhub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package azurerm

import (
"fmt"
"regexp"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/preview/iothub/mgmt/2018-12-01-preview/devices"
"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/locks"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmIotHubEndpointEventHub() *schema.Resource {
return &schema.Resource{
Create: resourceArmIotHubEndpointEventHubCreateUpdate,
Read: resourceArmIotHubEndpointEventHubRead,
Update: resourceArmIotHubEndpointEventHubCreateUpdate,
Delete: resourceArmIotHubEndpointEventHubDelete,
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.IoTHubEndpointName,
},

"resource_group_name": azure.SchemaResourceGroupName(),

"iothub_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.IoTHubName,
},

"connection_string": {
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
sharedAccessKeyRegex := regexp.MustCompile("SharedAccessKey=[^;]+")
sbProtocolRegex := regexp.MustCompile("sb://([^:]+)(:5671)?/;")

maskedNew := sbProtocolRegex.ReplaceAllString(new, "sb://$1:5671/;")
maskedNew = sharedAccessKeyRegex.ReplaceAllString(maskedNew, "SharedAccessKey=****")
return (new == d.Get(k).(string)) && (maskedNew == old)
},
Sensitive: true,
},
},
}
}

func resourceArmIotHubEndpointEventHubCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).IoTHub.ResourceClient
ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d)
defer cancel()

iothubName := d.Get("iothub_name").(string)
resourceGroup := d.Get("resource_group_name").(string)

locks.ByName(iothubName, iothubResourceName)
defer locks.UnlockByName(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)
}

endpointName := d.Get("name").(string)
resourceId := fmt.Sprintf("%s/Endpoints/%s", *iothub.ID, endpointName)

eventhubEndpoint := devices.RoutingEventHubProperties{
ConnectionString: utils.String(d.Get("connection_string").(string)),
Name: utils.String(endpointName),
SubscriptionID: utils.String(meta.(*ArmClient).subscriptionId),
ResourceGroup: utils.String(resourceGroup),
}

routing := iothub.Properties.Routing
if routing == nil {
routing = &devices.RoutingProperties{}
}

if routing.Endpoints == nil {
routing.Endpoints = &devices.RoutingEndpoints{}
}

if routing.Endpoints.EventHubs == nil {
eventHubs := make([]devices.RoutingEventHubProperties, 0)
routing.Endpoints.EventHubs = &eventHubs
}

endpoints := make([]devices.RoutingEventHubProperties, 0)

alreadyExists := false
for _, existingEndpoint := range *routing.Endpoints.EventHubs {
if existingEndpointName := existingEndpoint.Name; existingEndpointName != nil {
if strings.EqualFold(*existingEndpointName, endpointName) {
if d.IsNewResource() && requireResourcesToBeImported {
return tf.ImportAsExistsError("azurerm_iothub_endpoint_eventhub", resourceId)
}
endpoints = append(endpoints, eventhubEndpoint)
alreadyExists = true
}
} else {
endpoints = append(endpoints, existingEndpoint)
}
}

if d.IsNewResource() {
endpoints = append(endpoints, eventhubEndpoint)
} else if !alreadyExists {
return fmt.Errorf("Unable to find EventHub Endpoint %q defined for IotHub %q (Resource Group %q)", endpointName, iothubName, resourceGroup)
}
routing.Endpoints.EventHubs = &endpoints

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 resourceArmIotHubEndpointEventHubRead(d, meta)
}

func resourceArmIotHubEndpointEventHubRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).IoTHub.ResourceClient
ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d)
defer cancel()

parsedIothubEndpointId, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}

resourceGroup := parsedIothubEndpointId.ResourceGroup
iothubName := parsedIothubEndpointId.Path["IotHubs"]
endpointName := parsedIothubEndpointId.Path["Endpoints"]

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("name", endpointName)
d.Set("iothub_name", iothubName)
d.Set("resource_group_name", resourceGroup)

if iothub.Properties == nil || iothub.Properties.Routing == nil || iothub.Properties.Routing.Endpoints == nil {
return nil
}

if endpoints := iothub.Properties.Routing.Endpoints.EventHubs; endpoints != nil {
for _, endpoint := range *endpoints {
if existingEndpointName := endpoint.Name; existingEndpointName != nil {
if strings.EqualFold(*existingEndpointName, endpointName) {
d.Set("connection_string", endpoint.ConnectionString)
}
}
}
}

return nil
}

func resourceArmIotHubEndpointEventHubDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).IoTHub.ResourceClient
ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d)
defer cancel()

parsedIothubEndpointId, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}

resourceGroup := parsedIothubEndpointId.ResourceGroup
iothubName := parsedIothubEndpointId.Path["IotHubs"]
endpointName := parsedIothubEndpointId.Path["Endpoints"]

locks.ByName(iothubName, iothubResourceName)
defer locks.UnlockByName(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 || iothub.Properties.Routing.Endpoints == nil {
return nil
}
endpoints := iothub.Properties.Routing.Endpoints.EventHubs

if endpoints == nil {
return nil
}

updatedEndpoints := make([]devices.RoutingEventHubProperties, 0)
for _, endpoint := range *endpoints {
if existingEndpointName := endpoint.Name; existingEndpointName != nil {
if !strings.EqualFold(*existingEndpointName, endpointName) {
updatedEndpoints = append(updatedEndpoints, endpoint)
}
}
}
iothub.Properties.Routing.Endpoints.EventHubs = &updatedEndpoints

future, err := client.CreateOrUpdate(ctx, resourceGroup, iothubName, iothub, "")
if err != nil {
return fmt.Errorf("Error updating IotHub %q (Resource Group %q) with EventHub Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("Error waiting for IotHub %q (Resource Group %q) to finish updating EventHub Endpoint %q: %+v", iothubName, resourceGroup, endpointName, err)
}

return nil
}
Loading

0 comments on commit 7f5ad6b

Please sign in to comment.