From 933a60824128eb102b0af60162bf8834cbe74e0d Mon Sep 17 00:00:00 2001 From: Jochen Rauschenbusch Date: Sun, 10 May 2020 05:28:05 +0200 Subject: [PATCH] r/eventgrid-event-subscription: advanced filter --- .../eventgrid_event_subscription_resource.go | 422 +++++++++++++++++- ...ntgrid_event_subscription_resource_test.go | 72 +++ azurerm/utils/common_marshal.go | 20 + ...eventgrid_event_subscription.html.markdown | 31 ++ 4 files changed, 542 insertions(+), 3 deletions(-) diff --git a/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource.go b/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource.go index b133082a66398..d85e1e43c894b 100644 --- a/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource.go +++ b/azurerm/internal/services/eventgrid/eventgrid_event_subscription_resource.go @@ -228,6 +228,247 @@ func resourceArmEventGridEventSubscription() *schema.Resource { }, }, + "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, + }, + }, + }, + }, + }, + }, + }, + }, + "storage_blob_dead_letter_destination": { Type: schema.TypeList, MaxItems: 1, @@ -306,7 +547,10 @@ func resourceArmEventGridEventSubscriptionCreateUpdate(d *schema.ResourceData, m return fmt.Errorf("One of the following endpoint types must be specificed to create an EventGrid Event Subscription: %q", enpointPropertyNames()) } - filter := expandEventGridEventSubscriptionFilter(d) + filter, err := expandEventGridEventSubscriptionFilter(d) + if err != nil { + return fmt.Errorf("expanding filters for EventGrid Event Subscription %q (Scope %q): %+v", name, scope, err) + } expirationTime, err := expandEventGridExpirationTime(d) if err != nil { @@ -434,6 +678,9 @@ func resourceArmEventGridEventSubscriptionRead(d *schema.ResourceData, meta inte 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", id.Name, id.Scope, err) } + if err := d.Set("advanced_filter", flattenEventGridEventSubscriptionAdvancedFilter(filter)); err != nil { + return fmt.Errorf("Error setting `advanced_filter` for EventGrid Event Subscription %q (Scope %q): %s", id.Name, id.Scope, err) + } } if props.DeadLetterDestination != nil { @@ -605,7 +852,7 @@ func expandEventGridEventSubscriptionWebhookEndpoint(d *schema.ResourceData) eve } } -func expandEventGridEventSubscriptionFilter(d *schema.ResourceData) *eventgrid.EventSubscriptionFilter { +func expandEventGridEventSubscriptionFilter(d *schema.ResourceData) (*eventgrid.EventSubscriptionFilter, error) { filter := &eventgrid.EventSubscriptionFilter{} if includedEvents, ok := d.GetOk("included_event_types"); ok { @@ -623,7 +870,66 @@ func expandEventGridEventSubscriptionFilter(d *schema.ResourceData) *eventgrid.E filter.IsSubjectCaseSensitive = &caseSensitive } - return filter + 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 { @@ -733,6 +1039,84 @@ func flattenEventGridEventSubscriptionSubjectFilter(filter *eventgrid.EventSubsc return []interface{}{result} } +func flattenEventGridEventSubscriptionAdvancedFilter(input *eventgrid.EventSubscriptionFilter) []interface{} { + results := make([]interface{}, 0) + if input == 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 @@ -763,3 +1147,35 @@ func flattenEventGridEventSubscriptionRetryPolicy(retryPolicy *eventgrid.RetryPo 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/tests/eventgrid_event_subscription_resource_test.go b/azurerm/internal/services/eventgrid/tests/eventgrid_event_subscription_resource_test.go index dea09dffd18b5..2d2120bf3faf7 100644 --- a/azurerm/internal/services/eventgrid/tests/eventgrid_event_subscription_resource_test.go +++ b/azurerm/internal/services/eventgrid/tests/eventgrid_event_subscription_resource_test.go @@ -180,6 +180,29 @@ func TestAccAzureRMEventGridEventSubscription_filter(t *testing.T) { }) } +func TestAccAzureRMEventGridEventSubscription_advancedFilter(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_eventgrid_event_subscription", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMEventGridEventSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMEventGridEventSubscription_advancedFilter(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMEventGridEventSubscriptionExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "advanced_filter.0.number_less_than.0.key", "data.filesite"), + resource.TestCheckResourceAttr(data.ResourceName, "advanced_filter.0.number_less_than.0.value", "42.0"), + resource.TestCheckResourceAttr(data.ResourceName, "advanced_filter.0.string_begins_with.0.key", "topic"), + resource.TestCheckResourceAttr(data.ResourceName, "advanced_filter.0.string_begins_with.0.value", "topic_prefix"), + ), + }, + data.ImportStep(), + }, + }) +} + func testCheckAzureRMEventGridEventSubscriptionDestroy(s *terraform.State) error { client := acceptance.AzureProvider.Meta().(*clients.Client).EventGrid.EventSubscriptionsClient ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext @@ -536,3 +559,52 @@ resource "azurerm_eventgrid_event_subscription" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) } + +func testAccAzureRMEventGridEventSubscription_advancedFilter(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} +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_storage_account.test.id}" + storage_queue_endpoint { + storage_account_id = "${azurerm_storage_account.test.id}" + queue_name = "${azurerm_storage_queue.test.name}" + } + advanced_filter { + number_less_than { + key = "data.contentLength" + value = 42.0 + } + number_in { + key = "data.contentLength" + values = [1, 1, 2, 3, 5] + } + string_begins_with { + key = "data.blobType" + values = ["Page", "Block"] + } + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} diff --git a/azurerm/utils/common_marshal.go b/azurerm/utils/common_marshal.go index f2ee8a633d61b..5ec26337e1cdb 100644 --- a/azurerm/utils/common_marshal.go +++ b/azurerm/utils/common_marshal.go @@ -12,6 +12,16 @@ func ExpandStringSlice(input []interface{}) *[]string { return &result } +func ExpandFloatSlice(input []interface{}) *[]float64 { + result := make([]float64, 0) + for _, item := range input { + if item != nil { + result = append(result, item.(float64)) + } + } + return &result +} + func ExpandMapStringPtrString(input map[string]interface{}) map[string]*string { result := make(map[string]*string) for k, v := range input { @@ -30,6 +40,16 @@ func FlattenStringSlice(input *[]string) []interface{} { return result } +func FlattenFloatSlice(input *[]float64) []interface{} { + result := make([]interface{}, 0) + if input != nil { + for _, item := range *input { + result = append(result, item) + } + } + return result +} + func FlattenMapStringPtrString(input map[string]*string) map[string]interface{} { result := make(map[string]interface{}) for k, v := range input { diff --git a/website/docs/r/eventgrid_event_subscription.html.markdown b/website/docs/r/eventgrid_event_subscription.html.markdown index d9b3ded22ad12..6073021730846 100644 --- a/website/docs/r/eventgrid_event_subscription.html.markdown +++ b/website/docs/r/eventgrid_event_subscription.html.markdown @@ -81,6 +81,8 @@ The following arguments are supported: * `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. @@ -125,6 +127,35 @@ A `subject_filter` supports the following: --- +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.