From a31e64dfeeba0bdf2752ccddeab8ba17a554ea54 Mon Sep 17 00:00:00 2001 From: Jochen Rauschenbusch Date: Mon, 14 Dec 2020 09:13:00 +0100 Subject: [PATCH] r/eventgrid_system_topic_event_subscription --- .../services/eventgrid/client/client.go | 25 +- .../services/eventgrid/event_subscription.go | 1155 +++++++++++++++++ .../eventgrid_event_subscription_resource.go | 1151 ++-------------- ...ystem_topic_event_subscription_resource.go | 337 +++++ .../parse/system_topic_event_subscription.go | 38 + .../system_topic_event_subscription_test.go | 58 + .../services/eventgrid/registration.go | 11 +- .../eventgrid_domain_resource_test.go | 0 .../eventgrid_domain_topic_resource_test.go | 0 ...ntgrid_event_subscription_resource_test.go | 46 +- ..._topic_event_subscription_resource_test.go | 675 ++++++++++ .../eventgrid_system_topic_resource_test.go | 0 .../eventgrid_topic_data_source_test.go | 0 .../eventgrid_topic_resource_test.go | 0 ...tem_topic_event_subscription.html.markdown | 210 +++ 15 files changed, 2591 insertions(+), 1115 deletions(-) create mode 100644 azurerm/internal/services/eventgrid/event_subscription.go create mode 100644 azurerm/internal/services/eventgrid/eventgrid_system_topic_event_subscription_resource.go create mode 100644 azurerm/internal/services/eventgrid/parse/system_topic_event_subscription.go create mode 100644 azurerm/internal/services/eventgrid/parse/system_topic_event_subscription_test.go rename azurerm/internal/services/eventgrid/{ => tests}/eventgrid_domain_resource_test.go (100%) rename azurerm/internal/services/eventgrid/{ => tests}/eventgrid_domain_topic_resource_test.go (100%) rename azurerm/internal/services/eventgrid/{ => tests}/eventgrid_event_subscription_resource_test.go (93%) create mode 100644 azurerm/internal/services/eventgrid/tests/eventgrid_system_topic_event_subscription_resource_test.go rename azurerm/internal/services/eventgrid/{ => tests}/eventgrid_system_topic_resource_test.go (100%) rename azurerm/internal/services/eventgrid/{ => tests}/eventgrid_topic_data_source_test.go (100%) rename azurerm/internal/services/eventgrid/{ => tests}/eventgrid_topic_resource_test.go (100%) create mode 100644 website/docs/r/eventgrid_system_topic_event_subscription.html.markdown diff --git a/azurerm/internal/services/eventgrid/client/client.go b/azurerm/internal/services/eventgrid/client/client.go index 21f596fa3acf..de3f324ffbd4 100644 --- a/azurerm/internal/services/eventgrid/client/client.go +++ b/azurerm/internal/services/eventgrid/client/client.go @@ -6,11 +6,12 @@ import ( ) type Client struct { - DomainsClient *eventgrid.DomainsClient - DomainTopicsClient *eventgrid.DomainTopicsClient - EventSubscriptionsClient *eventgrid.EventSubscriptionsClient - TopicsClient *eventgrid.TopicsClient - SystemTopicsClient *eventgrid.SystemTopicsClient + DomainsClient *eventgrid.DomainsClient + DomainTopicsClient *eventgrid.DomainTopicsClient + EventSubscriptionsClient *eventgrid.EventSubscriptionsClient + TopicsClient *eventgrid.TopicsClient + SystemTopicsClient *eventgrid.SystemTopicsClient + SystemTopicEventSubscriptionsClient *eventgrid.SystemTopicEventSubscriptionsClient } func NewClient(o *common.ClientOptions) *Client { @@ -29,11 +30,15 @@ func NewClient(o *common.ClientOptions) *Client { SystemTopicsClient := eventgrid.NewSystemTopicsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&SystemTopicsClient.Client, o.ResourceManagerAuthorizer) + SystemTopicEventSubscriptionsClient := eventgrid.NewSystemTopicEventSubscriptionsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&SystemTopicEventSubscriptionsClient.Client, o.ResourceManagerAuthorizer) + return &Client{ - DomainsClient: &DomainsClient, - EventSubscriptionsClient: &EventSubscriptionsClient, - DomainTopicsClient: &DomainTopicsClient, - TopicsClient: &TopicsClient, - SystemTopicsClient: &SystemTopicsClient, + DomainsClient: &DomainsClient, + EventSubscriptionsClient: &EventSubscriptionsClient, + DomainTopicsClient: &DomainTopicsClient, + TopicsClient: &TopicsClient, + SystemTopicsClient: &SystemTopicsClient, + SystemTopicEventSubscriptionsClient: &SystemTopicEventSubscriptionsClient, } } diff --git a/azurerm/internal/services/eventgrid/event_subscription.go b/azurerm/internal/services/eventgrid/event_subscription.go new file mode 100644 index 000000000000..eaba65490d63 --- /dev/null +++ b/azurerm/internal/services/eventgrid/event_subscription.go @@ -0,0 +1,1155 @@ +package eventgrid + +import ( + "fmt" + "regexp" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/eventgrid/mgmt/2020-04-01-preview/eventgrid" + "github.com/Azure/go-autorest/autorest/date" + "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/utils" +) + +// EventSubscriptionEndpointType enumerates the values for event subscription endpoint types. +type EventSubscriptionEndpointType string + +const ( + // AzureFunctionEndpoint ... + AzureFunctionEndpoint EventSubscriptionEndpointType = "azure_function_endpoint" + // EventHubEndpoint ... + EventHubEndpoint EventSubscriptionEndpointType = "eventhub_endpoint" + // EventHubEndpointID ... + EventHubEndpointID EventSubscriptionEndpointType = "eventhub_endpoint_id" + // HybridConnectionEndpoint ... + HybridConnectionEndpoint EventSubscriptionEndpointType = "hybrid_connection_endpoint" + // HybridConnectionEndpointID ... + HybridConnectionEndpointID EventSubscriptionEndpointType = "hybrid_connection_endpoint_id" + // ServiceBusQueueEndpointID ... + ServiceBusQueueEndpointID EventSubscriptionEndpointType = "service_bus_queue_endpoint_id" + // ServiceBusTopicEndpointID ... + ServiceBusTopicEndpointID EventSubscriptionEndpointType = "service_bus_topic_endpoint_id" + // StorageQueueEndpoint ... + StorageQueueEndpoint EventSubscriptionEndpointType = "storage_queue_endpoint" + // WebHookEndpoint ... + WebHookEndpoint EventSubscriptionEndpointType = "webhook_endpoint" +) + +func eventSubscriptionSchemaEventSubscriptionName() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringIsNotEmpty, + validation.StringMatch( + regexp.MustCompile("^[-a-zA-Z0-9]{3,50}$"), + "EventGrid subscription name must be 3 - 50 characters long, contain only letters, numbers and hyphens.", + ), + ), + } +} + +func eventSubscriptionSchemaEventDeliverySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: string(eventgrid.EventGridSchema), + ValidateFunc: validation.StringInSlice([]string{ + string(eventgrid.EventGridSchema), + string(eventgrid.CloudEventSchemaV10), + string(eventgrid.CustomInputSchema), + }, false), + } +} + +func eventSubscriptionSchemaExpirationTimeUTC() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + } +} + +func eventSubscriptionSchemaAzureFunctionEndpoint(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: conflictsWith, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "function_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + "max_events_per_batch": { + Type: schema.TypeInt, + Optional: true, + }, + "preferred_batch_size_in_kilobytes": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + } +} + +func eventSubscriptionSchemaEventHubEndpointID(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: conflictsWith, + ValidateFunc: azure.ValidateResourceID, + } +} + +func eventSubscriptionSchemaEventHubEndpoint(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Deprecated: "Deprecated in favour of `" + "eventhub_endpoint_id" + "`", + Optional: true, + Computed: true, + ConflictsWith: conflictsWith, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "eventhub_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: azure.ValidateResourceID, + }, + }, + }, + } +} + +func eventSubscriptionSchemaHybridConnectionEndpointID(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: conflictsWith, + ValidateFunc: azure.ValidateResourceID, + } +} + +func eventSubscriptionSchemaHybridEndpoint(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Deprecated: "Deprecated in favour of `" + "hybrid_connection_endpoint_id" + "`", + Optional: true, + Computed: true, + ConflictsWith: conflictsWith, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hybrid_connection_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: azure.ValidateResourceID, + }, + }, + }, + } +} + +func eventSubscriptionSchemaServiceBusQueueEndpointID(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: conflictsWith, + ValidateFunc: azure.ValidateResourceID, + } +} + +func eventSubscriptionSchemaServiceBusTopicEndpointID(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: conflictsWith, + ValidateFunc: azure.ValidateResourceID, + } +} + +func eventSubscriptionSchemaStorageQueueEndpoint(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: conflictsWith, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "storage_account_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + "queue_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + } +} + +func eventSubscriptionSchemaWebHookEndpoint(conflictsWith []string) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: conflictsWith, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsURLWithHTTPS, + }, + "base_url": { + Type: schema.TypeString, + Computed: true, + }, + "max_events_per_batch": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 5000), + }, + "preferred_batch_size_in_kilobytes": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(1, 1024), + }, + "active_directory_tenant_id": { + Type: schema.TypeString, + Optional: true, + }, + "active_directory_app_id_or_uri": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + } +} + +func eventSubscriptionSchemaIncludedEventTypes() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + } +} + +func eventSubscriptionSchemaSubjectFilter() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subject_begins_with": { + Type: schema.TypeString, + Optional: true, + }, + "subject_ends_with": { + Type: schema.TypeString, + Optional: true, + }, + "case_sensitive": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + } +} + +func eventSubscriptionSchemaAdvancedFilter() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bool_equals": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "value": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, + "number_greater_than": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "value": { + Type: schema.TypeFloat, + Required: true, + }, + }, + }, + }, + "number_greater_than_or_equals": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "value": { + Type: schema.TypeFloat, + Required: true, + }, + }, + }, + }, + "number_less_than": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "value": { + Type: schema.TypeFloat, + Required: true, + }, + }, + }, + }, + "number_less_than_or_equals": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "value": { + Type: schema.TypeFloat, + Required: true, + }, + }, + }, + }, + "number_in": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "values": { + Type: schema.TypeList, + Required: true, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeFloat, + }, + }, + }, + }, + }, + "number_not_in": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "values": { + Type: schema.TypeList, + Required: true, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeFloat, + }, + }, + }, + }, + }, + "string_begins_with": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "values": { + Type: schema.TypeList, + Required: true, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "string_ends_with": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "values": { + Type: schema.TypeList, + Required: true, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "string_contains": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "values": { + Type: schema.TypeList, + Required: true, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "string_in": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "values": { + Type: schema.TypeList, + Required: true, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "string_not_in": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "values": { + Type: schema.TypeList, + Required: true, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + } +} + +func eventSubscriptionSchemaStorageBlobDeadletterDestination() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "storage_account_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + "storage_blob_container_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + } +} + +func eventSubscriptionSchemaRetryPolicy() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_delivery_attempts": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 30), + }, + "event_time_to_live": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 1440), + }, + }, + }, + } +} + +func eventSubscriptionSchemaLabels() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + } +} + +func expandEventGridExpirationTime(d *schema.ResourceData) (*date.Time, error) { + if expirationTimeUtc, ok := d.GetOk("expiration_time_utc"); ok { + if expirationTimeUtc == "" { + return nil, nil + } + + parsedExpirationTimeUtc, err := date.ParseTime(time.RFC3339, expirationTimeUtc.(string)) + if err != nil { + return nil, err + } + + return &date.Time{Time: parsedExpirationTimeUtc}, nil + } + + return nil, nil +} + +func expandEventGridEventSubscriptionDestination(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { + if v, ok := d.GetOk("azure_function_endpoint"); ok { + return expandEventGridEventSubscriptionAzureFunctionEndpoint(v) + } + + if v, ok := d.GetOk("eventhub_endpoint_id"); ok { + return &eventgrid.EventHubEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeEventHub, + EventHubEventSubscriptionDestinationProperties: &eventgrid.EventHubEventSubscriptionDestinationProperties{ + ResourceID: utils.String(v.(string)), + }, + } + } else if _, ok := d.GetOk("eventhub_endpoint"); ok { + return expandEventGridEventSubscriptionEventhubEndpoint(d) + } + + if v, ok := d.GetOk("hybrid_connection_endpoint_id"); ok { + return &eventgrid.HybridConnectionEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeHybridConnection, + HybridConnectionEventSubscriptionDestinationProperties: &eventgrid.HybridConnectionEventSubscriptionDestinationProperties{ + ResourceID: utils.String(v.(string)), + }, + } + } else if _, ok := d.GetOk("hybrid_connection_endpoint"); ok { + return expandEventGridEventSubscriptionHybridConnectionEndpoint(d) + } + + if v, ok := d.GetOk("service_bus_queue_endpoint_id"); ok { + return &eventgrid.ServiceBusQueueEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeServiceBusQueue, + ServiceBusQueueEventSubscriptionDestinationProperties: &eventgrid.ServiceBusQueueEventSubscriptionDestinationProperties{ + ResourceID: utils.String(v.(string)), + }, + } + } + + if v, ok := d.GetOk("service_bus_topic_endpoint_id"); ok { + return &eventgrid.ServiceBusTopicEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeServiceBusTopic, + ServiceBusTopicEventSubscriptionDestinationProperties: &eventgrid.ServiceBusTopicEventSubscriptionDestinationProperties{ + ResourceID: utils.String(v.(string)), + }, + } + } + + if _, ok := d.GetOk("storage_queue_endpoint"); ok { + return expandEventGridEventSubscriptionStorageQueueEndpoint(d) + } + + if v, ok := d.GetOk("webhook_endpoint"); ok { + return expandEventGridEventSubscriptionWebhookEndpoint(v) + } + + return nil +} + +func expandEventGridEventSubscriptionStorageQueueEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { + props := d.Get("storage_queue_endpoint").([]interface{})[0].(map[string]interface{}) + storageAccountID := props["storage_account_id"].(string) + queueName := props["queue_name"].(string) + + return eventgrid.StorageQueueEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeStorageQueue, + StorageQueueEventSubscriptionDestinationProperties: &eventgrid.StorageQueueEventSubscriptionDestinationProperties{ + ResourceID: &storageAccountID, + QueueName: &queueName, + }, + } +} + +func expandEventGridEventSubscriptionEventhubEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { + props := d.Get("eventhub_endpoint").([]interface{})[0].(map[string]interface{}) + eventHubID := props["eventhub_id"].(string) + + return eventgrid.EventHubEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeEventHub, + EventHubEventSubscriptionDestinationProperties: &eventgrid.EventHubEventSubscriptionDestinationProperties{ + ResourceID: &eventHubID, + }, + } +} + +func expandEventGridEventSubscriptionHybridConnectionEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { + props := d.Get("hybrid_connection_endpoint").([]interface{})[0].(map[string]interface{}) + hybridConnectionID := props["hybrid_connection_id"].(string) + + return eventgrid.HybridConnectionEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeHybridConnection, + HybridConnectionEventSubscriptionDestinationProperties: &eventgrid.HybridConnectionEventSubscriptionDestinationProperties{ + ResourceID: &hybridConnectionID, + }, + } +} + +func expandEventGridEventSubscriptionAzureFunctionEndpoint(input interface{}) eventgrid.BasicEventSubscriptionDestination { + configs := input.([]interface{}) + + props := eventgrid.AzureFunctionEventSubscriptionDestinationProperties{} + azureFunctionDestination := &eventgrid.AzureFunctionEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeAzureFunction, + AzureFunctionEventSubscriptionDestinationProperties: &props, + } + + if len(configs) == 0 { + return azureFunctionDestination + } + + config := configs[0].(map[string]interface{}) + + if v, ok := config["function_id"]; ok && v != "" { + props.ResourceID = utils.String(v.(string)) + } + + if v, ok := config["max_events_per_batch"]; ok && v != 0 { + props.MaxEventsPerBatch = utils.Int32(int32(v.(int))) + } + + if v, ok := config["preferred_batch_size_in_kilobytes"]; ok && v != 0 { + props.PreferredBatchSizeInKilobytes = utils.Int32(int32(v.(int))) + } + + return azureFunctionDestination +} + +func expandEventGridEventSubscriptionWebhookEndpoint(input interface{}) eventgrid.BasicEventSubscriptionDestination { + configs := input.([]interface{}) + + props := eventgrid.WebHookEventSubscriptionDestinationProperties{} + webhookDestination := &eventgrid.WebHookEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeWebHook, + WebHookEventSubscriptionDestinationProperties: &props, + } + + if len(configs) == 0 { + return webhookDestination + } + + config := configs[0].(map[string]interface{}) + + if v, ok := config["url"]; ok && v != "" { + props.EndpointURL = utils.String(v.(string)) + } + + if v, ok := config["max_events_per_batch"]; ok && v != 0 { + props.MaxEventsPerBatch = utils.Int32(int32(v.(int))) + } + + if v, ok := config["preferred_batch_size_in_kilobytes"]; ok && v != 0 { + props.PreferredBatchSizeInKilobytes = utils.Int32(int32(v.(int))) + } + + if v, ok := config["active_directory_tenant_id"]; ok && v != "" { + props.AzureActiveDirectoryTenantID = utils.String(v.(string)) + } + + if v, ok := config["active_directory_app_id_or_uri"]; ok && v != "" { + props.AzureActiveDirectoryApplicationIDOrURI = utils.String(v.(string)) + } + + return webhookDestination +} + +func expandEventGridEventSubscriptionFilter(d *schema.ResourceData) (*eventgrid.EventSubscriptionFilter, error) { + filter := &eventgrid.EventSubscriptionFilter{} + + if includedEvents, ok := d.GetOk("included_event_types"); ok { + filter.IncludedEventTypes = utils.ExpandStringSlice(includedEvents.([]interface{})) + } + + if v, ok := d.GetOk("subject_filter"); ok { + if v.([]interface{})[0] != nil { + config := v.([]interface{})[0].(map[string]interface{}) + subjectBeginsWith := config["subject_begins_with"].(string) + subjectEndsWith := config["subject_ends_with"].(string) + caseSensitive := config["case_sensitive"].(bool) + + filter.SubjectBeginsWith = &subjectBeginsWith + filter.SubjectEndsWith = &subjectEndsWith + filter.IsSubjectCaseSensitive = &caseSensitive + } + } + + if advancedFilter, ok := d.GetOk("advanced_filter"); ok { + advancedFilters := make([]eventgrid.BasicAdvancedFilter, 0) + for filterKey, filterSchema := range advancedFilter.([]interface{})[0].(map[string]interface{}) { + for _, options := range filterSchema.([]interface{}) { + if filter, err := expandAdvancedFilter(filterKey, options.(map[string]interface{})); err == nil { + advancedFilters = append(advancedFilters, filter) + } else { + return nil, err + } + } + } + filter.AdvancedFilters = &advancedFilters + } + + return filter, nil +} + +func expandAdvancedFilter(operatorType string, config map[string]interface{}) (eventgrid.BasicAdvancedFilter, error) { + k := config["key"].(string) + + switch operatorType { + case "bool_equals": + v := config["value"].(bool) + return eventgrid.BoolEqualsAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeBoolEquals, Value: &v}, nil + case "number_greater_than": + v := config["value"].(float64) + return eventgrid.NumberGreaterThanAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberGreaterThan, Value: &v}, nil + case "number_greater_than_or_equals": + v := config["value"].(float64) + return eventgrid.NumberGreaterThanOrEqualsAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberGreaterThanOrEquals, Value: &v}, nil + case "number_less_than": + v := config["value"].(float64) + return eventgrid.NumberLessThanAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberLessThan, Value: &v}, nil + case "number_less_than_or_equals": + v := config["value"].(float64) + return eventgrid.NumberLessThanOrEqualsAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberLessThanOrEquals, Value: &v}, nil + case "number_in": + v := utils.ExpandFloatSlice(config["values"].([]interface{})) + return eventgrid.NumberInAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberIn, Values: v}, nil + case "number_not_in": + v := utils.ExpandFloatSlice(config["values"].([]interface{})) + return eventgrid.NumberNotInAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberIn, Values: v}, nil + case "string_begins_with": + v := utils.ExpandStringSlice(config["values"].([]interface{})) + return eventgrid.StringBeginsWithAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringBeginsWith, Values: v}, nil + case "string_ends_with": + v := utils.ExpandStringSlice(config["values"].([]interface{})) + return eventgrid.StringEndsWithAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringEndsWith, Values: v}, nil + case "string_contains": + v := utils.ExpandStringSlice(config["values"].([]interface{})) + return eventgrid.StringContainsAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringContains, Values: v}, nil + case "string_in": + v := utils.ExpandStringSlice(config["values"].([]interface{})) + return eventgrid.StringInAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringIn, Values: v}, nil + case "string_not_in": + v := utils.ExpandStringSlice(config["values"].([]interface{})) + return eventgrid.StringNotInAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringNotIn, Values: v}, nil + default: + return nil, fmt.Errorf("Invalid `advanced_filter` operator_type %q used", operatorType) + } +} + +func expandEventGridEventSubscriptionStorageBlobDeadLetterDestination(d *schema.ResourceData) eventgrid.BasicDeadLetterDestination { + if v, ok := d.GetOk("storage_blob_dead_letter_destination"); ok { + dest := v.([]interface{})[0].(map[string]interface{}) + resourceID := dest["storage_account_id"].(string) + blobName := dest["storage_blob_container_name"].(string) + return eventgrid.StorageBlobDeadLetterDestination{ + EndpointType: eventgrid.EndpointTypeStorageBlob, + StorageBlobDeadLetterDestinationProperties: &eventgrid.StorageBlobDeadLetterDestinationProperties{ + ResourceID: &resourceID, + BlobContainerName: &blobName, + }, + } + } + + return nil +} + +func expandEventGridEventSubscriptionRetryPolicy(d *schema.ResourceData) *eventgrid.RetryPolicy { + if v, ok := d.GetOk("retry_policy"); ok { + dest := v.([]interface{})[0].(map[string]interface{}) + maxDeliveryAttempts := dest["max_delivery_attempts"].(int) + eventTimeToLive := dest["event_time_to_live"].(int) + return &eventgrid.RetryPolicy{ + MaxDeliveryAttempts: utils.Int32(int32(maxDeliveryAttempts)), + EventTimeToLiveInMinutes: utils.Int32(int32(eventTimeToLive)), + } + } + + return nil +} + +func flattenEventGridEventSubscriptionEventhubEndpoint(input *eventgrid.EventHubEventSubscriptionDestination) []interface{} { + if input == nil { + return nil + } + result := make(map[string]interface{}) + + if input.ResourceID != nil { + result["eventhub_id"] = *input.ResourceID + } + + return []interface{}{result} +} + +func flattenEventGridEventSubscriptionHybridConnectionEndpoint(input *eventgrid.HybridConnectionEventSubscriptionDestination) []interface{} { + if input == nil { + return nil + } + + hybridConnectionId := "" + if input.ResourceID != nil { + hybridConnectionId = *input.ResourceID + } + + return []interface{}{ + map[string]interface{}{ + "hybrid_connection_id": hybridConnectionId, + }, + } +} + +func flattenEventGridEventSubscriptionStorageQueueEndpoint(input *eventgrid.StorageQueueEventSubscriptionDestination) []interface{} { + if input == nil { + return nil + } + result := make(map[string]interface{}) + + if input.ResourceID != nil { + result["storage_account_id"] = *input.ResourceID + } + if input.QueueName != nil { + result["queue_name"] = *input.QueueName + } + + return []interface{}{result} +} + +func flattenEventGridEventSubscriptionAzureFunctionEndpoint(input *eventgrid.AzureFunctionEventSubscriptionDestination) []interface{} { + results := make([]interface{}, 0) + + if input == nil { + return results + } + + functionID := "" + if input.ResourceID != nil { + functionID = *input.ResourceID + } + + maxEventsPerBatch := 0 + if input.MaxEventsPerBatch != nil { + maxEventsPerBatch = int(*input.MaxEventsPerBatch) + } + + preferredBatchSize := 0 + if input.PreferredBatchSizeInKilobytes != nil { + preferredBatchSize = int(*input.PreferredBatchSizeInKilobytes) + } + + return append(results, map[string]interface{}{ + "function_id": functionID, + "max_events_per_batch": maxEventsPerBatch, + "preferred_batch_size_in_kilobytes": preferredBatchSize, + }) +} + +func flattenEventGridEventSubscriptionWebhookEndpoint(input *eventgrid.WebHookEventSubscriptionDestination, fullURL *eventgrid.EventSubscriptionFullURL) []interface{} { + results := make([]interface{}, 0) + + if input == nil { + return results + } + + webhookURL := "" + if fullURL != nil { + webhookURL = *fullURL.EndpointURL + } + + webhookBaseURL := "" + if input.EndpointBaseURL != nil { + webhookBaseURL = *input.EndpointBaseURL + } + + maxEventsPerBatch := 0 + if input.MaxEventsPerBatch != nil { + maxEventsPerBatch = int(*input.MaxEventsPerBatch) + } + + preferredBatchSizeInKilobytes := 0 + if input.PreferredBatchSizeInKilobytes != nil { + preferredBatchSizeInKilobytes = int(*input.PreferredBatchSizeInKilobytes) + } + + azureActiveDirectoryTenantID := "" + if input.AzureActiveDirectoryTenantID != nil { + azureActiveDirectoryTenantID = *input.AzureActiveDirectoryTenantID + } + + azureActiveDirectoryApplicationIDOrURI := "" + if input.AzureActiveDirectoryApplicationIDOrURI != nil { + azureActiveDirectoryApplicationIDOrURI = *input.AzureActiveDirectoryApplicationIDOrURI + } + + return append(results, map[string]interface{}{ + "url": webhookURL, + "base_url": webhookBaseURL, + "max_events_per_batch": maxEventsPerBatch, + "preferred_batch_size_in_kilobytes": preferredBatchSizeInKilobytes, + "active_directory_tenant_id": azureActiveDirectoryTenantID, + "active_directory_app_id_or_uri": azureActiveDirectoryApplicationIDOrURI, + }) +} + +func flattenEventGridEventSubscriptionSubjectFilter(filter *eventgrid.EventSubscriptionFilter) []interface{} { + if (filter.SubjectBeginsWith != nil && *filter.SubjectBeginsWith == "") && (filter.SubjectEndsWith != nil && *filter.SubjectEndsWith == "") { + return nil + } + result := make(map[string]interface{}) + + if filter.SubjectBeginsWith != nil { + result["subject_begins_with"] = *filter.SubjectBeginsWith + } + + if filter.SubjectEndsWith != nil { + result["subject_ends_with"] = *filter.SubjectEndsWith + } + + if filter.IsSubjectCaseSensitive != nil { + result["case_sensitive"] = *filter.IsSubjectCaseSensitive + } + + return []interface{}{result} +} + +func flattenEventGridEventSubscriptionAdvancedFilter(input *eventgrid.EventSubscriptionFilter) []interface{} { + results := make([]interface{}, 0) + if input == nil || input.AdvancedFilters == nil { + return results + } + + boolEquals := make([]interface{}, 0) + numberGreaterThan := make([]interface{}, 0) + numberGreaterThanOrEquals := make([]interface{}, 0) + numberLessThan := make([]interface{}, 0) + numberLessThanOrEquals := make([]interface{}, 0) + numberIn := make([]interface{}, 0) + numberNotIn := make([]interface{}, 0) + stringBeginsWith := make([]interface{}, 0) + stringEndsWith := make([]interface{}, 0) + stringContains := make([]interface{}, 0) + stringIn := make([]interface{}, 0) + stringNotIn := make([]interface{}, 0) + + for _, item := range *input.AdvancedFilters { + switch f := item.(type) { + case eventgrid.BoolEqualsAdvancedFilter: + v := interface{}(f.Value) + boolEquals = append(boolEquals, flattenValue(f.Key, &v)) + case eventgrid.NumberGreaterThanAdvancedFilter: + v := interface{}(f.Value) + numberGreaterThan = append(numberGreaterThan, flattenValue(f.Key, &v)) + case eventgrid.NumberGreaterThanOrEqualsAdvancedFilter: + v := interface{}(f.Value) + numberGreaterThanOrEquals = append(numberGreaterThanOrEquals, flattenValue(f.Key, &v)) + case eventgrid.NumberLessThanAdvancedFilter: + v := interface{}(f.Value) + numberLessThan = append(numberLessThan, flattenValue(f.Key, &v)) + case eventgrid.NumberLessThanOrEqualsAdvancedFilter: + v := interface{}(f.Value) + numberLessThanOrEquals = append(numberLessThanOrEquals, flattenValue(f.Key, &v)) + case eventgrid.NumberInAdvancedFilter: + v := utils.FlattenFloatSlice(f.Values) + numberIn = append(numberIn, flattenValues(f.Key, &v)) + case eventgrid.NumberNotInAdvancedFilter: + v := utils.FlattenFloatSlice(f.Values) + numberNotIn = append(numberNotIn, flattenValues(f.Key, &v)) + case eventgrid.StringBeginsWithAdvancedFilter: + v := utils.FlattenStringSlice(f.Values) + stringBeginsWith = append(stringBeginsWith, flattenValues(f.Key, &v)) + case eventgrid.StringEndsWithAdvancedFilter: + v := utils.FlattenStringSlice(f.Values) + stringEndsWith = append(stringEndsWith, flattenValues(f.Key, &v)) + case eventgrid.StringContainsAdvancedFilter: + v := utils.FlattenStringSlice(f.Values) + stringContains = append(stringContains, flattenValues(f.Key, &v)) + case eventgrid.StringInAdvancedFilter: + v := utils.FlattenStringSlice(f.Values) + stringIn = append(stringIn, flattenValues(f.Key, &v)) + case eventgrid.StringNotInAdvancedFilter: + v := utils.FlattenStringSlice(f.Values) + stringNotIn = append(stringNotIn, flattenValues(f.Key, &v)) + } + } + + return []interface{}{ + map[string][]interface{}{ + "bool_equals": boolEquals, + "number_greater_than": numberGreaterThan, + "number_greater_than_or_equals": numberGreaterThanOrEquals, + "number_less_than": numberLessThan, + "number_less_than_or_equals": numberLessThanOrEquals, + "number_in": numberIn, + "number_not_in": numberNotIn, + "string_begins_with": stringBeginsWith, + "string_ends_with": stringEndsWith, + "string_contains": stringContains, + "string_in": stringIn, + "string_not_in": stringNotIn, + }, + } +} + +func flattenEventGridEventSubscriptionStorageBlobDeadLetterDestination(dest *eventgrid.StorageBlobDeadLetterDestination) []interface{} { + if dest == nil { + return nil + } + result := make(map[string]interface{}) + + if dest.ResourceID != nil { + result["storage_account_id"] = *dest.ResourceID + } + + if dest.BlobContainerName != nil { + result["storage_blob_container_name"] = *dest.BlobContainerName + } + + return []interface{}{result} +} + +func flattenEventGridEventSubscriptionRetryPolicy(retryPolicy *eventgrid.RetryPolicy) []interface{} { + result := make(map[string]interface{}) + + if v := retryPolicy.EventTimeToLiveInMinutes; v != nil { + result["event_time_to_live"] = int(*v) + } + + if v := retryPolicy.MaxDeliveryAttempts; v != nil { + result["max_delivery_attempts"] = int(*v) + } + + return []interface{}{result} +} + +func flattenValue(inputKey *string, inputValue *interface{}) map[string]interface{} { + key := "" + if inputKey != nil { + key = *inputKey + } + var value interface{} + if inputValue != nil { + value = inputValue + } + + return map[string]interface{}{ + "key": key, + "value": value, + } +} + +func flattenValues(inputKey *string, inputValues *[]interface{}) map[string]interface{} { + key := "" + if inputKey != nil { + key = *inputKey + } + values := make([]interface{}, 0) + if inputValues != nil { + values = *inputValues + } + + return map[string]interface{}{ + "key": key, + "values": values, + } +} diff --git a/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource.go b/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource.go index 83625b4b3c76..68c396826a98 100644 --- a/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource.go +++ b/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource.go @@ -3,15 +3,12 @@ package eventgrid import ( "fmt" "log" - "regexp" "time" "github.com/Azure/azure-sdk-for-go/services/preview/eventgrid/mgmt/2020-04-01-preview/eventgrid" - "github.com/Azure/go-autorest/autorest/date" "github.com/hashicorp/go-azure-helpers/response" "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/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/eventgrid/parse" @@ -20,17 +17,17 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -func enpointPropertyNames() []string { +func PossibleEventSubscriptionEndpointTypes() []string { return []string{ - "azure_function_endpoint", - "eventhub_endpoint", - "eventhub_endpoint_id", - "hybrid_connection_endpoint", - "hybrid_connection_endpoint_id", - "service_bus_queue_endpoint_id", - "service_bus_topic_endpoint_id", - "storage_queue_endpoint", - "webhook_endpoint", + string(AzureFunctionEndpoint), + string(EventHubEndpoint), + string(EventHubEndpointID), + string(HybridConnectionEndpoint), + string(HybridConnectionEndpointID), + string(ServiceBusQueueEndpointID), + string(ServiceBusTopicEndpointID), + string(StorageQueueEndpoint), + string(WebHookEndpoint), } } @@ -54,18 +51,7 @@ func resourceEventGridEventSubscription() *schema.Resource { }), Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringIsNotEmpty, - validation.StringMatch( - regexp.MustCompile("^[-a-zA-Z0-9]{3,50}$"), - "EventGrid subscription name must be 3 - 50 characters long, contain only letters, numbers and hyphens.", - ), - ), - }, + "name": eventSubscriptionSchemaEventSubscriptionName(), "scope": { Type: schema.TypeString, @@ -74,23 +60,9 @@ func resourceEventGridEventSubscription() *schema.Resource { ValidateFunc: validation.StringIsNotEmpty, }, - "event_delivery_schema": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: string(eventgrid.EventGridSchema), - ValidateFunc: validation.StringInSlice([]string{ - string(eventgrid.EventGridSchema), - string(eventgrid.CloudEventSchemaV10), - string(eventgrid.CustomInputSchema), - }, false), - }, + "event_delivery_schema": eventSubscriptionSchemaEventDeliverySchema(), - "expiration_time_utc": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, + "expiration_time_utc": eventSubscriptionSchemaExpirationTimeUTC(), "topic_name": { Type: schema.TypeString, @@ -99,479 +71,80 @@ func resourceEventGridEventSubscription() *schema.Resource { Deprecated: "This field has been updated to readonly field since Apr 25, 2019 so no longer has any affect and will be removed in version 3.0 of the provider.", }, - "azure_function_endpoint": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "azure_function_endpoint"), - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "function_id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: azure.ValidateResourceID, - }, - "max_events_per_batch": { - Type: schema.TypeInt, - Optional: true, - }, - "preferred_batch_size_in_kilobytes": { - Type: schema.TypeInt, - Optional: true, - }, - }, - }, - }, + "azure_function_endpoint": eventSubscriptionSchemaAzureFunctionEndpoint( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(AzureFunctionEndpoint), + ), + ), - "eventhub_endpoint_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "eventhub_endpoint_id"), - ValidateFunc: azure.ValidateResourceID, - }, + "eventhub_endpoint_id": eventSubscriptionSchemaEventHubEndpointID( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(EventHubEndpointID), + ), + ), - "eventhub_endpoint": { - Type: schema.TypeList, - MaxItems: 1, - Deprecated: "Deprecated in favour of `" + "eventhub_endpoint_id" + "`", - Optional: true, - Computed: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "eventhub_endpoint"), - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "eventhub_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: azure.ValidateResourceID, - }, - }, - }, - }, + "eventhub_endpoint": eventSubscriptionSchemaEventHubEndpoint( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(EventHubEndpoint), + ), + ), - "hybrid_connection_endpoint_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "hybrid_connection_endpoint_id"), - ValidateFunc: azure.ValidateResourceID, - }, + "hybrid_connection_endpoint_id": eventSubscriptionSchemaHybridConnectionEndpointID( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(HybridConnectionEndpointID), + ), + ), - "hybrid_connection_endpoint": { - Type: schema.TypeList, - MaxItems: 1, - Deprecated: "Deprecated in favour of `" + "hybrid_connection_endpoint_id" + "`", - Optional: true, - Computed: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "hybrid_connection_endpoint"), - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "hybrid_connection_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: azure.ValidateResourceID, - }, - }, - }, - }, + "hybrid_connection_endpoint": eventSubscriptionSchemaHybridEndpoint( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(HybridConnectionEndpoint), + ), + ), - "service_bus_queue_endpoint_id": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "service_bus_queue_endpoint_id"), - ValidateFunc: azure.ValidateResourceID, - }, + "service_bus_queue_endpoint_id": eventSubscriptionSchemaServiceBusQueueEndpointID( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(ServiceBusQueueEndpointID), + ), + ), - "service_bus_topic_endpoint_id": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "service_bus_topic_endpoint_id"), - ValidateFunc: azure.ValidateResourceID, - }, + "service_bus_topic_endpoint_id": eventSubscriptionSchemaServiceBusTopicEndpointID( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(ServiceBusTopicEndpointID), + ), + ), - "storage_queue_endpoint": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "storage_queue_endpoint"), - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "storage_account_id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: azure.ValidateResourceID, - }, - "queue_name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, + "storage_queue_endpoint": eventSubscriptionSchemaStorageQueueEndpoint( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(StorageQueueEndpoint), + ), + ), - "webhook_endpoint": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - ConflictsWith: utils.RemoveFromStringArray(enpointPropertyNames(), "webhook_endpoint"), - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "url": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.IsURLWithHTTPS, - }, - "base_url": { - Type: schema.TypeString, - Computed: true, - }, - "max_events_per_batch": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 5000), - }, - "preferred_batch_size_in_kilobytes": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 1024), - }, - "active_directory_tenant_id": { - Type: schema.TypeString, - Optional: true, - }, - "active_directory_app_id_or_uri": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, + "webhook_endpoint": eventSubscriptionSchemaWebHookEndpoint( + utils.RemoveFromStringArray( + PossibleEventSubscriptionEndpointTypes(), + string(WebHookEndpoint), + ), + ), - "included_event_types": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, + "included_event_types": eventSubscriptionSchemaIncludedEventTypes(), - "subject_filter": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "subject_begins_with": { - Type: schema.TypeString, - Optional: true, - }, - "subject_ends_with": { - Type: schema.TypeString, - Optional: true, - }, - "case_sensitive": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, + "subject_filter": eventSubscriptionSchemaSubjectFilter(), - "advanced_filter": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "bool_equals": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "value": { - Type: schema.TypeBool, - Required: true, - }, - }, - }, - }, - "number_greater_than": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "value": { - Type: schema.TypeFloat, - Required: true, - }, - }, - }, - }, - "number_greater_than_or_equals": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "value": { - Type: schema.TypeFloat, - Required: true, - }, - }, - }, - }, - "number_less_than": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "value": { - Type: schema.TypeFloat, - Required: true, - }, - }, - }, - }, - "number_less_than_or_equals": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "value": { - Type: schema.TypeFloat, - Required: true, - }, - }, - }, - }, - "number_in": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "values": { - Type: schema.TypeList, - Required: true, - MaxItems: 5, - Elem: &schema.Schema{ - Type: schema.TypeFloat, - }, - }, - }, - }, - }, - "number_not_in": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "values": { - Type: schema.TypeList, - Required: true, - MaxItems: 5, - Elem: &schema.Schema{ - Type: schema.TypeFloat, - }, - }, - }, - }, - }, - "string_begins_with": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "values": { - Type: schema.TypeList, - Required: true, - MaxItems: 5, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - "string_ends_with": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "values": { - Type: schema.TypeList, - Required: true, - MaxItems: 5, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - "string_contains": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "values": { - Type: schema.TypeList, - Required: true, - MaxItems: 5, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - "string_in": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "values": { - Type: schema.TypeList, - Required: true, - MaxItems: 5, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - "string_not_in": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "values": { - Type: schema.TypeList, - Required: true, - MaxItems: 5, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - }, - }, - }, + "advanced_filter": eventSubscriptionSchemaAdvancedFilter(), - "storage_blob_dead_letter_destination": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "storage_account_id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: azure.ValidateResourceID, - }, - "storage_blob_container_name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - }, - }, - }, + "storage_blob_dead_letter_destination": eventSubscriptionSchemaStorageBlobDeadletterDestination(), - "retry_policy": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "max_delivery_attempts": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 30), - }, - "event_time_to_live": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 1440), - }, - }, - }, - }, + "retry_policy": eventSubscriptionSchemaRetryPolicy(), - "labels": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, + "labels": eventSubscriptionSchemaLabels(), }, } } @@ -599,7 +172,7 @@ func resourceEventGridEventSubscriptionCreateUpdate(d *schema.ResourceData, meta destination := expandEventGridEventSubscriptionDestination(d) if destination == nil { - return fmt.Errorf("One of the following endpoint types must be specificed to create an EventGrid Event Subscription: %q", enpointPropertyNames()) + return fmt.Errorf("One of the following endpoint types must be specificed to create an EventGrid Event Subscription: %q", PossibleEventSubscriptionEndpointTypes()) } filter, err := expandEventGridEventSubscriptionFilter(d) @@ -788,579 +361,3 @@ func resourceEventGridEventSubscriptionDelete(d *schema.ResourceData, meta inter return nil } - -func expandEventGridExpirationTime(d *schema.ResourceData) (*date.Time, error) { - if expirationTimeUtc, ok := d.GetOk("expiration_time_utc"); ok { - if expirationTimeUtc == "" { - return nil, nil - } - - parsedExpirationTimeUtc, err := date.ParseTime(time.RFC3339, expirationTimeUtc.(string)) - if err != nil { - return nil, err - } - - return &date.Time{Time: parsedExpirationTimeUtc}, nil - } - - return nil, nil -} - -func expandEventGridEventSubscriptionDestination(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { - if v, ok := d.GetOk("azure_function_endpoint"); ok { - return expandEventGridEventSubscriptionAzureFunctionEndpoint(v) - } - - if v, ok := d.GetOk("eventhub_endpoint_id"); ok { - return &eventgrid.EventHubEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeEventHub, - EventHubEventSubscriptionDestinationProperties: &eventgrid.EventHubEventSubscriptionDestinationProperties{ - ResourceID: utils.String(v.(string)), - }, - } - } else if _, ok := d.GetOk("eventhub_endpoint"); ok { - return expandEventGridEventSubscriptionEventhubEndpoint(d) - } - - if v, ok := d.GetOk("hybrid_connection_endpoint_id"); ok { - return &eventgrid.HybridConnectionEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeHybridConnection, - HybridConnectionEventSubscriptionDestinationProperties: &eventgrid.HybridConnectionEventSubscriptionDestinationProperties{ - ResourceID: utils.String(v.(string)), - }, - } - } else if _, ok := d.GetOk("hybrid_connection_endpoint"); ok { - return expandEventGridEventSubscriptionHybridConnectionEndpoint(d) - } - - if v, ok := d.GetOk("service_bus_queue_endpoint_id"); ok { - return &eventgrid.ServiceBusQueueEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeServiceBusQueue, - ServiceBusQueueEventSubscriptionDestinationProperties: &eventgrid.ServiceBusQueueEventSubscriptionDestinationProperties{ - ResourceID: utils.String(v.(string)), - }, - } - } - - if v, ok := d.GetOk("service_bus_topic_endpoint_id"); ok { - return &eventgrid.ServiceBusTopicEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeServiceBusTopic, - ServiceBusTopicEventSubscriptionDestinationProperties: &eventgrid.ServiceBusTopicEventSubscriptionDestinationProperties{ - ResourceID: utils.String(v.(string)), - }, - } - } - - if _, ok := d.GetOk("storage_queue_endpoint"); ok { - return expandEventGridEventSubscriptionStorageQueueEndpoint(d) - } - - if v, ok := d.GetOk("webhook_endpoint"); ok { - return expandEventGridEventSubscriptionWebhookEndpoint(v) - } - - return nil -} - -func expandEventGridEventSubscriptionStorageQueueEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { - props := d.Get("storage_queue_endpoint").([]interface{})[0].(map[string]interface{}) - storageAccountID := props["storage_account_id"].(string) - queueName := props["queue_name"].(string) - - return eventgrid.StorageQueueEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeStorageQueue, - StorageQueueEventSubscriptionDestinationProperties: &eventgrid.StorageQueueEventSubscriptionDestinationProperties{ - ResourceID: &storageAccountID, - QueueName: &queueName, - }, - } -} - -func expandEventGridEventSubscriptionEventhubEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { - props := d.Get("eventhub_endpoint").([]interface{})[0].(map[string]interface{}) - eventHubID := props["eventhub_id"].(string) - - return eventgrid.EventHubEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeEventHub, - EventHubEventSubscriptionDestinationProperties: &eventgrid.EventHubEventSubscriptionDestinationProperties{ - ResourceID: &eventHubID, - }, - } -} - -func expandEventGridEventSubscriptionHybridConnectionEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { - props := d.Get("hybrid_connection_endpoint").([]interface{})[0].(map[string]interface{}) - hybridConnectionID := props["hybrid_connection_id"].(string) - - return eventgrid.HybridConnectionEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeHybridConnection, - HybridConnectionEventSubscriptionDestinationProperties: &eventgrid.HybridConnectionEventSubscriptionDestinationProperties{ - ResourceID: &hybridConnectionID, - }, - } -} - -func expandEventGridEventSubscriptionAzureFunctionEndpoint(input interface{}) eventgrid.BasicEventSubscriptionDestination { - configs := input.([]interface{}) - - props := eventgrid.AzureFunctionEventSubscriptionDestinationProperties{} - azureFunctionDestination := &eventgrid.AzureFunctionEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeAzureFunction, - AzureFunctionEventSubscriptionDestinationProperties: &props, - } - - if len(configs) == 0 { - return azureFunctionDestination - } - - config := configs[0].(map[string]interface{}) - - if v, ok := config["function_id"]; ok && v != "" { - props.ResourceID = utils.String(v.(string)) - } - - if v, ok := config["max_events_per_batch"]; ok && v != 0 { - props.MaxEventsPerBatch = utils.Int32(int32(v.(int))) - } - - if v, ok := config["preferred_batch_size_in_kilobytes"]; ok && v != 0 { - props.PreferredBatchSizeInKilobytes = utils.Int32(int32(v.(int))) - } - - return azureFunctionDestination -} - -func expandEventGridEventSubscriptionWebhookEndpoint(input interface{}) eventgrid.BasicEventSubscriptionDestination { - configs := input.([]interface{}) - - props := eventgrid.WebHookEventSubscriptionDestinationProperties{} - webhookDestination := &eventgrid.WebHookEventSubscriptionDestination{ - EndpointType: eventgrid.EndpointTypeWebHook, - WebHookEventSubscriptionDestinationProperties: &props, - } - - if len(configs) == 0 { - return webhookDestination - } - - config := configs[0].(map[string]interface{}) - - if v, ok := config["url"]; ok && v != "" { - props.EndpointURL = utils.String(v.(string)) - } - - if v, ok := config["max_events_per_batch"]; ok && v != 0 { - props.MaxEventsPerBatch = utils.Int32(int32(v.(int))) - } - - if v, ok := config["preferred_batch_size_in_kilobytes"]; ok && v != 0 { - props.PreferredBatchSizeInKilobytes = utils.Int32(int32(v.(int))) - } - - if v, ok := config["active_directory_tenant_id"]; ok && v != "" { - props.AzureActiveDirectoryTenantID = utils.String(v.(string)) - } - - if v, ok := config["active_directory_app_id_or_uri"]; ok && v != "" { - props.AzureActiveDirectoryApplicationIDOrURI = utils.String(v.(string)) - } - - return webhookDestination -} - -func expandEventGridEventSubscriptionFilter(d *schema.ResourceData) (*eventgrid.EventSubscriptionFilter, error) { - filter := &eventgrid.EventSubscriptionFilter{} - - if includedEvents, ok := d.GetOk("included_event_types"); ok { - filter.IncludedEventTypes = utils.ExpandStringSlice(includedEvents.([]interface{})) - } - - if v, ok := d.GetOk("subject_filter"); ok { - if v.([]interface{})[0] != nil { - config := v.([]interface{})[0].(map[string]interface{}) - subjectBeginsWith := config["subject_begins_with"].(string) - subjectEndsWith := config["subject_ends_with"].(string) - caseSensitive := config["case_sensitive"].(bool) - - filter.SubjectBeginsWith = &subjectBeginsWith - filter.SubjectEndsWith = &subjectEndsWith - filter.IsSubjectCaseSensitive = &caseSensitive - } - } - - if advancedFilter, ok := d.GetOk("advanced_filter"); ok { - advancedFilters := make([]eventgrid.BasicAdvancedFilter, 0) - for filterKey, filterSchema := range advancedFilter.([]interface{})[0].(map[string]interface{}) { - for _, options := range filterSchema.([]interface{}) { - if filter, err := expandAdvancedFilter(filterKey, options.(map[string]interface{})); err == nil { - advancedFilters = append(advancedFilters, filter) - } else { - return nil, err - } - } - } - filter.AdvancedFilters = &advancedFilters - } - - return filter, nil -} - -func expandAdvancedFilter(operatorType string, config map[string]interface{}) (eventgrid.BasicAdvancedFilter, error) { - k := config["key"].(string) - - switch operatorType { - case "bool_equals": - v := config["value"].(bool) - return eventgrid.BoolEqualsAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeBoolEquals, Value: &v}, nil - case "number_greater_than": - v := config["value"].(float64) - return eventgrid.NumberGreaterThanAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberGreaterThan, Value: &v}, nil - case "number_greater_than_or_equals": - v := config["value"].(float64) - return eventgrid.NumberGreaterThanOrEqualsAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberGreaterThanOrEquals, Value: &v}, nil - case "number_less_than": - v := config["value"].(float64) - return eventgrid.NumberLessThanAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberLessThan, Value: &v}, nil - case "number_less_than_or_equals": - v := config["value"].(float64) - return eventgrid.NumberLessThanOrEqualsAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberLessThanOrEquals, Value: &v}, nil - case "number_in": - v := utils.ExpandFloatSlice(config["values"].([]interface{})) - return eventgrid.NumberInAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberIn, Values: v}, nil - case "number_not_in": - v := utils.ExpandFloatSlice(config["values"].([]interface{})) - return eventgrid.NumberNotInAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeNumberIn, Values: v}, nil - case "string_begins_with": - v := utils.ExpandStringSlice(config["values"].([]interface{})) - return eventgrid.StringBeginsWithAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringBeginsWith, Values: v}, nil - case "string_ends_with": - v := utils.ExpandStringSlice(config["values"].([]interface{})) - return eventgrid.StringEndsWithAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringEndsWith, Values: v}, nil - case "string_contains": - v := utils.ExpandStringSlice(config["values"].([]interface{})) - return eventgrid.StringContainsAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringContains, Values: v}, nil - case "string_in": - v := utils.ExpandStringSlice(config["values"].([]interface{})) - return eventgrid.StringInAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringIn, Values: v}, nil - case "string_not_in": - v := utils.ExpandStringSlice(config["values"].([]interface{})) - return eventgrid.StringNotInAdvancedFilter{Key: &k, OperatorType: eventgrid.OperatorTypeStringNotIn, Values: v}, nil - default: - return nil, fmt.Errorf("Invalid `advanced_filter` operator_type %q used", operatorType) - } -} - -func expandEventGridEventSubscriptionStorageBlobDeadLetterDestination(d *schema.ResourceData) eventgrid.BasicDeadLetterDestination { - if v, ok := d.GetOk("storage_blob_dead_letter_destination"); ok { - dest := v.([]interface{})[0].(map[string]interface{}) - resourceID := dest["storage_account_id"].(string) - blobName := dest["storage_blob_container_name"].(string) - return eventgrid.StorageBlobDeadLetterDestination{ - EndpointType: eventgrid.EndpointTypeStorageBlob, - StorageBlobDeadLetterDestinationProperties: &eventgrid.StorageBlobDeadLetterDestinationProperties{ - ResourceID: &resourceID, - BlobContainerName: &blobName, - }, - } - } - - return nil -} - -func expandEventGridEventSubscriptionRetryPolicy(d *schema.ResourceData) *eventgrid.RetryPolicy { - if v, ok := d.GetOk("retry_policy"); ok { - dest := v.([]interface{})[0].(map[string]interface{}) - maxDeliveryAttempts := dest["max_delivery_attempts"].(int) - eventTimeToLive := dest["event_time_to_live"].(int) - return &eventgrid.RetryPolicy{ - MaxDeliveryAttempts: utils.Int32(int32(maxDeliveryAttempts)), - EventTimeToLiveInMinutes: utils.Int32(int32(eventTimeToLive)), - } - } - - return nil -} - -func flattenEventGridEventSubscriptionEventhubEndpoint(input *eventgrid.EventHubEventSubscriptionDestination) []interface{} { - if input == nil { - return nil - } - result := make(map[string]interface{}) - - if input.ResourceID != nil { - result["eventhub_id"] = *input.ResourceID - } - - return []interface{}{result} -} - -func flattenEventGridEventSubscriptionHybridConnectionEndpoint(input *eventgrid.HybridConnectionEventSubscriptionDestination) []interface{} { - if input == nil { - return nil - } - - hybridConnectionId := "" - if input.ResourceID != nil { - hybridConnectionId = *input.ResourceID - } - - return []interface{}{ - map[string]interface{}{ - "hybrid_connection_id": hybridConnectionId, - }, - } -} - -func flattenEventGridEventSubscriptionStorageQueueEndpoint(input *eventgrid.StorageQueueEventSubscriptionDestination) []interface{} { - if input == nil { - return nil - } - result := make(map[string]interface{}) - - if input.ResourceID != nil { - result["storage_account_id"] = *input.ResourceID - } - if input.QueueName != nil { - result["queue_name"] = *input.QueueName - } - - return []interface{}{result} -} - -func flattenEventGridEventSubscriptionAzureFunctionEndpoint(input *eventgrid.AzureFunctionEventSubscriptionDestination) []interface{} { - results := make([]interface{}, 0) - - if input == nil { - return results - } - - functionID := "" - if input.ResourceID != nil { - functionID = *input.ResourceID - } - - maxEventsPerBatch := 0 - if input.MaxEventsPerBatch != nil { - maxEventsPerBatch = int(*input.MaxEventsPerBatch) - } - - preferredBatchSize := 0 - if input.PreferredBatchSizeInKilobytes != nil { - preferredBatchSize = int(*input.PreferredBatchSizeInKilobytes) - } - - return append(results, map[string]interface{}{ - "function_id": functionID, - "max_events_per_batch": maxEventsPerBatch, - "preferred_batch_size_in_kilobytes": preferredBatchSize, - }) -} - -func flattenEventGridEventSubscriptionWebhookEndpoint(input *eventgrid.WebHookEventSubscriptionDestination, fullURL *eventgrid.EventSubscriptionFullURL) []interface{} { - results := make([]interface{}, 0) - - if input == nil { - return results - } - - webhookURL := "" - if fullURL != nil { - webhookURL = *fullURL.EndpointURL - } - - webhookBaseURL := "" - if input.EndpointBaseURL != nil { - webhookBaseURL = *input.EndpointBaseURL - } - - maxEventsPerBatch := 0 - if input.MaxEventsPerBatch != nil { - maxEventsPerBatch = int(*input.MaxEventsPerBatch) - } - - preferredBatchSizeInKilobytes := 0 - if input.PreferredBatchSizeInKilobytes != nil { - preferredBatchSizeInKilobytes = int(*input.PreferredBatchSizeInKilobytes) - } - - azureActiveDirectoryTenantID := "" - if input.AzureActiveDirectoryTenantID != nil { - azureActiveDirectoryTenantID = *input.AzureActiveDirectoryTenantID - } - - azureActiveDirectoryApplicationIDOrURI := "" - if input.AzureActiveDirectoryApplicationIDOrURI != nil { - azureActiveDirectoryApplicationIDOrURI = *input.AzureActiveDirectoryApplicationIDOrURI - } - - return append(results, map[string]interface{}{ - "url": webhookURL, - "base_url": webhookBaseURL, - "max_events_per_batch": maxEventsPerBatch, - "preferred_batch_size_in_kilobytes": preferredBatchSizeInKilobytes, - "active_directory_tenant_id": azureActiveDirectoryTenantID, - "active_directory_app_id_or_uri": azureActiveDirectoryApplicationIDOrURI, - }) -} - -func flattenEventGridEventSubscriptionSubjectFilter(filter *eventgrid.EventSubscriptionFilter) []interface{} { - if (filter.SubjectBeginsWith != nil && *filter.SubjectBeginsWith == "") && (filter.SubjectEndsWith != nil && *filter.SubjectEndsWith == "") { - return nil - } - result := make(map[string]interface{}) - - if filter.SubjectBeginsWith != nil { - result["subject_begins_with"] = *filter.SubjectBeginsWith - } - - if filter.SubjectEndsWith != nil { - result["subject_ends_with"] = *filter.SubjectEndsWith - } - - if filter.IsSubjectCaseSensitive != nil { - result["case_sensitive"] = *filter.IsSubjectCaseSensitive - } - - return []interface{}{result} -} - -func flattenEventGridEventSubscriptionAdvancedFilter(input *eventgrid.EventSubscriptionFilter) []interface{} { - results := make([]interface{}, 0) - if input == nil || input.AdvancedFilters == nil { - return results - } - - boolEquals := make([]interface{}, 0) - numberGreaterThan := make([]interface{}, 0) - numberGreaterThanOrEquals := make([]interface{}, 0) - numberLessThan := make([]interface{}, 0) - numberLessThanOrEquals := make([]interface{}, 0) - numberIn := make([]interface{}, 0) - numberNotIn := make([]interface{}, 0) - stringBeginsWith := make([]interface{}, 0) - stringEndsWith := make([]interface{}, 0) - stringContains := make([]interface{}, 0) - stringIn := make([]interface{}, 0) - stringNotIn := make([]interface{}, 0) - - for _, item := range *input.AdvancedFilters { - switch f := item.(type) { - case eventgrid.BoolEqualsAdvancedFilter: - v := interface{}(f.Value) - boolEquals = append(boolEquals, flattenValue(f.Key, &v)) - case eventgrid.NumberGreaterThanAdvancedFilter: - v := interface{}(f.Value) - numberGreaterThan = append(numberGreaterThan, flattenValue(f.Key, &v)) - case eventgrid.NumberGreaterThanOrEqualsAdvancedFilter: - v := interface{}(f.Value) - numberGreaterThanOrEquals = append(numberGreaterThanOrEquals, flattenValue(f.Key, &v)) - case eventgrid.NumberLessThanAdvancedFilter: - v := interface{}(f.Value) - numberLessThan = append(numberLessThan, flattenValue(f.Key, &v)) - case eventgrid.NumberLessThanOrEqualsAdvancedFilter: - v := interface{}(f.Value) - numberLessThanOrEquals = append(numberLessThanOrEquals, flattenValue(f.Key, &v)) - case eventgrid.NumberInAdvancedFilter: - v := utils.FlattenFloatSlice(f.Values) - numberIn = append(numberIn, flattenValues(f.Key, &v)) - case eventgrid.NumberNotInAdvancedFilter: - v := utils.FlattenFloatSlice(f.Values) - numberNotIn = append(numberNotIn, flattenValues(f.Key, &v)) - case eventgrid.StringBeginsWithAdvancedFilter: - v := utils.FlattenStringSlice(f.Values) - stringBeginsWith = append(stringBeginsWith, flattenValues(f.Key, &v)) - case eventgrid.StringEndsWithAdvancedFilter: - v := utils.FlattenStringSlice(f.Values) - stringEndsWith = append(stringEndsWith, flattenValues(f.Key, &v)) - case eventgrid.StringContainsAdvancedFilter: - v := utils.FlattenStringSlice(f.Values) - stringContains = append(stringContains, flattenValues(f.Key, &v)) - case eventgrid.StringInAdvancedFilter: - v := utils.FlattenStringSlice(f.Values) - stringIn = append(stringIn, flattenValues(f.Key, &v)) - case eventgrid.StringNotInAdvancedFilter: - v := utils.FlattenStringSlice(f.Values) - stringNotIn = append(stringNotIn, flattenValues(f.Key, &v)) - } - } - - return []interface{}{ - map[string][]interface{}{ - "bool_equals": boolEquals, - "number_greater_than": numberGreaterThan, - "number_greater_than_or_equals": numberGreaterThanOrEquals, - "number_less_than": numberLessThan, - "number_less_than_or_equals": numberLessThanOrEquals, - "number_in": numberIn, - "number_not_in": numberNotIn, - "string_begins_with": stringBeginsWith, - "string_ends_with": stringEndsWith, - "string_contains": stringContains, - "string_in": stringIn, - "string_not_in": stringNotIn, - }, - } -} - -func flattenEventGridEventSubscriptionStorageBlobDeadLetterDestination(dest *eventgrid.StorageBlobDeadLetterDestination) []interface{} { - if dest == nil { - return nil - } - result := make(map[string]interface{}) - - if dest.ResourceID != nil { - result["storage_account_id"] = *dest.ResourceID - } - - if dest.BlobContainerName != nil { - result["storage_blob_container_name"] = *dest.BlobContainerName - } - - return []interface{}{result} -} - -func flattenEventGridEventSubscriptionRetryPolicy(retryPolicy *eventgrid.RetryPolicy) []interface{} { - result := make(map[string]interface{}) - - if v := retryPolicy.EventTimeToLiveInMinutes; v != nil { - result["event_time_to_live"] = int(*v) - } - - if v := retryPolicy.MaxDeliveryAttempts; v != nil { - result["max_delivery_attempts"] = int(*v) - } - - return []interface{}{result} -} - -func flattenValue(inputKey *string, inputValue *interface{}) map[string]interface{} { - key := "" - if inputKey != nil { - key = *inputKey - } - var value interface{} - if inputValue != nil { - value = inputValue - } - - return map[string]interface{}{ - "key": key, - "value": value, - } -} - -func flattenValues(inputKey *string, inputValues *[]interface{}) map[string]interface{} { - key := "" - if inputKey != nil { - key = *inputKey - } - values := make([]interface{}, 0) - if inputValues != nil { - values = *inputValues - } - - return map[string]interface{}{ - "key": key, - "values": values, - } -} diff --git a/azurerm/internal/services/eventgrid/eventgrid_system_topic_event_subscription_resource.go b/azurerm/internal/services/eventgrid/eventgrid_system_topic_event_subscription_resource.go new file mode 100644 index 000000000000..c9828ae9d961 --- /dev/null +++ b/azurerm/internal/services/eventgrid/eventgrid_system_topic_event_subscription_resource.go @@ -0,0 +1,337 @@ +package eventgrid + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/eventgrid/mgmt/2020-04-01-preview/eventgrid" + "github.com/hashicorp/go-azure-helpers/response" + "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/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/eventgrid/parse" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func PossibleSystemTopicEventSubscriptionEndpointTypes() []string { + return []string{ + string(AzureFunctionEndpoint), + string(EventHubEndpointID), + string(HybridConnectionEndpointID), + string(ServiceBusQueueEndpointID), + string(ServiceBusTopicEndpointID), + string(StorageQueueEndpoint), + string(WebHookEndpoint), + } +} + +func resourceEventGridSystemTopicEventSubscription() *schema.Resource { + return &schema.Resource{ + Create: resourceEventGridSystemTopicEventSubscriptionCreateUpdate, + Read: resourceEventGridSystemTopicEventSubscriptionRead, + Update: resourceEventGridSystemTopicEventSubscriptionCreateUpdate, + Delete: resourceEventGridSystemTopicEventSubscriptionDelete, + + 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), + }, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.SystemTopicEventSubscriptionID(id) + return err + }), + + Schema: map[string]*schema.Schema{ + "name": eventSubscriptionSchemaEventSubscriptionName(), + + "system_topic": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "event_delivery_schema": eventSubscriptionSchemaEventDeliverySchema(), + + "expiration_time_utc": eventSubscriptionSchemaExpirationTimeUTC(), + + "azure_function_endpoint": eventSubscriptionSchemaAzureFunctionEndpoint( + utils.RemoveFromStringArray( + PossibleSystemTopicEventSubscriptionEndpointTypes(), + string(AzureFunctionEndpoint), + ), + ), + + "eventhub_endpoint_id": eventSubscriptionSchemaEventHubEndpointID( + utils.RemoveFromStringArray( + PossibleSystemTopicEventSubscriptionEndpointTypes(), + string(EventHubEndpointID), + ), + ), + + "hybrid_connection_endpoint_id": eventSubscriptionSchemaHybridConnectionEndpointID( + utils.RemoveFromStringArray( + PossibleSystemTopicEventSubscriptionEndpointTypes(), + string(HybridConnectionEndpointID), + ), + ), + + "service_bus_queue_endpoint_id": eventSubscriptionSchemaServiceBusQueueEndpointID( + utils.RemoveFromStringArray( + PossibleSystemTopicEventSubscriptionEndpointTypes(), + string(ServiceBusQueueEndpointID), + ), + ), + + "service_bus_topic_endpoint_id": eventSubscriptionSchemaServiceBusTopicEndpointID( + utils.RemoveFromStringArray( + PossibleSystemTopicEventSubscriptionEndpointTypes(), + string(ServiceBusTopicEndpointID), + ), + ), + + "storage_queue_endpoint": eventSubscriptionSchemaStorageQueueEndpoint( + utils.RemoveFromStringArray( + PossibleSystemTopicEventSubscriptionEndpointTypes(), + string(StorageQueueEndpoint), + ), + ), + + "webhook_endpoint": eventSubscriptionSchemaWebHookEndpoint( + utils.RemoveFromStringArray( + PossibleSystemTopicEventSubscriptionEndpointTypes(), + string(WebHookEndpoint), + ), + ), + + "included_event_types": eventSubscriptionSchemaIncludedEventTypes(), + + "subject_filter": eventSubscriptionSchemaSubjectFilter(), + + "advanced_filter": eventSubscriptionSchemaAdvancedFilter(), + + "storage_blob_dead_letter_destination": eventSubscriptionSchemaStorageBlobDeadletterDestination(), + + "retry_policy": eventSubscriptionSchemaRetryPolicy(), + + "labels": eventSubscriptionSchemaLabels(), + }, + } +} + +func resourceEventGridSystemTopicEventSubscriptionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).EventGrid.SystemTopicEventSubscriptionsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + systemTopic := d.Get("system_topic").(string) + + if d.IsNewResource() { + existing, err := client.Get(ctx, resourceGroup, systemTopic, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing EventGrid System Topic Event Subscription %q (System Topic %q): %s", name, systemTopic, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_eventgrid_system_topic_event_subscription", *existing.ID) + } + } + + destination := expandEventGridEventSubscriptionDestination(d) + if destination == nil { + return fmt.Errorf("One of the following endpoint types must be specificed to create an EventGrid System Topic Event Subscription: %q", PossibleSystemTopicEventSubscriptionEndpointTypes()) + } + + filter, err := expandEventGridEventSubscriptionFilter(d) + if err != nil { + return fmt.Errorf("expanding filters for EventGrid System Topic Event Subscription %q (System Topic %q): %+v", name, systemTopic, err) + } + + expirationTime, err := expandEventGridExpirationTime(d) + if err != nil { + return fmt.Errorf("Error creating/updating EventGrid System Topic Event Subscription %q (System Topic %q): %s", name, systemTopic, err) + } + + eventSubscriptionProperties := eventgrid.EventSubscriptionProperties{ + Destination: destination, + Filter: filter, + DeadLetterDestination: expandEventGridEventSubscriptionStorageBlobDeadLetterDestination(d), + RetryPolicy: expandEventGridEventSubscriptionRetryPolicy(d), + Labels: utils.ExpandStringSlice(d.Get("labels").([]interface{})), + EventDeliverySchema: eventgrid.EventDeliverySchema(d.Get("event_delivery_schema").(string)), + ExpirationTimeUtc: expirationTime, + } + + eventSubscription := eventgrid.EventSubscription{ + EventSubscriptionProperties: &eventSubscriptionProperties, + } + + log.Printf("[INFO] preparing arguments for AzureRM EventGrid System Topic Event Subscription creation with Properties: %+v.", eventSubscription) + + future, err := client.CreateOrUpdate(ctx, resourceGroup, systemTopic, name, eventSubscription) + if err != nil { + return fmt.Errorf("Error creating/updating EventGrid System Topic Event Subscription %q (System Topic %q): %s", name, systemTopic, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for EventGrid System Topic Event Subscription %q (System Topic %q) to become available: %s", name, systemTopic, err) + } + + read, err := client.Get(ctx, resourceGroup, systemTopic, name) + if err != nil { + return fmt.Errorf("Error retrieving EventGrid System Topic Event Subscription %q (System Topic %q): %s", name, systemTopic, err) + } + if read.ID == nil { + return fmt.Errorf("Cannot read EventGrid System Topic Event Subscription %s (System Topic %s) ID", name, systemTopic) + } + + d.SetId(*read.ID) + + return resourceEventGridSystemTopicEventSubscriptionRead(d, meta) +} + +func resourceEventGridSystemTopicEventSubscriptionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).EventGrid.SystemTopicEventSubscriptionsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SystemTopicEventSubscriptionID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.SystemTopic, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[WARN] EventGrid System Topic Event Subscription '%q' was not found (System Topic %q)", id.Name, id.SystemTopic) + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on EventGrid System Topic Event Subscription '%q' (System Topic %q): %+v", id.Name, id.SystemTopic, err) + } + + d.Set("name", resp.Name) + d.Set("system_topic", id.SystemTopic) + d.Set("resource_group_name", id.ResourceGroup) + + if props := resp.EventSubscriptionProperties; props != nil { + if props.ExpirationTimeUtc != nil { + d.Set("expiration_time_utc", props.ExpirationTimeUtc.Format(time.RFC3339)) + } + + d.Set("event_delivery_schema", string(props.EventDeliverySchema)) + + if azureFunctionEndpoint, ok := props.Destination.AsAzureFunctionEventSubscriptionDestination(); ok { + if err := d.Set("azure_function_endpoint", flattenEventGridEventSubscriptionAzureFunctionEndpoint(azureFunctionEndpoint)); err != nil { + return fmt.Errorf("Error setting `%q` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", "azure_function_endpoint", id.Name, id.SystemTopic, err) + } + } + if v, ok := props.Destination.AsEventHubEventSubscriptionDestination(); ok { + if err := d.Set("eventhub_endpoint_id", v.ResourceID); err != nil { + return fmt.Errorf("Error setting `%q` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", "eventhub_endpoint_id", id.Name, id.SystemTopic, err) + } + } + if v, ok := props.Destination.AsHybridConnectionEventSubscriptionDestination(); ok { + if err := d.Set("hybrid_connection_endpoint_id", v.ResourceID); err != nil { + return fmt.Errorf("Error setting `%q` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", "hybrid_connection_endpoint_id", id.Name, id.SystemTopic, err) + } + } + if serviceBusQueueEndpoint, ok := props.Destination.AsServiceBusQueueEventSubscriptionDestination(); ok { + if err := d.Set("service_bus_queue_endpoint_id", serviceBusQueueEndpoint.ResourceID); err != nil { + return fmt.Errorf("Error setting `%q` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", "service_bus_queue_endpoint_id", id.Name, id.SystemTopic, err) + } + } + if serviceBusTopicEndpoint, ok := props.Destination.AsServiceBusTopicEventSubscriptionDestination(); ok { + if err := d.Set("service_bus_topic_endpoint_id", serviceBusTopicEndpoint.ResourceID); err != nil { + return fmt.Errorf("Error setting `%q` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", "service_bus_topic_endpoint_id", id.Name, id.SystemTopic, err) + } + } + if v, ok := props.Destination.AsStorageQueueEventSubscriptionDestination(); ok { + if err := d.Set("storage_queue_endpoint", flattenEventGridEventSubscriptionStorageQueueEndpoint(v)); err != nil { + return fmt.Errorf("Error setting `%q` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", "storage_queue_endpoint", id.Name, id.SystemTopic, err) + } + } + if v, ok := props.Destination.AsWebHookEventSubscriptionDestination(); ok { + fullURL, err := client.GetFullURL(ctx, id.ResourceGroup, id.SystemTopic, id.Name) + if err != nil { + return fmt.Errorf("Error making Read request on EventGrid System Topic Event Subscription full URL '%s': %+v", id.Name, err) + } + if err := d.Set("webhook_endpoint", flattenEventGridEventSubscriptionWebhookEndpoint(v, &fullURL)); err != nil { + return fmt.Errorf("Error setting `%q` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", "webhook_endpoint", id.Name, id.SystemTopic, err) + } + } + + if filter := props.Filter; filter != nil { + d.Set("included_event_types", filter.IncludedEventTypes) + if err := d.Set("subject_filter", flattenEventGridEventSubscriptionSubjectFilter(filter)); err != nil { + return fmt.Errorf("Error setting `subject_filter` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", id.Name, id.SystemTopic, err) + } + if err := d.Set("advanced_filter", flattenEventGridEventSubscriptionAdvancedFilter(filter)); err != nil { + return fmt.Errorf("Error setting `advanced_filter` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", id.Name, id.SystemTopic, err) + } + } + + if props.DeadLetterDestination != nil { + if storageBlobDeadLetterDestination, ok := props.DeadLetterDestination.AsStorageBlobDeadLetterDestination(); ok { + if err := d.Set("storage_blob_dead_letter_destination", flattenEventGridEventSubscriptionStorageBlobDeadLetterDestination(storageBlobDeadLetterDestination)); err != nil { + return fmt.Errorf("Error setting `storage_blob_dead_letter_destination` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", id.Name, id.SystemTopic, err) + } + } + } + + if retryPolicy := props.RetryPolicy; retryPolicy != nil { + if err := d.Set("retry_policy", flattenEventGridEventSubscriptionRetryPolicy(retryPolicy)); err != nil { + return fmt.Errorf("Error setting `retry_policy` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", id.Name, id.SystemTopic, err) + } + } + + if err := d.Set("labels", props.Labels); err != nil { + return fmt.Errorf("Error setting `labels` for EventGrid System Topic Event Subscription %q (System Topic %q): %s", id.Name, id.SystemTopic, err) + } + } + + return nil +} + +func resourceEventGridSystemTopicEventSubscriptionDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).EventGrid.SystemTopicEventSubscriptionsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SystemTopicEventSubscriptionID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.SystemTopic, id.Name) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("Error deleting Event Grid System Topic Event Subscription %q (System Topic %q): %+v", id.Name, id.SystemTopic, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("Error deleting Event Grid System Topic Event Subscription %q (System Topic %q): %+v", id.Name, id.SystemTopic, err) + } + + return nil +} diff --git a/azurerm/internal/services/eventgrid/parse/system_topic_event_subscription.go b/azurerm/internal/services/eventgrid/parse/system_topic_event_subscription.go new file mode 100644 index 000000000000..a9958078b11e --- /dev/null +++ b/azurerm/internal/services/eventgrid/parse/system_topic_event_subscription.go @@ -0,0 +1,38 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type SystemTopicEventSubscriptionId struct { + ResourceGroup string + SystemTopic string + Name string +} + +func SystemTopicEventSubscriptionID(input string) (*SystemTopicEventSubscriptionId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("[ERROR] Unable to parse EventGrid System Topic Event Subscription ID %q: %+v", input, err) + } + + systemTopicEventSubscriptionID := SystemTopicEventSubscriptionId{ + ResourceGroup: id.ResourceGroup, + } + + if systemTopicEventSubscriptionID.SystemTopic, err = id.PopSegment("systemTopics"); err != nil { + return nil, err + } + + if systemTopicEventSubscriptionID.Name, err = id.PopSegment("eventSubscriptions"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &systemTopicEventSubscriptionID, nil +} diff --git a/azurerm/internal/services/eventgrid/parse/system_topic_event_subscription_test.go b/azurerm/internal/services/eventgrid/parse/system_topic_event_subscription_test.go new file mode 100644 index 000000000000..a4b72d6f154f --- /dev/null +++ b/azurerm/internal/services/eventgrid/parse/system_topic_event_subscription_test.go @@ -0,0 +1,58 @@ +package parse + +import ( + "testing" +) + +func TestSystenTopicEventGridEventSubscriptionId(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *SystemTopicEventSubscriptionId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/providers/Microsoft.EventGrid/systemTopics/topic1/eventSubscriptions/subscription1", + Expected: nil, + }, + { + Name: "Event Grid System Topic Scope", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.EventGrid/systemTopics/topic1/eventSubscriptions/subscription1", + Expected: &SystemTopicEventSubscriptionId{ + Name: "subscription1", + SystemTopic: "topic1", + ResourceGroup: "resGroup1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Name) + + actual, err := SystemTopicEventSubscriptionID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + + if actual.SystemTopic != v.Expected.SystemTopic { + t.Fatalf("Expected %q but got %q for System Topic", v.Expected.SystemTopic, actual.SystemTopic) + } + + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for Resource Group", v.Expected.ResourceGroup, actual.ResourceGroup) + } + } +} diff --git a/azurerm/internal/services/eventgrid/registration.go b/azurerm/internal/services/eventgrid/registration.go index 08c34267cae7..e81b33ddbdc4 100644 --- a/azurerm/internal/services/eventgrid/registration.go +++ b/azurerm/internal/services/eventgrid/registration.go @@ -28,10 +28,11 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_eventgrid_domain": resourceEventGridDomain(), - "azurerm_eventgrid_domain_topic": resourceEventGridDomainTopic(), - "azurerm_eventgrid_event_subscription": resourceEventGridEventSubscription(), - "azurerm_eventgrid_topic": resourceEventGridTopic(), - "azurerm_eventgrid_system_topic": resourceEventGridSystemTopic(), + "azurerm_eventgrid_domain": resourceEventGridDomain(), + "azurerm_eventgrid_domain_topic": resourceEventGridDomainTopic(), + "azurerm_eventgrid_event_subscription": resourceEventGridEventSubscription(), + "azurerm_eventgrid_topic": resourceEventGridTopic(), + "azurerm_eventgrid_system_topic": resourceEventGridSystemTopic(), + "azurerm_eventgrid_system_topic_event_subscription": resourceEventGridSystemTopicEventSubscription(), } } diff --git a/azurerm/internal/services/eventgrid/eventgrid_domain_resource_test.go b/azurerm/internal/services/eventgrid/tests/eventgrid_domain_resource_test.go similarity index 100% rename from azurerm/internal/services/eventgrid/eventgrid_domain_resource_test.go rename to azurerm/internal/services/eventgrid/tests/eventgrid_domain_resource_test.go diff --git a/azurerm/internal/services/eventgrid/eventgrid_domain_topic_resource_test.go b/azurerm/internal/services/eventgrid/tests/eventgrid_domain_topic_resource_test.go similarity index 100% rename from azurerm/internal/services/eventgrid/eventgrid_domain_topic_resource_test.go rename to azurerm/internal/services/eventgrid/tests/eventgrid_domain_topic_resource_test.go diff --git a/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource_test.go b/azurerm/internal/services/eventgrid/tests/eventgrid_event_subscription_resource_test.go similarity index 93% rename from azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource_test.go rename to azurerm/internal/services/eventgrid/tests/eventgrid_event_subscription_resource_test.go index 6b8f50d8b5f8..026aa370c2f1 100644 --- a/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource_test.go +++ b/azurerm/internal/services/eventgrid/tests/eventgrid_event_subscription_resource_test.go @@ -223,8 +223,8 @@ resource "azurerm_resource_group" "test" { resource "azurerm_storage_account" "test" { name = "acctestacc%s" - resource_group_name = "${azurerm_resource_group.test.name}" - location = "${azurerm_resource_group.test.location}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location account_tier = "Standard" account_replication_type = "LRS" @@ -235,20 +235,20 @@ resource "azurerm_storage_account" "test" { resource "azurerm_storage_queue" "test" { name = "mysamplequeue-%d" - storage_account_name = "${azurerm_storage_account.test.name}" + storage_account_name = azurerm_storage_account.test.name } resource "azurerm_storage_container" "test" { name = "vhds" - storage_account_name = "${azurerm_storage_account.test.name}" + storage_account_name = azurerm_storage_account.test.name container_access_type = "private" } resource "azurerm_storage_blob" "test" { name = "herpderp1.vhd" - storage_account_name = "${azurerm_storage_account.test.name}" - storage_container_name = "${azurerm_storage_container.test.name}" + storage_account_name = azurerm_storage_account.test.name + storage_container_name = azurerm_storage_container.test.name type = "Page" size = 5120 @@ -256,16 +256,16 @@ resource "azurerm_storage_blob" "test" { resource "azurerm_eventgrid_event_subscription" "test" { name = "acctesteg-%d" - scope = "${azurerm_resource_group.test.id}" + scope = azurerm_resource_group.test.id storage_queue_endpoint { - storage_account_id = "${azurerm_storage_account.test.id}" - queue_name = "${azurerm_storage_queue.test.name}" + storage_account_id = azurerm_storage_account.test.id + queue_name = azurerm_storage_queue.test.name } storage_blob_dead_letter_destination { - storage_account_id = "${azurerm_storage_account.test.id}" - storage_blob_container_name = "${azurerm_storage_container.test.name}" + storage_account_id = azurerm_storage_account.test.id + storage_blob_container_name = azurerm_storage_container.test.name } retry_policy { @@ -474,8 +474,8 @@ resource "azurerm_resource_group" "test" { resource "azurerm_storage_account" "test" { name = "acctestacc%s" - resource_group_name = "${azurerm_resource_group.test.name}" - location = "${azurerm_resource_group.test.location}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location account_tier = "Standard" account_replication_type = "LRS" @@ -486,16 +486,16 @@ resource "azurerm_storage_account" "test" { resource "azurerm_storage_queue" "test" { name = "mysamplequeue-%d" - storage_account_name = "${azurerm_storage_account.test.name}" + storage_account_name = azurerm_storage_account.test.name } resource "azurerm_eventgrid_event_subscription" "test" { name = "acctest-eg-%d" - scope = "${azurerm_resource_group.test.id}" + scope = azurerm_resource_group.test.id storage_queue_endpoint { - storage_account_id = "${azurerm_storage_account.test.id}" - queue_name = "${azurerm_storage_queue.test.name}" + storage_account_id = azurerm_storage_account.test.id + queue_name = azurerm_storage_queue.test.name } included_event_types = ["Microsoft.Storage.BlobCreated", "Microsoft.Storage.BlobDeleted"] @@ -521,8 +521,8 @@ resource "azurerm_resource_group" "test" { resource "azurerm_storage_account" "test" { name = "acctestacc%s" - resource_group_name = "${azurerm_resource_group.test.name}" - location = "${azurerm_resource_group.test.location}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location account_tier = "Standard" account_replication_type = "LRS" @@ -533,16 +533,16 @@ resource "azurerm_storage_account" "test" { resource "azurerm_storage_queue" "test" { name = "mysamplequeue-%d" - storage_account_name = "${azurerm_storage_account.test.name}" + storage_account_name = azurerm_storage_account.test.name } resource "azurerm_eventgrid_event_subscription" "test" { name = "acctesteg-%d" - scope = "${azurerm_storage_account.test.id}" + scope = azurerm_storage_account.test.id storage_queue_endpoint { - storage_account_id = "${azurerm_storage_account.test.id}" - queue_name = "${azurerm_storage_queue.test.name}" + storage_account_id = azurerm_storage_account.test.id + queue_name = azurerm_storage_queue.test.name } advanced_filter { diff --git a/azurerm/internal/services/eventgrid/tests/eventgrid_system_topic_event_subscription_resource_test.go b/azurerm/internal/services/eventgrid/tests/eventgrid_system_topic_event_subscription_resource_test.go new file mode 100644 index 000000000000..657f86ec3817 --- /dev/null +++ b/azurerm/internal/services/eventgrid/tests/eventgrid_system_topic_event_subscription_resource_test.go @@ -0,0 +1,675 @@ +package eventgrid_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/eventgrid/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type EventGridSystemTopicEventSubscriptionResource struct { +} + +func TestAccEventGridSystemTopicEventSubscription_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_system_topic_event_subscription", "test") + r := EventGridSystemTopicEventSubscriptionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("event_delivery_schema").HasValue("EventGridSchema"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccEventGridSystemTopicEventSubscription_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_system_topic_event_subscription", "test") + r := EventGridSystemTopicEventSubscriptionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.requiresImport(data), + ExpectError: acceptance.RequiresImportError("azurerm_eventgrid_system_topic_event_subscription"), + }, + }) +} + +func TestAccEventGridSystemTopicEventSubscription_eventHubID(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_system_topic_event_subscription", "test") + r := EventGridSystemTopicEventSubscriptionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.eventHubID(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("event_delivery_schema").HasValue("CloudEventSchemaV1_0"), + check.That(data.ResourceName).Key("eventhub_endpoint_id").Exists(), + ), + }, + data.ImportStep(), + }) +} + +func TestAccEventGridSystemTopicEventSubscription_serviceBusQueueID(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_system_topic_event_subscription", "test") + r := EventGridSystemTopicEventSubscriptionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.serviceBusQueueID(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("event_delivery_schema").HasValue("CloudEventSchemaV1_0"), + check.That(data.ResourceName).Key("service_bus_queue_endpoint_id").Exists(), + ), + }, + data.ImportStep(), + }) +} + +func TestAccEventGridSystemTopicEventSubscription_serviceBusTopicID(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_system_topic_event_subscription", "test") + r := EventGridSystemTopicEventSubscriptionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.serviceBusTopicID(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("event_delivery_schema").HasValue("CloudEventSchemaV1_0"), + check.That(data.ResourceName).Key("service_bus_topic_endpoint_id").Exists(), + ), + }, + data.ImportStep(), + }) +} + +func TestAccEventGridSystemTopicEventSubscription_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_system_topic_event_subscription", "test") + r := EventGridSystemTopicEventSubscriptionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("event_delivery_schema").HasValue("EventGridSchema"), + check.That(data.ResourceName).Key("storage_queue_endpoint.#").HasValue("1"), + check.That(data.ResourceName).Key("storage_blob_dead_letter_destination.#").HasValue("1"), + check.That(data.ResourceName).Key("included_event_types.0").HasValue("Microsoft.Resources.ResourceWriteSuccess"), + check.That(data.ResourceName).Key("retry_policy.0.max_delivery_attempts").HasValue("11"), + check.That(data.ResourceName).Key("retry_policy.0.event_time_to_live").HasValue("11"), + check.That(data.ResourceName).Key("labels.0").HasValue("test"), + check.That(data.ResourceName).Key("labels.2").HasValue("test2"), + ), + }, + { + Config: r.update(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("included_event_types.0").HasValue("Microsoft.Storage.BlobCreated"), + check.That(data.ResourceName).Key("included_event_types.1").HasValue("Microsoft.Storage.BlobDeleted"), + check.That(data.ResourceName).Key("subject_filter.0.subject_ends_with").HasValue(".jpg"), + check.That(data.ResourceName).Key("subject_filter.0.subject_begins_with").HasValue("test/test"), + check.That(data.ResourceName).Key("retry_policy.0.max_delivery_attempts").HasValue("10"), + check.That(data.ResourceName).Key("retry_policy.0.event_time_to_live").HasValue("12"), + check.That(data.ResourceName).Key("labels.0").HasValue("test4"), + check.That(data.ResourceName).Key("labels.2").HasValue("test6"), + ), + }, + }) +} + +func TestAccEventGridSystemTopicEventSubscription_filter(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_system_topic_event_subscription", "test") + r := EventGridSystemTopicEventSubscriptionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.filter(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("included_event_types.0").HasValue("Microsoft.Storage.BlobCreated"), + check.That(data.ResourceName).Key("included_event_types.1").HasValue("Microsoft.Storage.BlobDeleted"), + check.That(data.ResourceName).Key("subject_filter.0.subject_ends_with").HasValue(".jpg"), + check.That(data.ResourceName).Key("subject_filter.0.subject_begins_with").HasValue("test/test"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccEventGridSystemTopicEventSubscription_advancedFilter(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_system_topic_event_subscription", "test") + r := EventGridSystemTopicEventSubscriptionResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.advancedFilter(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("advanced_filter.0.bool_equals.0.key").HasValue("subject"), + check.That(data.ResourceName).Key("advanced_filter.0.bool_equals.0.value").HasValue("true"), + check.That(data.ResourceName).Key("advanced_filter.0.number_greater_than.0.key").HasValue("data.metadataVersion"), + check.That(data.ResourceName).Key("advanced_filter.0.number_greater_than.0.value").HasValue("1"), + check.That(data.ResourceName).Key("advanced_filter.0.number_greater_than_or_equals.0.key").HasValue("data.contentLength"), + check.That(data.ResourceName).Key("advanced_filter.0.number_greater_than_or_equals.0.value").HasValue("42"), + check.That(data.ResourceName).Key("advanced_filter.0.number_less_than.0.key").HasValue("data.contentLength"), + check.That(data.ResourceName).Key("advanced_filter.0.number_less_than.0.value").HasValue("42.1"), + check.That(data.ResourceName).Key("advanced_filter.0.number_less_than_or_equals.0.key").HasValue("data.metadataVersion"), + check.That(data.ResourceName).Key("advanced_filter.0.number_less_than_or_equals.0.value").HasValue("2"), + check.That(data.ResourceName).Key("advanced_filter.0.number_in.0.key").HasValue("data.contentLength"), + check.That(data.ResourceName).Key("advanced_filter.0.number_in.0.values.0").HasValue("0"), + check.That(data.ResourceName).Key("advanced_filter.0.number_not_in.0.key").HasValue("data.contentLength"), + check.That(data.ResourceName).Key("advanced_filter.0.number_not_in.0.values.0").HasValue("5"), + check.That(data.ResourceName).Key("advanced_filter.0.string_begins_with.0.key").HasValue("subject"), + check.That(data.ResourceName).Key("advanced_filter.0.string_begins_with.0.values.0").HasValue("foo"), + check.That(data.ResourceName).Key("advanced_filter.0.string_ends_with.0.key").HasValue("subject"), + check.That(data.ResourceName).Key("advanced_filter.0.string_ends_with.0.values.0").HasValue("bar"), + check.That(data.ResourceName).Key("advanced_filter.0.string_contains.0.key").HasValue("data.contentType"), + check.That(data.ResourceName).Key("advanced_filter.0.string_contains.0.values.0").HasValue("application"), + check.That(data.ResourceName).Key("advanced_filter.0.string_in.0.key").HasValue("data.blobType"), + check.That(data.ResourceName).Key("advanced_filter.0.string_in.0.values.0").HasValue("Block"), + check.That(data.ResourceName).Key("advanced_filter.0.string_not_in.0.key").HasValue("data.blobType"), + check.That(data.ResourceName).Key("advanced_filter.0.string_not_in.0.values.0").HasValue("Page"), + ), + }, + data.ImportStep(), + }) +} + +func (EventGridSystemTopicEventSubscriptionResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.SystemTopicEventSubscriptionID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.EventGrid.SystemTopicEventSubscriptionsClient.Get(ctx, id.ResourceGroup, id.SystemTopic, id.Name) + if err != nil { + return nil, fmt.Errorf("retrieving EventGrid System Topic Event Subscription %q (System Topic: %q): %+v", id.Name, id.SystemTopic, err) + } + + return utils.Bool(resp.EventSubscriptionProperties != nil), nil +} + +func (EventGridSystemTopicEventSubscriptionResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-eg-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_storage_queue" "test" { + name = "mysamplequeue-%[1]d" + storage_account_name = azurerm_storage_account.test.name +} + +resource "azurerm_storage_container" "test" { + name = "vhds" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_blob" "test" { + name = "herpderp1.vhd" + + storage_account_name = azurerm_storage_account.test.name + storage_container_name = azurerm_storage_container.test.name + + type = "Page" + size = 5120 +} + +resource "azurerm_eventgrid_system_topic" "test" { + name = "acctesteg-%[1]d" + location = "Global" + resource_group_name = azurerm_resource_group.test.name + source_arm_resource_id = azurerm_resource_group.test.id + topic_type = "Microsoft.Resources.ResourceGroups" +} + +resource "azurerm_eventgrid_system_topic_event_subscription" "test" { + name = "acctesteg-%[1]d" + system_topic = azurerm_eventgrid_system_topic.test.name + resource_group_name = azurerm_resource_group.test.name + + storage_queue_endpoint { + storage_account_id = azurerm_storage_account.test.id + queue_name = azurerm_storage_queue.test.name + } + + storage_blob_dead_letter_destination { + storage_account_id = azurerm_storage_account.test.id + storage_blob_container_name = azurerm_storage_container.test.name + } + + retry_policy { + event_time_to_live = 11 + max_delivery_attempts = 11 + } + + labels = ["test", "test1", "test2"] +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (EventGridSystemTopicEventSubscriptionResource) requiresImport(data acceptance.TestData) string { + template := EventGridSystemTopicEventSubscriptionResource{}.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_eventgrid_system_topic_event_subscription" "import" { + name = azurerm_eventgrid_system_topic_event_subscription.test.name + system_topic = azurerm_eventgrid_system_topic_event_subscription.test.system_topic + resource_group_name = azurerm_eventgrid_system_topic_event_subscription.test.resource_group_name +} +`, template) +} + +func (EventGridSystemTopicEventSubscriptionResource) update(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-eg-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_storage_queue" "test" { + name = "mysamplequeue-%[1]d" + storage_account_name = azurerm_storage_account.test.name +} + +resource "azurerm_storage_container" "test" { + name = "vhds" + storage_account_name = azurerm_storage_account.test.name + container_access_type = "private" +} + +resource "azurerm_storage_blob" "test" { + name = "herpderp1.vhd" + + storage_account_name = azurerm_storage_account.test.name + storage_container_name = azurerm_storage_container.test.name + + type = "Page" + size = 5120 +} + +resource "azurerm_eventgrid_system_topic" "test" { + name = "acctesteg-%[1]d" + location = "Global" + resource_group_name = azurerm_resource_group.test.name + source_arm_resource_id = azurerm_resource_group.test.id + topic_type = "Microsoft.Resources.ResourceGroups" +} + +resource "azurerm_eventgrid_system_topic_event_subscription" "test" { + name = "acctesteg-%[1]d" + system_topic = azurerm_eventgrid_system_topic.test.name + resource_group_name = azurerm_resource_group.test.name + + storage_queue_endpoint { + storage_account_id = azurerm_storage_account.test.id + queue_name = azurerm_storage_queue.test.name + } + + storage_blob_dead_letter_destination { + storage_account_id = azurerm_storage_account.test.id + storage_blob_container_name = azurerm_storage_container.test.name + } + + retry_policy { + event_time_to_live = 12 + max_delivery_attempts = 10 + } + + subject_filter { + subject_begins_with = "test/test" + subject_ends_with = ".jpg" + } + + included_event_types = ["Microsoft.Storage.BlobCreated", "Microsoft.Storage.BlobDeleted"] + labels = ["test4", "test5", "test6"] +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (EventGridSystemTopicEventSubscriptionResource) eventHubID(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-eg-%[1]d" + location = "%[2]s" +} + +resource "azurerm_eventhub_namespace" "test" { + name = "acctesteventhubnamespace-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Basic" +} + +resource "azurerm_eventhub" "test" { + name = "acctesteventhub-%[1]d" + namespace_name = azurerm_eventhub_namespace.test.name + resource_group_name = azurerm_resource_group.test.name + partition_count = 2 + message_retention = 1 +} + +resource "azurerm_eventgrid_system_topic" "test" { + name = "acctesteg-%[1]d" + location = "Global" + resource_group_name = azurerm_resource_group.test.name + source_arm_resource_id = azurerm_resource_group.test.id + topic_type = "Microsoft.Resources.ResourceGroups" +} + +resource "azurerm_eventgrid_system_topic_event_subscription" "test" { + name = "acctesteg-%[1]d" + system_topic = azurerm_eventgrid_system_topic.test.name + resource_group_name = azurerm_resource_group.test.name + + event_delivery_schema = "CloudEventSchemaV1_0" + + eventhub_endpoint_id = azurerm_eventhub.test.id +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (EventGridSystemTopicEventSubscriptionResource) serviceBusQueueID(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-eg-%[1]d" + location = "%[2]s" +} + +resource "azurerm_servicebus_namespace" "example" { + name = "acctestservicebusnamespace-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Basic" +} + +resource "azurerm_servicebus_queue" "test" { + name = "acctestservicebusqueue-%[1]d" + resource_group_name = azurerm_resource_group.test.name + namespace_name = azurerm_servicebus_namespace.example.name + enable_partitioning = true +} + +resource "azurerm_eventgrid_system_topic" "test" { + name = "acctesteg-%[1]d" + location = "Global" + resource_group_name = azurerm_resource_group.test.name + source_arm_resource_id = azurerm_resource_group.test.id + topic_type = "Microsoft.Resources.ResourceGroups" +} + +resource "azurerm_eventgrid_system_topic_event_subscription" "test" { + name = "acctesteg-%[1]d" + system_topic = azurerm_eventgrid_system_topic.test.name + resource_group_name = azurerm_resource_group.test.name + + event_delivery_schema = "CloudEventSchemaV1_0" + service_bus_queue_endpoint_id = azurerm_servicebus_queue.test.id +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (EventGridSystemTopicEventSubscriptionResource) serviceBusTopicID(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-eg-%[1]d" + location = "%[2]s" +} + +resource "azurerm_servicebus_namespace" "example" { + name = "acctestservicebusnamespace-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Standard" +} + +resource "azurerm_servicebus_topic" "test" { + name = "acctestservicebustopic-%[1]d" + resource_group_name = azurerm_resource_group.test.name + namespace_name = azurerm_servicebus_namespace.example.name + enable_partitioning = true +} + +resource "azurerm_eventgrid_system_topic" "test" { + name = "acctesteg-%[1]d" + location = "Global" + resource_group_name = azurerm_resource_group.test.name + source_arm_resource_id = azurerm_resource_group.test.id + topic_type = "Microsoft.Resources.ResourceGroups" +} + +resource "azurerm_eventgrid_system_topic_event_subscription" "test" { + name = "acctesteg-%[1]d" + system_topic = azurerm_eventgrid_system_topic.test.name + resource_group_name = azurerm_resource_group.test.name + + event_delivery_schema = "CloudEventSchemaV1_0" + service_bus_topic_endpoint_id = azurerm_servicebus_topic.test.id +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (EventGridSystemTopicEventSubscriptionResource) filter(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-eg-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_storage_queue" "test" { + name = "mysamplequeue-%[1]d" + storage_account_name = azurerm_storage_account.test.name +} + +resource "azurerm_eventgrid_system_topic" "test" { + name = "acctesteg-%[1]d" + location = "Global" + resource_group_name = azurerm_resource_group.test.name + source_arm_resource_id = azurerm_resource_group.test.id + topic_type = "Microsoft.Resources.ResourceGroups" +} + +resource "azurerm_eventgrid_system_topic_event_subscription" "test" { + name = "acctesteg-%[1]d" + system_topic = azurerm_eventgrid_system_topic.test.name + resource_group_name = azurerm_resource_group.test.name + + storage_queue_endpoint { + storage_account_id = azurerm_storage_account.test.id + queue_name = azurerm_storage_queue.test.name + } + + included_event_types = ["Microsoft.Storage.BlobCreated", "Microsoft.Storage.BlobDeleted"] + + subject_filter { + subject_begins_with = "test/test" + subject_ends_with = ".jpg" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (EventGridSystemTopicEventSubscriptionResource) advancedFilter(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-eg-%[1]d" + location = "%[2]s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_storage_queue" "test" { + name = "mysamplequeue-%[1]d" + storage_account_name = azurerm_storage_account.test.name +} + +resource "azurerm_eventgrid_system_topic" "test" { + name = "acctesteg-%[1]d" + location = "Global" + resource_group_name = azurerm_resource_group.test.name + source_arm_resource_id = azurerm_resource_group.test.id + topic_type = "Microsoft.Resources.ResourceGroups" +} + +resource "azurerm_eventgrid_system_topic_event_subscription" "test" { + name = "acctesteg-%[1]d" + system_topic = azurerm_eventgrid_system_topic.test.name + resource_group_name = azurerm_resource_group.test.name + + storage_queue_endpoint { + storage_account_id = azurerm_storage_account.test.id + queue_name = azurerm_storage_queue.test.name + } + + advanced_filter { + bool_equals { + key = "subject" + value = true + } + number_greater_than { + key = "data.metadataVersion" + value = 1 + } + number_greater_than_or_equals { + key = "data.contentLength" + value = 42.0 + } + number_less_than { + key = "data.contentLength" + value = 42.1 + } + number_less_than_or_equals { + key = "data.metadataVersion" + value = 2 + } + number_in { + key = "data.contentLength" + values = [0, 1, 1, 2, 3] + } + number_not_in { + key = "data.contentLength" + values = [5, 8, 13, 21, 34] + } + string_begins_with { + key = "subject" + values = ["foo"] + } + string_ends_with { + key = "subject" + values = ["bar"] + } + string_contains { + key = "data.contentType" + values = ["application", "octet-stream"] + } + string_in { + key = "data.blobType" + values = ["Block"] + } + string_not_in { + key = "data.blobType" + values = ["Page"] + } + } + +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} diff --git a/azurerm/internal/services/eventgrid/eventgrid_system_topic_resource_test.go b/azurerm/internal/services/eventgrid/tests/eventgrid_system_topic_resource_test.go similarity index 100% rename from azurerm/internal/services/eventgrid/eventgrid_system_topic_resource_test.go rename to azurerm/internal/services/eventgrid/tests/eventgrid_system_topic_resource_test.go diff --git a/azurerm/internal/services/eventgrid/eventgrid_topic_data_source_test.go b/azurerm/internal/services/eventgrid/tests/eventgrid_topic_data_source_test.go similarity index 100% rename from azurerm/internal/services/eventgrid/eventgrid_topic_data_source_test.go rename to azurerm/internal/services/eventgrid/tests/eventgrid_topic_data_source_test.go diff --git a/azurerm/internal/services/eventgrid/eventgrid_topic_resource_test.go b/azurerm/internal/services/eventgrid/tests/eventgrid_topic_resource_test.go similarity index 100% rename from azurerm/internal/services/eventgrid/eventgrid_topic_resource_test.go rename to azurerm/internal/services/eventgrid/tests/eventgrid_topic_resource_test.go diff --git a/website/docs/r/eventgrid_system_topic_event_subscription.html.markdown b/website/docs/r/eventgrid_system_topic_event_subscription.html.markdown new file mode 100644 index 000000000000..853583313ec1 --- /dev/null +++ b/website/docs/r/eventgrid_system_topic_event_subscription.html.markdown @@ -0,0 +1,210 @@ +--- +subcategory: "Messaging" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_eventgrid_system_topic_event_subscription" +description: |- + Manages an EventGrid System Topic Event Subscription. +--- + +# azurerm_eventgrid_system_topic_event_subscription + +Manages an EventGrid System Topic Event Subscription. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-rg" + location = "West US 2" +} + +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" + + tags = { + environment = "staging" + } +} + +resource "azurerm_storage_queue" "example" { + name = "examplestoragequeue" + storage_account_name = azurerm_storage_account.example.name +} + +resource "azurerm_system_topic" "example" { + name = "example-system-topic" + location = "Global" + resource_group_name = azurerm_resource_group.example.name + source_arm_resource_id = azurerm_resource_group.example.id + topic_type = "Microsoft.Resources.ResourceGroups" +} + +resource "azurerm_eventgrid_system_topic_event_subscription" "example" { + name = "example-event-subscription" + system_topic = azurerm_system_topic.example.name + resource_group_name = azurerm_resource_group.example.name + + storage_queue_endpoint { + storage_account_id = azurerm_storage_account.example.id + queue_name = azurerm_storage_queue.example.name + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Messaging. Changing this forces a new Messaging to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the Messaging should exist. Changing this forces a new Messaging to be created. + +* `system_topic` - (Required) TODO. Changing this forces a new Messaging to be created. + +* `expiration_time_utc` - (Optional) Specifies the expiration time of the event subscription (Datetime Format `RFC 3339`). + +* `event_delivery_schema` - (Optional) Specifies the event delivery schema for the event subscription. Possible values include: `EventGridSchema`, `CloudEventSchemaV1_0`, `CustomInputSchema`. Defaults to `EventGridSchema`. Changing this forces a new resource to be created. + +* `azure_function_endpoint` - (Optional) An `azure_function_endpoint` block as defined below. + +* `eventhub_endpoint_id` - (Optional) Specifies the id where the Event Hub is located. + +* `hybrid_connection_endpoint_id` - (Optional) Specifies the id where the Hybrid Connection is located. + +* `service_bus_queue_endpoint_id` - (Optional) Specifies the id where the Service Bus Queue is located. + +* `service_bus_topic_endpoint_id` - (Optional) Specifies the id where the Service Bus Topic is located. + +* `storage_queue_endpoint` - (Optional) A `storage_queue_endpoint` block as defined below. + +* `webhook_endpoint` - (Optional) A `webhook_endpoint` block as defined below. + +~> **NOTE:** One of `eventhub_endpoint`, `eventhub_endpoint_id`, `hybrid_connection_endpoint`, `hybrid_connection_endpoint_id`, `service_bus_queue_endpoint_id`, `service_bus_topic_endpoint_id`, `storage_queue_endpoint` or `webhook_endpoint` must be specified. + +* `included_event_types` - (Optional) A list of applicable event types that need to be part of the event subscription. + +* `subject_filter` - (Optional) A `subject_filter` block as defined below. + +* `advanced_filter` - (Optional) A `advanced_filter` block as defined below. + +* `storage_blob_dead_letter_destination` - (Optional) A `storage_blob_dead_letter_destination` block as defined below. + +* `retry_policy` - (Optional) A `retry_policy` block as defined below. + +* `labels` - (Optional) A list of labels to assign to the event subscription. + +--- + +A `storage_queue_endpoint` supports the following: + +* `storage_account_id` - (Required) Specifies the id of the storage account id where the storage queue is located. + +* `queue_name` - (Required) Specifies the name of the storage queue where the Event Subscription will receive events. + +--- + +An `azure_function_endpoint` supports the following: + +* `function_id` - (Required) Specifies the ID of the Function where the Event Subscription will receive events. This must be the functions ID in format {function_app.id}/functions/{name}. + +* `max_events_per_batch` - (Optional) Maximum number of events per batch. + +* `preferred_batch_size_in_kilobytes` - (Optional) Preferred batch size in Kilobytes. + +--- + +A `webhook_endpoint` supports the following: + +* `url` - (Required) Specifies the url of the webhook where the Event Subscription will receive events. + +* `base_url` - (Computed) The base url of the webhook where the Event Subscription will receive events. + +* `max_events_per_batch` - (Optional) Maximum number of events per batch. + +* `preferred_batch_size_in_kilobytes` - (Optional) Preferred batch size in Kilobytes. + +* `active_directory_tenant_id` - (Optional) The Azure Active Directory Tenant ID to get the access token that will be included as the bearer token in delivery requests. + +* `active_directory_app_id_or_uri` - (Optional) The Azure Active Directory Application ID or URI to get the access token that will be included as the bearer token in delivery requests. + +--- + +A `subject_filter` supports the following: + +* `subject_begins_with` - (Optional) A string to filter events for an event subscription based on a resource path prefix. + +* `subject_ends_with` - (Optional) A string to filter events for an event subscription based on a resource path suffix. + +* `case_sensitive` - (Optional) Specifies if `subject_begins_with` and `subject_ends_with` case sensitive. This value defaults to `false`. + +--- + +A `advanced_filter` supports the following nested blocks: + +* `bool_equals` - Compares a value of an event using a single boolean value. +* `number_greater_than` - Compares a value of an event using a single floating point number. +* `number_greater_than_or_equals` - Compares a value of an event using a single floating point number. +* `number_less_than` - Compares a value of an event using a single floating point number. +* `number_less_than_or_equals` - Compares a value of an event using a single floating point number. +* `number_in` - Compares a value of an event using multiple floating point numbers. +* `number_not_in` - Compares a value of an event using multiple floating point numbers. +* `string_begins_with` - Compares a value of an event using multiple string values. +* `string_ends_with` - Compares a value of an event using multiple string values. +* `string_contains` - Compares a value of an event using multiple string values. +* `string_in` - Compares a value of an event using multiple string values. +* `string_not_in` - Compares a value of an event using multiple string values. + +Each nested block consists of a key and a value(s) element. + +* `key` - (Required) Specifies the field within the event data that you want to use for filtering. Type of the field can be a number, boolean, or string. + +* `value` - (Required) Specifies a single value to compare to when using a single value operator. + +**OR** + +* `values` - (Required) Specifies an array of values to compare to when using a multiple values operator. + +~> **NOTE:** A maximum of 5 advanced filters are allowed. + +--- + +A `storage_blob_dead_letter_destination` supports the following: + +* `storage_account_id` - (Required) Specifies the id of the storage account id where the storage blob is located. + +* `storage_blob_container_name` - (Required) Specifies the name of the Storage blob container that is the destination of the deadletter events. + +--- + +A `retry_policy` supports the following: + +* `max_delivery_attempts` - (Required) Specifies the maximum number of delivery retry attempts for events. + +* `event_time_to_live` - (Required) Specifies the time to live (in minutes) for events. Supported range is `1` to `1440`. Defaults to `1440`. See [official documentation](https://docs.microsoft.com/en-us/azure/event-grid/manage-event-delivery#set-retry-policy) for more details. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the EventGrid System Topic. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Messaging. +* `read` - (Defaults to 5 minutes) Used when retrieving the Messaging. +* `update` - (Defaults to 30 minutes) Used when updating the Messaging. +* `delete` - (Defaults to 30 minutes) Used when deleting the Messaging. + +## Import + +EventGrid System Topic Event Subscriptions can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_eventgrid_system_topic_event_subscription.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/systemTopics/topic1/eventSubscriptions/subscription1 +```