diff --git a/azurerm/config.go b/azurerm/config.go index d0a4adf6544d..0e8d8ebcdfdb 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -112,11 +112,12 @@ type ArmClient struct { kubernetesClustersClient containerservice.ManagedClustersClient containerGroupsClient containerinstance.ContainerGroupsClient - eventGridDomainsClient eventgrid.DomainsClient - eventGridTopicsClient eventgrid.TopicsClient - eventHubClient eventhub.EventHubsClient - eventHubConsumerGroupClient eventhub.ConsumerGroupsClient - eventHubNamespacesClient eventhub.NamespacesClient + eventGridDomainsClient eventgrid.DomainsClient + eventGridEventSubscriptionsClient eventgrid.EventSubscriptionsClient + eventGridTopicsClient eventgrid.TopicsClient + eventHubClient eventhub.EventHubsClient + eventHubConsumerGroupClient eventhub.ConsumerGroupsClient + eventHubNamespacesClient eventhub.NamespacesClient solutionsClient operationsmanagement.SolutionsClient @@ -836,6 +837,10 @@ func (c *ArmClient) registerEventGridClients(endpoint, subscriptionId string, au egdc := eventgrid.NewDomainsClientWithBaseURI(endpoint, subscriptionId) c.configureClient(&egdc.Client, auth) c.eventGridDomainsClient = egdc + + egesc := eventgrid.NewEventSubscriptionsClientWithBaseURI(endpoint, subscriptionId) + c.configureClient(&egesc.Client, auth) + c.eventGridEventSubscriptionsClient = egesc } func (c *ArmClient) registerEventHubClients(endpoint, subscriptionId string, auth autorest.Authorizer) { diff --git a/azurerm/provider.go b/azurerm/provider.go index 2f9af9a52416..91d59e8d6423 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -219,6 +219,7 @@ func Provider() terraform.ResourceProvider { "azurerm_dns_txt_record": resourceArmDnsTxtRecord(), "azurerm_dns_zone": resourceArmDnsZone(), "azurerm_eventgrid_domain": resourceArmEventGridDomain(), + "azurerm_eventgrid_event_subscription": resourceArmEventGridEventSubscription(), "azurerm_eventgrid_topic": resourceArmEventGridTopic(), "azurerm_eventhub_authorization_rule": resourceArmEventHubAuthorizationRule(), "azurerm_eventhub_consumer_group": resourceArmEventHubConsumerGroup(), diff --git a/azurerm/resource_arm_eventgrid_event_subscription.go b/azurerm/resource_arm_eventgrid_event_subscription.go new file mode 100644 index 000000000000..2855e1b31a3e --- /dev/null +++ b/azurerm/resource_arm_eventgrid_event_subscription.go @@ -0,0 +1,641 @@ +package azurerm + +import ( + "fmt" + "log" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/eventgrid/mgmt/2018-09-15-preview/eventgrid" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmEventGridEventSubscription() *schema.Resource { + return &schema.Resource{ + Create: resourceArmEventGridEventSubscriptionCreateUpdate, + Read: resourceArmEventGridEventSubscriptionRead, + Update: resourceArmEventGridEventSubscriptionCreateUpdate, + Delete: resourceArmEventGridEventSubscriptionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "scope": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "event_delivery_schema": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: string(eventgrid.EventGridSchema), + ValidateFunc: validation.StringInSlice([]string{ + string(eventgrid.CloudEventV01Schema), + string(eventgrid.CustomInputSchema), + string(eventgrid.EventGridSchema), + }, false), + }, + + "topic_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "storage_queue_endpoint": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{"eventhub_endpoint", "hybrid_connection_endpoint", "webhook_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: validate.NoEmptyStrings, + }, + }, + }, + }, + + "eventhub_endpoint": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{"storage_queue_endpoint", "hybrid_connection_endpoint", "webhook_endpoint"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "eventhub_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + }, + }, + }, + + "hybrid_connection_endpoint": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{"storage_queue_endpoint", "eventhub_endpoint", "webhook_endpoint"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hybrid_connection_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + }, + }, + }, + + "webhook_endpoint": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{"storage_queue_endpoint", "eventhub_endpoint", "hybrid_connection_endpoint"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "url": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.URLIsHTTPS, + }, + }, + }, + }, + + "included_event_types": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "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, + }, + }, + }, + }, + + "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: validate.NoEmptyStrings, + }, + }, + }, + }, + + "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), + }, + }, + }, + }, + + "labels": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func resourceArmEventGridEventSubscriptionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).eventGridEventSubscriptionsClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + scope := d.Get("scope").(string) + + if requireResourcesToBeImported && d.IsNewResource() { + existing, err := client.Get(ctx, scope, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing EventGrid Event Subscription %q (Scope %q): %s", name, scope, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_eventgrid_event_subscription", *existing.ID) + } + } + + destination := expandEventGridEventSubscriptionDestination(d) + if destination == nil { + return fmt.Errorf("One of `webhook_endpoint`, eventhub_endpoint` `hybrid_connection_endpoint` or `storage_queue_endpoint` must be specificed to create an EventGrid Event Subscription") + } + + eventSubscriptionProperties := eventgrid.EventSubscriptionProperties{ + Destination: destination, + Filter: expandEventGridEventSubscriptionFilter(d), + DeadLetterDestination: expandEventGridEventSubscriptionStorageBlobDeadLetterDestination(d), + RetryPolicy: expandEventGridEventSubscriptionRetryPolicy(d), + Labels: utils.ExpandStringArray(d.Get("labels").([]interface{})), + EventDeliverySchema: eventgrid.EventDeliverySchema(d.Get("event_delivery_schema").(string)), + } + + if v, ok := d.GetOk("topic_name"); ok { + eventSubscriptionProperties.Topic = utils.String(v.(string)) + } + + eventSubscription := eventgrid.EventSubscription{ + EventSubscriptionProperties: &eventSubscriptionProperties, + } + + log.Printf("[INFO] preparing arguments for AzureRM EventGrid Event Subscription creation with Properties: %+v.", eventSubscription) + + future, err := client.CreateOrUpdate(ctx, scope, name, eventSubscription) + if err != nil { + return fmt.Errorf("Error creating/updating EventGrid Event Subscription %q (Scope %q): %s", name, scope, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for EventGrid Event Subscription %q (Scope %q) to become available: %s", name, scope, err) + } + + read, err := client.Get(ctx, scope, name) + if err != nil { + return fmt.Errorf("Error retrieving EventGrid Event Subscription %q (Scope %q): %s", name, scope, err) + } + if read.ID == nil { + return fmt.Errorf("Cannot read EventGrid Event Subscription %s (Scope %s) ID", name, scope) + } + + d.SetId(*read.ID) + + return resourceArmEventGridEventSubscriptionRead(d, meta) +} + +func resourceArmEventGridEventSubscriptionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).eventGridEventSubscriptionsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureEventGridEventSubscriptionID(d.Id()) + if err != nil { + return err + } + scope := id.Scope + name := id.Name + + resp, err := client.Get(ctx, scope, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[WARN] EventGrid Event Subscription '%s' was not found (resource group '%s')", name, scope) + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on EventGrid Event Subscription '%s': %+v", name, err) + } + + d.Set("name", resp.Name) + d.Set("scope", scope) + + if props := resp.EventSubscriptionProperties; props != nil { + d.Set("event_delivery_schema", string(props.EventDeliverySchema)) + + if props.Topic != nil && *props.Topic != "" { + d.Set("topic_name", *props.Topic) + } + + if storageQueueEndpoint, ok := props.Destination.AsStorageQueueEventSubscriptionDestination(); ok { + if err := d.Set("storage_queue_endpoint", flattenEventGridEventSubscriptionStorageQueueEndpoint(storageQueueEndpoint)); err != nil { + return fmt.Errorf("Error setting `storage_queue_endpoint` for EventGrid Event Subscription %q (Scope %q): %s", name, scope, err) + } + } + if eventHubEndpoint, ok := props.Destination.AsEventHubEventSubscriptionDestination(); ok { + if err := d.Set("eventhub_endpoint", flattenEventGridEventSubscriptionEventHubEndpoint(eventHubEndpoint)); err != nil { + return fmt.Errorf("Error setting `eventhub_endpoint` for EventGrid Event Subscription %q (Scope %q): %s", name, scope, err) + } + } + if hybridConnectionEndpoint, ok := props.Destination.AsHybridConnectionEventSubscriptionDestination(); ok { + if err := d.Set("hybrid_connection_endpoint", flattenEventGridEventSubscriptionHybridConnectionEndpoint(hybridConnectionEndpoint)); err != nil { + return fmt.Errorf("Error setting `hybrid_connection_endpoint` for EventGrid Event Subscription %q (Scope %q): %s", name, scope, err) + } + } + if webhookEndpoint, ok := props.Destination.AsWebHookEventSubscriptionDestination(); ok { + if err := d.Set("webhook_endpoint", flattenEventGridEventSubscriptionWebhookEndpoint(webhookEndpoint)); err != nil { + return fmt.Errorf("Error setting `webhook_endpoint` for EventGrid Event Subscription %q (Scope %q): %s", name, scope, 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 Event Subscription %q (Scope %q): %s", name, scope, 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 Event Subscription %q (Scope %q): %s", name, scope, 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 Event Subscription %q (Scope %q): %s", name, scope, err) + } + } + + if labels := props.Labels; labels != nil { + if err := d.Set("labels", *labels); err != nil { + return fmt.Errorf("Error setting `labels` for EventGrid Event Subscription %q (Scope %q): %s", name, scope, err) + } + } + } + + return nil +} + +func resourceArmEventGridEventSubscriptionDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).eventGridEventSubscriptionsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureEventGridEventSubscriptionID(d.Id()) + if err != nil { + return err + } + scope := id.Scope + name := id.Name + + future, err := client.Delete(ctx, scope, name) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("Error deleting Event Grid Event Subscription %q: %+v", name, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("Error deleting Event Grid Event Subscription %q: %+v", name, err) + } + + return nil +} + +type AzureEventGridEventSubscriptionID struct { + Scope string + Name string +} + +func parseAzureEventGridEventSubscriptionID(id string) (*AzureEventGridEventSubscriptionID, error) { + segments := strings.Split(id, "/providers/Microsoft.EventGrid/eventSubscriptions/") + if len(segments) != 2 { + return nil, fmt.Errorf("Expected ID to be in the format `{scope}/providers/Microsoft.EventGrid/eventSubscriptions/{name} - got %d segments", len(segments)) + } + + scope := segments[0] + name := segments[1] + eventSubscriptionID := AzureEventGridEventSubscriptionID{ + Scope: scope, + Name: name, + } + return &eventSubscriptionID, nil +} + +func expandEventGridEventSubscriptionDestination(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { + if _, ok := d.GetOk("storage_queue_endpoint"); ok { + return expandEventGridEventSubscriptionStorageQueueEndpoint(d) + } + + if _, ok := d.GetOk("eventhub_endpoint"); ok { + return expandEventGridEventSubscriptionEventHubEndpoint(d) + } + + if _, ok := d.GetOk("hybrid_connection_endpoint"); ok { + return expandEventGridEventSubscriptionHybridConnectionEndpoint(d) + } + + if _, ok := d.GetOk("webhook_endpoint"); ok { + return expandEventGridEventSubscriptionWebhookEndpoint(d) + } + + 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) + + storageQueueEndpoint := eventgrid.StorageQueueEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeStorageQueue, + StorageQueueEventSubscriptionDestinationProperties: &eventgrid.StorageQueueEventSubscriptionDestinationProperties{ + ResourceID: &storageAccountID, + QueueName: &queueName, + }, + } + return storageQueueEndpoint +} + +func expandEventGridEventSubscriptionEventHubEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { + props := d.Get("eventhub_endpoint").([]interface{})[0].(map[string]interface{}) + eventHubID := props["eventhub_id"].(string) + + eventHubEndpoint := eventgrid.EventHubEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeEventHub, + EventHubEventSubscriptionDestinationProperties: &eventgrid.EventHubEventSubscriptionDestinationProperties{ + ResourceID: &eventHubID, + }, + } + return eventHubEndpoint +} + +func expandEventGridEventSubscriptionHybridConnectionEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { + props := d.Get("hybrid_connection_endpoint").([]interface{})[0].(map[string]interface{}) + hybridConnectionID := props["hybrid_connection_id"].(string) + + hybridConnectionEndpoint := eventgrid.HybridConnectionEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeHybridConnection, + HybridConnectionEventSubscriptionDestinationProperties: &eventgrid.HybridConnectionEventSubscriptionDestinationProperties{ + ResourceID: &hybridConnectionID, + }, + } + return hybridConnectionEndpoint +} + +func expandEventGridEventSubscriptionWebhookEndpoint(d *schema.ResourceData) eventgrid.BasicEventSubscriptionDestination { + props := d.Get("webhook_endpoint").([]interface{})[0].(map[string]interface{}) + url := props["url"].(string) + + webhookEndpoint := eventgrid.WebHookEventSubscriptionDestination{ + EndpointType: eventgrid.EndpointTypeWebHook, + WebHookEventSubscriptionDestinationProperties: &eventgrid.WebHookEventSubscriptionDestinationProperties{ + EndpointURL: &url, + }, + } + return webhookEndpoint +} + +func expandEventGridEventSubscriptionFilter(d *schema.ResourceData) *eventgrid.EventSubscriptionFilter { + filter := &eventgrid.EventSubscriptionFilter{} + + if includedEvents, ok := d.GetOk("included_event_types"); ok { + filter.IncludedEventTypes = utils.ExpandStringArray(includedEvents.([]interface{})) + } + + if subjectFilter, ok := d.GetOk("subject_filter"); ok { + config := subjectFilter.([]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 + } + + return filter +} + +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 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 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 + } + result := make(map[string]interface{}) + + if input.ResourceID != nil { + result["eventhub_id"] = *input.ResourceID + } + + return []interface{}{result} +} + +func flattenEventGridEventSubscriptionWebhookEndpoint(input *eventgrid.WebHookEventSubscriptionDestination) []interface{} { + if input == nil { + return nil + } + result := make(map[string]interface{}) + + if input.EndpointURL != nil { + result["url"] = *input.EndpointURL + } + + return []interface{}{result} +} + +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 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} +} diff --git a/azurerm/resource_arm_eventgrid_event_subscription_test.go b/azurerm/resource_arm_eventgrid_event_subscription_test.go new file mode 100644 index 000000000000..2843adb32634 --- /dev/null +++ b/azurerm/resource_arm_eventgrid_event_subscription_test.go @@ -0,0 +1,423 @@ +package azurerm + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMEventGridEventSubscription_basic(t *testing.T) { + resourceName := "azurerm_eventgrid_event_subscription.test" + ri := tf.AccRandTimeInt() + rs := strings.ToLower(acctest.RandString(11)) + + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventGridEventSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventGridEventSubscription_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventGridEventSubscriptionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_delivery_schema", "EventGridSchema"), + resource.TestCheckResourceAttr(resourceName, "storage_queue_endpoint.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_blob_dead_letter_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "included_event_types.0", "All"), + resource.TestCheckResourceAttr(resourceName, "retry_policy.0.max_delivery_attempts", "11"), + resource.TestCheckResourceAttr(resourceName, "retry_policy.0.event_time_to_live", "11"), + resource.TestCheckResourceAttr(resourceName, "labels.0", "test"), + resource.TestCheckResourceAttr(resourceName, "labels.2", "test2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMEventGridEventSubscription_eventhub(t *testing.T) { + resourceName := "azurerm_eventgrid_event_subscription.test" + ri := tf.AccRandTimeInt() + + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventGridEventSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventGridEventSubscription_eventhub(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventGridEventSubscriptionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_delivery_schema", "CloudEventV01Schema"), + resource.TestCheckResourceAttr(resourceName, "eventhub_endpoint.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMEventGridEventSubscription_update(t *testing.T) { + resourceName := "azurerm_eventgrid_event_subscription.test" + ri := tf.AccRandTimeInt() + rs := strings.ToLower(acctest.RandString(11)) + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventGridEventSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventGridEventSubscription_basic(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventGridEventSubscriptionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "event_delivery_schema", "EventGridSchema"), + resource.TestCheckResourceAttr(resourceName, "storage_queue_endpoint.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_blob_dead_letter_destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "included_event_types.0", "All"), + resource.TestCheckResourceAttr(resourceName, "retry_policy.0.max_delivery_attempts", "11"), + resource.TestCheckResourceAttr(resourceName, "retry_policy.0.event_time_to_live", "11"), + resource.TestCheckResourceAttr(resourceName, "labels.0", "test"), + resource.TestCheckResourceAttr(resourceName, "labels.2", "test2"), + ), + }, + { + Config: testAccAzureRMEventGridEventSubscription_update(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventGridEventSubscriptionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "included_event_types.0", "Microsoft.Storage.BlobCreated"), + resource.TestCheckResourceAttr(resourceName, "included_event_types.1", "Microsoft.Storage.BlobDeleted"), + resource.TestCheckResourceAttr(resourceName, "subject_filter.0.subject_ends_with", ".jpg"), + resource.TestCheckResourceAttr(resourceName, "subject_filter.0.subject_begins_with", "test/test"), + resource.TestCheckResourceAttr(resourceName, "retry_policy.0.max_delivery_attempts", "10"), + resource.TestCheckResourceAttr(resourceName, "retry_policy.0.event_time_to_live", "12"), + resource.TestCheckResourceAttr(resourceName, "labels.0", "test4"), + resource.TestCheckResourceAttr(resourceName, "labels.2", "test6"), + ), + }, + }, + }) +} + +func TestAccAzureRMEventGridEventSubscription_filter(t *testing.T) { + resourceName := "azurerm_eventgrid_event_subscription.test" + ri := tf.AccRandTimeInt() + rs := strings.ToLower(acctest.RandString(11)) + + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMEventGridEventSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventGridEventSubscription_filter(ri, rs, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventGridEventSubscriptionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "included_event_types.0", "Microsoft.Storage.BlobCreated"), + resource.TestCheckResourceAttr(resourceName, "included_event_types.1", "Microsoft.Storage.BlobDeleted"), + resource.TestCheckResourceAttr(resourceName, "subject_filter.0.subject_ends_with", ".jpg"), + resource.TestCheckResourceAttr(resourceName, "subject_filter.0.subject_begins_with", "test/test"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMEventGridEventSubscriptionDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).eventGridEventSubscriptionsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_eventgrid_event_subscription" { + continue + } + + name := rs.Primary.Attributes["name"] + scope := rs.Primary.Attributes["scope"] + + resp, err := client.Get(ctx, scope, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + + return err + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("EventGrid Event Subscription still exists:\n%#v", resp) + } + } + + return nil +} + +func testCheckAzureRMEventGridEventSubscriptionExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + scope, hasScope := rs.Primary.Attributes["scope"] + if !hasScope { + return fmt.Errorf("Bad: no scope found in state for EventGrid Event Subscription: %s", name) + } + + client := testAccProvider.Meta().(*ArmClient).eventGridEventSubscriptionsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + resp, err := client.Get(ctx, scope, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: EventGrid Event Subscription %q (scope: %s) does not exist", name, scope) + } + + return fmt.Errorf("Bad: Get on eventGridEventSubscriptionsClient: %s", err) + } + + return nil + } +} + +func testAccAzureRMEventGridEventSubscription_basic(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%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-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" +} + +resource "azurerm_storage_container" "test" { + name = "vhds" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "private" +} + +resource "azurerm_storage_blob" "test" { + name = "herpderp1.vhd" + + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + storage_container_name = "${azurerm_storage_container.test.name}" + + type = "page" + size = 5120 +} + +resource "azurerm_eventgrid_event_subscription" "test" { + name = "acctesteg-%d" + 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_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"] +} +`, rInt, location, rString, rInt, rInt) +} + +func testAccAzureRMEventGridEventSubscription_update(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%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-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" +} + +resource "azurerm_storage_container" "test" { + name = "vhds" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + container_access_type = "private" +} + +resource "azurerm_storage_blob" "test" { + name = "herpderp1.vhd" + + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" + storage_container_name = "${azurerm_storage_container.test.name}" + + type = "page" + size = 5120 +} + +resource "azurerm_eventgrid_event_subscription" "test" { + name = "acctesteg-%d" + 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_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"] +} +`, rInt, location, rString, rInt, rInt) +} + +func testAccAzureRMEventGridEventSubscription_eventhub(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_eventhub_namespace" "test" { + name = "acctesteventhubnamespace-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Basic" +} + +resource "azurerm_eventhub" "test" { + name = "acctesteventhub-%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_event_subscription" "test" { + name = "acctesteg-%d" + scope = "${azurerm_resource_group.test.id}" + event_delivery_schema = "CloudEventV01Schema" + eventhub_endpoint { + eventhub_id = "${azurerm_eventhub.test.id}" + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureRMEventGridEventSubscription_filter(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%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-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_storage_account.test.name}" +} + +resource "azurerm_eventgrid_event_subscription" "test" { + name = "acctesteg-%d" + scope = "${azurerm_resource_group.test.id}" + 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" + } +} +`, rInt, location, rString, rInt, rInt) +} diff --git a/website/docs/r/eventgrid_event_subscription.html.markdown b/website/docs/r/eventgrid_event_subscription.html.markdown new file mode 100644 index 000000000000..f383071e7e01 --- /dev/null +++ b/website/docs/r/eventgrid_event_subscription.html.markdown @@ -0,0 +1,145 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_eventgrid_event_subscription" +sidebar_current: "docs-azurerm-resource-messaging-eventgrid-event-subscription" +description: |- + Manages an EventGrid Event Subscription + +--- + +# azurerm_eventgrid_event_subscription + +Manages an EventGrid Event Subscription + +## Example Usage + +```hcl +resource "azurerm_resource_group" "default" { + name = "defaultResourceGroup" + location = "West US 2" +} + +resource "azurerm_storage_account" "default" { + name = "defaultStorageAccount" + resource_group_name = "${azurerm_resource_group.default.name}" + location = "${azurerm_resource_group.default.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags { + environment = "staging" + } + } + + resource "azurerm_storage_queue" "default" { + name = defaultStorageQueue" + resource_group_name = "${azurerm_resource_group.default.name}" + storage_account_name = "${azurerm_storage_account.default.name}" + } + +resource "azurerm_eventgrid_event_subscription" "default" { + name = "defaultEventSubscription" + scope = "${azurerm_resource_group.default.id}" + storage_queue_endpoint { + storage_account_id = "${azurerm_storage_account.default.id}" + queue_name = "${azurerm_storage_queue.default.name}" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the EventGrid Event Subscription resource. Changing this forces a new resource to be created. + +* `scope` - (Required) Specifies the scope at which the EventGrid Event Subscription should be created. Changing this forces a new resource to be created. + +* `event_delivery_schema` - (Optional) Specifies the event delivery schema for the event subscription. Possible values include: `EventGridSchema`, `CloudEventV01Schema`, `CustomInputSchema`. + +* `topic_name` - (Optional) Specifies the name of the topic to associate with the event subscription. + +* `storage_queue_endpoint` - (Optional) A `storage_queue_endpoint` block as defined below. + +* `eventhub_endpoint` - (Optional) A `eventhub_endpoint` block as defined below. + +* `hybrid_connection_endpoint` - (Optional) A `hybrid_connection_endpoint` block as defined below. + +* `webhook_endpoint` - (Optional) A `webhook_endpoint` block as defined below. + +~> **NOTE:** One of `storage_queue_endpoint`, `eventhub_endpoint`, `hybrid_connection_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. + +* `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 Subscriptio will receive events. + +--- + +A `eventhub_endpoint` supports the following: + +* `eventhub_id` - (Required) Specifies the id of the eventhub where the Event Subscription will receive events. + +--- + +A `hybrid_connection_endpoint` supports the following: + +* `hybrid_connection_id` - (Required) Specifies the id of the hybrid connection where the Event Subscription will receive events. + +A `webhook_endpoint` supports the following: + +* `url` - (Required) Specifies the url of the webhook where the Event Subscription will recieve events. + +--- + +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 `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. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the EventGrid Event Subscription. + +## Import + +EventGrid Domain's can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_eventgrid_event_subscription.eventSubscription1 +/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.EventGrid/eventSubscriptions/eventSubscription1 +```