diff --git a/internal/services/search/search_service_data_source.go b/internal/services/search/search_service_data_source.go index 8498bae1fef6e..b66909a84d385 100644 --- a/internal/services/search/search_service_data_source.go +++ b/internal/services/search/search_service_data_source.go @@ -2,8 +2,11 @@ package search import ( "fmt" + "net/http" + "strings" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" @@ -13,6 +16,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" ) func dataSourceSearchService() *pluginsdk.Resource { @@ -29,7 +33,7 @@ func dataSourceSearchService() *pluginsdk.Resource { Required: true, }, - "resource_group_name": commonschema.ResourceGroupName(), + "resource_group_name": commonschema.ResourceGroupNameForDataSource(), "replica_count": { Type: pluginsdk.TypeInt, @@ -102,9 +106,9 @@ func dataSourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) er if model := resp.Model; model != nil { if props := model.Properties; props != nil { - partitionCount := 0 - replicaCount := 0 - publicNetworkAccess := false + partitionCount := 1 + replicaCount := 1 + publicNetworkAccess := true if count := props.PartitionCount; count != nil { partitionCount = int(*count) @@ -115,7 +119,7 @@ func dataSourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) er } if props.PublicNetworkAccess != nil { - publicNetworkAccess = *props.PublicNetworkAccess != "Disabled" + publicNetworkAccess = strings.EqualFold(string(pointer.From(props.PublicNetworkAccess)), string(services.PublicNetworkAccessEnabled)) } d.Set("partition_count", partitionCount) @@ -128,19 +132,23 @@ func dataSourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) er } } + primaryKey := "" + secondaryKey := "" adminKeysClient := meta.(*clients.Client).Search.AdminKeysClient adminKeysId, err := adminkeys.ParseSearchServiceID(d.Id()) if err != nil { return err } - adminKeysResp, err := adminKeysClient.Get(ctx, *adminKeysId, adminkeys.GetOperationOptions{}) - if err == nil { - if model := adminKeysResp.Model; model != nil { - d.Set("primary_key", model.PrimaryKey) - d.Set("secondary_key", model.SecondaryKey) - } + if err != nil && !response.WasStatusCode(adminKeysResp.HttpResponse, http.StatusForbidden) { + return fmt.Errorf("retrieving Admin Keys for %s: %+v", id, err) + } + if model := adminKeysResp.Model; model != nil { + primaryKey = utils.NormalizeNilableString(model.PrimaryKey) + secondaryKey = utils.NormalizeNilableString(model.SecondaryKey) } + d.Set("primary_key", primaryKey) + d.Set("secondary_key", secondaryKey) queryKeysClient := meta.(*clients.Client).Search.QueryKeysClient queryKeysId, err := querykeys.ParseSearchServiceID(d.Id()) @@ -148,10 +156,11 @@ func dataSourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) er return err } queryKeysResp, err := queryKeysClient.ListBySearchService(ctx, *queryKeysId, querykeys.ListBySearchServiceOperationOptions{}) - if err == nil { - if model := queryKeysResp.Model; model != nil { - d.Set("query_keys", flattenSearchQueryKeys(*model)) - } + if err != nil && !response.WasStatusCode(queryKeysResp.HttpResponse, http.StatusForbidden) { + return fmt.Errorf("retrieving Query Keys for %s: %+v", id, err) + } + if err := d.Set("query_keys", flattenSearchQueryKeys(queryKeysResp.Model)); err != nil { + return fmt.Errorf("setting `query_keys`: %+v", err) } return nil diff --git a/internal/services/search/search_service_resource.go b/internal/services/search/search_service_resource.go index 4c94ea2e6de47..1326780a708ae 100644 --- a/internal/services/search/search_service_resource.go +++ b/internal/services/search/search_service_resource.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/search/2022-09-01/adminkeys" "github.com/hashicorp/go-azure-sdk/resource-manager/search/2022-09-01/querykeys" "github.com/hashicorp/go-azure-sdk/resource-manager/search/2022-09-01/services" - "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/helpers/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" @@ -91,10 +90,10 @@ func resourceSearchService() *pluginsdk.Resource { }), }, - "local_authentication_disabled": { + "local_authentication_enabled": { Type: pluginsdk.TypeBool, Optional: true, - Default: false, + Default: true, }, "authentication_failure_mode": { @@ -110,7 +109,7 @@ func resourceSearchService() *pluginsdk.Resource { Type: pluginsdk.TypeString, Optional: true, ForceNew: true, - Default: services.HostingModeDefault, + Default: string(services.HostingModeDefault), ValidateFunc: validation.StringInSlice([]string{ string(services.HostingModeDefault), string(services.HostingModeHighDensity), @@ -123,11 +122,6 @@ func resourceSearchService() *pluginsdk.Resource { Default: false, }, - "customer_managed_key_enforcement_compliance": { - Type: pluginsdk.TypeString, - Computed: true, - }, - "primary_key": { Type: pluginsdk.TypeString, Computed: true, @@ -184,7 +178,7 @@ func resourceSearchService() *pluginsdk.Resource { func resourceSearchServiceCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Search.ServicesClient subscriptionId := meta.(*clients.Client).Account.SubscriptionId - ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() id := services.NewSearchServiceID(subscriptionId, d.Get("resource_group_name").(string), d.Get("name").(string)) @@ -198,8 +192,6 @@ func resourceSearchServiceCreate(d *pluginsdk.ResourceData, meta interface{}) er return tf.ImportAsExistsError("azurerm_search_service", id.ID()) } - location := azure.NormalizeLocation(d.Get("location").(string)) - publicNetworkAccess := services.PublicNetworkAccessEnabled if enabled := d.Get("public_network_access_enabled").(bool); !enabled { publicNetworkAccess = services.PublicNetworkAccessDisabled @@ -210,7 +202,7 @@ func resourceSearchServiceCreate(d *pluginsdk.ResourceData, meta interface{}) er ipRulesRaw := d.Get("allowed_ips").(*pluginsdk.Set).List() hostingMode := services.HostingMode(d.Get("hosting_mode").(string)) cmkEnforcementEnabled := d.Get("customer_managed_key_enforcement_enabled").(bool) - localAuthenticationDisabled := d.Get("local_authentication_disabled").(bool) + localAuthenticationEnabled := d.Get("local_authentication_enabled").(bool) authenticationFailureMode := d.Get("authentication_failure_mode").(string) cmkEnforcement := services.SearchEncryptionWithCmkDisabled @@ -243,31 +235,31 @@ func resourceSearchServiceCreate(d *pluginsdk.ResourceData, meta interface{}) er return err } - if localAuthenticationDisabled && authenticationFailureMode != "" { + if !localAuthenticationEnabled && authenticationFailureMode != "" { return fmt.Errorf("'authentication_failure_mode' cannot be defined if 'local_authentication_disabled' has been set to 'true'") } - // API Only Mode (Defalut)(e.g. localAuthenticationDisabled = false)... + // API Only Mode (Default) (e.g. localAuthenticationEnabled = true)... authenticationOptions := pointer.To(services.DataPlaneAuthOptions{ ApiKeyOnly: pointer.To(apiKeyOnly), }) - if !localAuthenticationDisabled && authenticationFailureMode != "" { + if localAuthenticationEnabled && authenticationFailureMode != "" { // API & RBAC Mode.. authenticationOptions = pointer.To(services.DataPlaneAuthOptions{ AadOrApiKey: pointer.To(services.DataPlaneAadOrApiKeyAuthOption{ - AadAuthFailureMode: (*services.AadAuthFailureMode)(pointer.To(authenticationFailureMode)), + AadAuthFailureMode: pointer.To(services.AadAuthFailureMode(authenticationFailureMode)), }), }) } - if localAuthenticationDisabled { + if !localAuthenticationEnabled { // RBAC Only Mode... authenticationOptions = nil } - searchService := services.SearchService{ - Location: location, + payload := services.SearchService{ + Location: location.Normalize(d.Get("location").(string)), Sku: pointer.To(services.Sku{ Name: pointer.To(skuName), }), @@ -281,7 +273,7 @@ func resourceSearchServiceCreate(d *pluginsdk.ResourceData, meta interface{}) er }), HostingMode: pointer.To(hostingMode), AuthOptions: authenticationOptions, - DisableLocalAuth: pointer.To(localAuthenticationDisabled), + DisableLocalAuth: pointer.To(!localAuthenticationEnabled), PartitionCount: pointer.To(partitionCount), ReplicaCount: pointer.To(replicaCount), }, @@ -297,10 +289,10 @@ func resourceSearchServiceCreate(d *pluginsdk.ResourceData, meta interface{}) er // in the create call, only in the update call when 'identity' is removed from the // configuration file... if expandedIdentity.Type != identity.TypeNone { - searchService.Identity = expandedIdentity + payload.Identity = expandedIdentity } - err = client.CreateOrUpdateThenPoll(ctx, id, searchService, services.CreateOrUpdateOperationOptions{}) + err = client.CreateOrUpdateThenPoll(ctx, id, payload, services.CreateOrUpdateOperationOptions{}) if err != nil { return fmt.Errorf("creating %s: %+v", id, err) } @@ -312,7 +304,7 @@ func resourceSearchServiceCreate(d *pluginsdk.ResourceData, meta interface{}) er func resourceSearchServiceUpdate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Search.ServicesClient - ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) defer cancel() id, err := services.ParseSearchServiceID(d.Id()) @@ -324,120 +316,128 @@ func resourceSearchServiceUpdate(d *pluginsdk.ResourceData, meta interface{}) er if err != nil { return fmt.Errorf("retrieving %s: %+v", id, err) } + if resp.Model == nil { + return fmt.Errorf("retrieving existing %s: %+v", id, err) + } + model := *resp.Model + if model.Properties == nil { + return fmt.Errorf("retrieving existing %s: `properties` was nil", id) + } - if model := resp.Model; model != nil { - if d.HasChange("public_network_access_enabled") { - publicNetworkAccess := services.PublicNetworkAccessEnabled - if enabled := d.Get("public_network_access_enabled").(bool); !enabled { - publicNetworkAccess = services.PublicNetworkAccessDisabled - } - - model.Properties.PublicNetworkAccess = pointer.To(publicNetworkAccess) + if d.HasChange("customer_managed_key_enforcement_enabled") { + cmkEnforcement := services.SearchEncryptionWithCmkDisabled + if enabled := d.Get("customer_managed_key_enforcement_enabled").(bool); enabled { + cmkEnforcement = services.SearchEncryptionWithCmkEnabled + } + model.Properties.EncryptionWithCmk = &services.EncryptionWithCmk{ + Enforcement: pointer.To(cmkEnforcement), } + } - if d.HasChange("identity") { - expandedIdentity, err := identity.ExpandSystemAssigned(d.Get("identity").([]interface{})) - if err != nil { - return fmt.Errorf("expanding `identity`: %+v", err) - } + if d.HasChange("hosting_mode") { + hostingMode := services.HostingMode(d.Get("hosting_mode").(string)) + if model.Sku == nil { + return fmt.Errorf("updating `hosting_mode` for %s: unable to validate the hosting_mode since `model.Sku` was nil", *id) + } - model.Identity = expandedIdentity + if pointer.From(model.Sku.Name) != services.SkuNameStandardThree && hostingMode == services.HostingModeHighDensity { + return fmt.Errorf("'hosting_mode' can only be set to %q if the 'sku' is %q, got %q", services.HostingModeHighDensity, services.SkuNameStandardThree, pointer.From(model.Sku.Name)) } - if d.HasChange("hosting_mode") { - hostingMode := services.HostingMode(d.Get("hosting_mode").(string)) - if pointer.From(model.Sku.Name) != services.SkuNameStandardThree && hostingMode == services.HostingModeHighDensity { - return fmt.Errorf("'hosting_mode' can only be set to %q if the 'sku' is %q, got %q", services.HostingModeHighDensity, services.SkuNameStandardThree, pointer.From(model.Sku.Name)) - } + model.Properties.HostingMode = pointer.To(hostingMode) + } - model.Properties.HostingMode = pointer.To(hostingMode) + if d.HasChange("identity") { + expandedIdentity, err := identity.ExpandSystemAssigned(d.Get("identity").([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) } - if d.HasChange("customer_managed_key_enforcement_enabled") { - cmkEnforcement := services.SearchEncryptionWithCmkDisabled - if enabled := d.Get("customer_managed_key_enforcement_enabled").(bool); enabled { - cmkEnforcement = services.SearchEncryptionWithCmkEnabled - } + model.Identity = expandedIdentity + } - model.Properties.EncryptionWithCmk.Enforcement = pointer.To(cmkEnforcement) + if d.HasChange("public_network_access_enabled") { + publicNetworkAccess := services.PublicNetworkAccessEnabled + if enabled := d.Get("public_network_access_enabled").(bool); !enabled { + publicNetworkAccess = services.PublicNetworkAccessDisabled } - if d.HasChange("local_authentication_disabled") || d.HasChange("authentication_failure_mode") { - localAuthenticationDisabled := d.Get("local_authentication_disabled").(bool) - authenticationFailureMode := d.Get("authentication_failure_mode").(string) - - if localAuthenticationDisabled && authenticationFailureMode != "" { - return fmt.Errorf("'authentication_failure_mode' cannot be defined if 'local_authentication_disabled' has been set to 'true'") - } + model.Properties.PublicNetworkAccess = pointer.To(publicNetworkAccess) + } - var apiKeyOnly interface{} = make(map[string]interface{}, 0) + if d.HasChanges("authentication_failure_mode", "local_authentication_enabled") { + authenticationFailureMode := d.Get("authentication_failure_mode").(string) + localAuthenticationEnabled := d.Get("local_authentication_enabled").(bool) + if !localAuthenticationEnabled && authenticationFailureMode != "" { + return fmt.Errorf("'authentication_failure_mode' cannot be defined if 'local_authentication_enabled' has been set to 'false'") + } - // API Only Mode (Defalut)... - authenticationOptions := pointer.To(services.DataPlaneAuthOptions{ - ApiKeyOnly: pointer.To(apiKeyOnly), - }) + var apiKeyOnly interface{} = make(map[string]interface{}, 0) - if !localAuthenticationDisabled && authenticationFailureMode != "" { - // API & RBAC Mode.. - authenticationOptions = pointer.To(services.DataPlaneAuthOptions{ - AadOrApiKey: pointer.To(services.DataPlaneAadOrApiKeyAuthOption{ - AadAuthFailureMode: (*services.AadAuthFailureMode)(pointer.To(authenticationFailureMode)), - }), - }) - } + // API Only Mode (Default)... + authenticationOptions := pointer.To(services.DataPlaneAuthOptions{ + ApiKeyOnly: pointer.To(apiKeyOnly), + }) - if localAuthenticationDisabled { - // RBAC Only Mode... - authenticationOptions = nil - } + if localAuthenticationEnabled && authenticationFailureMode != "" { + // API & RBAC Mode.. + authenticationOptions = pointer.To(services.DataPlaneAuthOptions{ + AadOrApiKey: pointer.To(services.DataPlaneAadOrApiKeyAuthOption{ + AadAuthFailureMode: (*services.AadAuthFailureMode)(pointer.To(authenticationFailureMode)), + }), + }) + } - model.Properties.DisableLocalAuth = pointer.To(localAuthenticationDisabled) - model.Properties.AuthOptions = authenticationOptions + if !localAuthenticationEnabled { + // RBAC Only Mode... + authenticationOptions = nil } - if d.HasChange("replica_count") { - replicaCount, err := validateSearchServiceReplicaCount(int64(d.Get("replica_count").(int)), pointer.From(model.Sku.Name)) - if err != nil { - return err - } + model.Properties.DisableLocalAuth = pointer.To(!localAuthenticationEnabled) + model.Properties.AuthOptions = authenticationOptions + } - model.Properties.ReplicaCount = pointer.To(replicaCount) + if d.HasChange("replica_count") { + replicaCount, err := validateSearchServiceReplicaCount(int64(d.Get("replica_count").(int)), pointer.From(model.Sku.Name)) + if err != nil { + return err } - if d.HasChange("partition_count") { - partitionCount := int64(d.Get("partition_count").(int)) - // NOTE: 'partition_count' values greater than 1 are not valid for 'free' or 'basic' SKUs... - if (pointer.From(model.Sku.Name) == services.SkuNameFree || pointer.From(model.Sku.Name) == services.SkuNameBasic) && partitionCount > 1 { - return fmt.Errorf("'partition_count' values greater than 1 cannot be set for the %q SKU, got %d)", pointer.From(model.Sku.Name), partitionCount) - } - - // NOTE: If SKU is 'standard3' and the 'hosting_mode' is set to 'highDensity' the maximum number of partitions allowed is 3 - // where if 'hosting_mode' is set to 'default' the maximum number of partitions is 12... - if pointer.From(model.Sku.Name) == services.SkuNameStandardThree && partitionCount > 3 && pointer.From(model.Properties.HostingMode) == services.HostingModeHighDensity { - return fmt.Errorf("%q SKUs in %q mode can have a maximum of 3 partitions, got %d", services.SkuNameStandardThree, services.HostingModeHighDensity, partitionCount) - } + model.Properties.ReplicaCount = pointer.To(replicaCount) + } - model.Properties.PartitionCount = pointer.To(partitionCount) + if d.HasChange("partition_count") { + partitionCount := int64(d.Get("partition_count").(int)) + // NOTE: 'partition_count' values greater than 1 are not valid for 'free' or 'basic' SKUs... + if (pointer.From(model.Sku.Name) == services.SkuNameFree || pointer.From(model.Sku.Name) == services.SkuNameBasic) && partitionCount > 1 { + return fmt.Errorf("'partition_count' values greater than 1 cannot be set for the %q SKU, got %d)", pointer.From(model.Sku.Name), partitionCount) } - if d.HasChange("allowed_ips") { - ipRulesRaw := d.Get("allowed_ips").(*pluginsdk.Set).List() - model.Properties.NetworkRuleSet.IPRules = expandSearchServiceIPRules(ipRulesRaw) + // NOTE: If SKU is 'standard3' and the 'hosting_mode' is set to 'highDensity' the maximum number of partitions allowed is 3 + // where if 'hosting_mode' is set to 'default' the maximum number of partitions is 12... + if pointer.From(model.Sku.Name) == services.SkuNameStandardThree && partitionCount > 3 && pointer.From(model.Properties.HostingMode) == services.HostingModeHighDensity { + return fmt.Errorf("%q SKUs in %q mode can have a maximum of 3 partitions, got %d", services.SkuNameStandardThree, services.HostingModeHighDensity, partitionCount) } - if d.HasChange("tags") { - model.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) - } + model.Properties.PartitionCount = pointer.To(partitionCount) + } - err = client.CreateOrUpdateThenPoll(ctx, pointer.From(id), pointer.From(model), services.CreateOrUpdateOperationOptions{}) - if err != nil { - return fmt.Errorf("updating %s: %+v", id, err) + if d.HasChange("allowed_ips") { + ipRulesRaw := d.Get("allowed_ips").(*pluginsdk.Set).List() + model.Properties.NetworkRuleSet = &services.NetworkRuleSet{ + IPRules: expandSearchServiceIPRules(ipRulesRaw), } + } - return resourceSearchServiceRead(d, meta) + if d.HasChange("tags") { + model.Tags = tags.Expand(d.Get("tags").(map[string]interface{})) } - return nil + if err = client.CreateOrUpdateThenPoll(ctx, *id, model, services.CreateOrUpdateOperationOptions{}); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return resourceSearchServiceRead(d, meta) } func resourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) error { @@ -479,7 +479,7 @@ func resourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) erro publicNetworkAccess := true // publicNetworkAccess defaults to true... cmkEnforcement := false // cmkEnforcment defaults to false... hostingMode := services.HostingModeDefault - localAuthDisabled := false + localAuthEnabled := true authFailureMode := "" if count := props.PartitionCount; count != nil { @@ -500,16 +500,14 @@ func resourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) erro hostingMode = *props.HostingMode } - var cmkCompliance string if props.EncryptionWithCmk != nil { cmkEnforcement = strings.EqualFold(string(pointer.From(props.EncryptionWithCmk.Enforcement)), string(services.SearchEncryptionWithCmkEnabled)) - cmkCompliance = string(pointer.From(props.EncryptionWithCmk.EncryptionComplianceStatus)) } // I am using 'DisableLocalAuth' here because when you are in // RBAC Only Mode, the 'props.AuthOptions' will be 'nil'... if props.DisableLocalAuth != nil { - localAuthDisabled = pointer.From(props.DisableLocalAuth) + localAuthEnabled = !pointer.From(props.DisableLocalAuth) // if the AuthOptions are nil that means you are in RBAC Only Mode... if props.AuthOptions != nil { @@ -523,13 +521,12 @@ func resourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) erro } d.Set("authentication_failure_mode", authFailureMode) - d.Set("local_authentication_disabled", localAuthDisabled) + d.Set("local_authentication_enabled", localAuthEnabled) d.Set("partition_count", partitionCount) d.Set("replica_count", replicaCount) d.Set("public_network_access_enabled", publicNetworkAccess) d.Set("hosting_mode", hostingMode) d.Set("customer_managed_key_enforcement_enabled", cmkEnforcement) - d.Set("customer_managed_key_enforcement_compliance", cmkCompliance) d.Set("allowed_ips", flattenSearchServiceIPRules(props.NetworkRuleSet)) } @@ -549,11 +546,12 @@ func resourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) erro } adminKeysResp, err := adminKeysClient.Get(ctx, *adminKeysId, adminkeys.GetOperationOptions{}) - if err == nil { - if model := adminKeysResp.Model; model != nil { - d.Set("primary_key", model.PrimaryKey) - d.Set("secondary_key", model.SecondaryKey) - } + if err != nil { + return fmt.Errorf("retrieving Admin Keys for %s: %+v", *id, err) + } + if model := adminKeysResp.Model; model != nil { + d.Set("primary_key", model.PrimaryKey) + d.Set("secondary_key", model.SecondaryKey) } queryKeysClient := meta.(*clients.Client).Search.QueryKeysClient @@ -562,10 +560,11 @@ func resourceSearchServiceRead(d *pluginsdk.ResourceData, meta interface{}) erro return err } queryKeysResp, err := queryKeysClient.ListBySearchService(ctx, *queryKeysId, querykeys.ListBySearchServiceOperationOptions{}) - if err == nil { - if model := queryKeysResp.Model; model != nil { - d.Set("query_keys", flattenSearchQueryKeys(*model)) - } + if err != nil { + return fmt.Errorf("retrieving Query Keys for %s: %+v", *id, err) + } + if err := d.Set("query_keys", flattenSearchQueryKeys(queryKeysResp.Model)); err != nil { + return fmt.Errorf("setting `query_keys`: %+v", err) } return nil @@ -581,30 +580,23 @@ func resourceSearchServiceDelete(d *pluginsdk.ResourceData, meta interface{}) er return err } - resp, err := client.Delete(ctx, *id, services.DeleteOperationOptions{}) - if err != nil { - if response.WasNotFound(resp.HttpResponse) { - return nil - } - + if _, err := client.Delete(ctx, *id, services.DeleteOperationOptions{}); err != nil { return fmt.Errorf("deleting %s: %+v", *id, err) } return nil } -func flattenSearchQueryKeys(input []querykeys.QueryKey) []interface{} { +func flattenSearchQueryKeys(input *[]querykeys.QueryKey) []interface{} { results := make([]interface{}, 0) - for _, v := range input { - result := make(map[string]interface{}) - - if v.Name != nil { - result["name"] = *v.Name + if input != nil { + for _, v := range *input { + results = append(results, map[string]interface{}{ + "name": utils.NormalizeNilableString(v.Name), + "key": utils.NormalizeNilableString(v.Key), + }) } - result["key"] = *v.Key - - results = append(results, result) } return results @@ -612,9 +604,6 @@ func flattenSearchQueryKeys(input []querykeys.QueryKey) []interface{} { func expandSearchServiceIPRules(input []interface{}) *[]services.IPRule { output := make([]services.IPRule, 0) - if input == nil { - return &output - } for _, rule := range input { if rule != nil { @@ -628,12 +617,11 @@ func expandSearchServiceIPRules(input []interface{}) *[]services.IPRule { } func flattenSearchServiceIPRules(input *services.NetworkRuleSet) []interface{} { - if input == nil || *input.IPRules == nil || len(*input.IPRules) == 0 { - return nil - } result := make([]interface{}, 0) - for _, rule := range *input.IPRules { - result = append(result, rule.Value) + if input != nil || input.IPRules != nil { + for _, rule := range *input.IPRules { + result = append(result, rule.Value) + } } return result } diff --git a/internal/services/search/search_service_resource_test.go b/internal/services/search/search_service_resource_test.go index 5627985a5f14e..4bfdcbc82f1aa 100644 --- a/internal/services/search/search_service_resource_test.go +++ b/internal/services/search/search_service_resource_test.go @@ -16,13 +16,13 @@ import ( type SearchServiceResource struct{} -func TestAccSearchService_basicStandard(t *testing.T) { +func TestAccSearchService_basicSku(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_search_service", "test") r := SearchServiceResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.basic(data, "standard"), + Config: r.basic(data, "basic"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -31,7 +31,7 @@ func TestAccSearchService_basicStandard(t *testing.T) { }) } -func TestAccSearchService_basicFree(t *testing.T) { +func TestAccSearchService_freeSku(t *testing.T) { // Regression test case for issue #10151 data := acceptance.BuildTestData(t, "azurerm_search_service", "test") r := SearchServiceResource{} @@ -47,13 +47,13 @@ func TestAccSearchService_basicFree(t *testing.T) { }) } -func TestAccSearchService_basicBasic(t *testing.T) { +func TestAccSearchService_standardSku(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_search_service", "test") r := SearchServiceResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.basic(data, "basic"), + Config: r.basic(data, "standard"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -309,8 +309,7 @@ func TestAccSearchService_apiAccessControlRbacError(t *testing.T) { data.ResourceTest(t, r, []acceptance.TestStep{ { - Config: r.apiAccessControlBoth(data, true, "http401WithBearerChallenge"), - Check: acceptance.ComposeTestCheckFunc(), + Config: r.apiAccessControlBoth(data, false, "http401WithBearerChallenge"), ExpectError: regexp.MustCompile("cannot be defined"), }, }) @@ -329,28 +328,28 @@ func TestAccSearchService_apiAccessControlUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.apiAccessControlApiKeysOrRBAC(data, false), + Config: r.apiAccessControlApiKeysOrRBAC(data, true), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), { - Config: r.apiAccessControlApiKeysOrRBAC(data, true), + Config: r.apiAccessControlApiKeysOrRBAC(data, false), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), { - Config: r.apiAccessControlBoth(data, false, "http401WithBearerChallenge"), + Config: r.apiAccessControlBoth(data, true, "http401WithBearerChallenge"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), { - Config: r.apiAccessControlBoth(data, false, "http403"), + Config: r.apiAccessControlBoth(data, true, "http403"), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -366,7 +365,7 @@ func TestAccSearchService_apiAccessControlUpdate(t *testing.T) { }) } -func (t SearchServiceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { +func (r SearchServiceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := services.ParseSearchServiceID(state.ID) if err != nil { return nil, err @@ -403,10 +402,6 @@ resource "azurerm_search_service" "test" { resource_group_name = azurerm_resource_group.test.name location = azurerm_resource_group.test.location sku = "%s" - - tags = { - environment = "staging" - } } `, template, data.RandomInteger, sku) } @@ -421,10 +416,6 @@ resource "azurerm_search_service" "import" { resource_group_name = azurerm_search_service.test.resource_group_name location = azurerm_search_service.test.location sku = azurerm_search_service.test.sku - - tags = { - environment = "staging" - } } `, template) } @@ -471,12 +462,7 @@ resource "azurerm_search_service" "test" { resource_group_name = azurerm_resource_group.test.name location = azurerm_resource_group.test.location sku = "standard" - - allowed_ips = ["168.1.5.65", "1.2.3.0/24"] - - tags = { - environment = "staging" - } + allowed_ips = ["168.1.5.65", "1.2.3.0/24"] } `, template, data.RandomInteger) } @@ -499,10 +485,6 @@ resource "azurerm_search_service" "test" { identity { type = "SystemAssigned" } - - tags = { - environment = "staging" - } } `, template, data.RandomInteger) } @@ -522,10 +504,6 @@ resource "azurerm_search_service" "test" { location = azurerm_resource_group.test.location sku = "%s" hosting_mode = "highDensity" - - tags = { - environment = "staging" - } } `, template, data.RandomInteger, sku) } @@ -584,15 +562,11 @@ resource "azurerm_search_service" "test" { sku = "standard" customer_managed_key_enforcement_enabled = %t - - tags = { - environment = "staging" - } } `, template, data.RandomInteger, enforceCustomerManagedKey) } -func (r SearchServiceResource) apiAccessControlApiKeysOrRBAC(data acceptance.TestData, localAuthenticationDisabled bool) string { +func (r SearchServiceResource) apiAccessControlApiKeysOrRBAC(data acceptance.TestData, localAuthenticationEnabled bool) string { template := r.template(data) return fmt.Sprintf(` provider "azurerm" { @@ -607,16 +581,12 @@ resource "azurerm_search_service" "test" { location = azurerm_resource_group.test.location sku = "standard" - local_authentication_disabled = %t - - tags = { - environment = "staging" - } + local_authentication_enabled = %t } -`, template, data.RandomInteger, localAuthenticationDisabled) +`, template, data.RandomInteger, localAuthenticationEnabled) } -func (r SearchServiceResource) apiAccessControlBoth(data acceptance.TestData, localAuthenticationDisabled bool, authenticationFailureMode string) string { +func (r SearchServiceResource) apiAccessControlBoth(data acceptance.TestData, localAuthenticationEnabled bool, authenticationFailureMode string) string { template := r.template(data) return fmt.Sprintf(` provider "azurerm" { @@ -631,12 +601,8 @@ resource "azurerm_search_service" "test" { location = azurerm_resource_group.test.location sku = "standard" - local_authentication_disabled = %t - authentication_failure_mode = "%s" - - tags = { - environment = "staging" - } + local_authentication_enabled = %t + authentication_failure_mode = "%s" } -`, template, data.RandomInteger, localAuthenticationDisabled, authenticationFailureMode) +`, template, data.RandomInteger, localAuthenticationEnabled, authenticationFailureMode) } diff --git a/website/docs/r/search_service.html.markdown b/website/docs/r/search_service.html.markdown index de47b1782f9d2..29b01303d6984 100644 --- a/website/docs/r/search_service.html.markdown +++ b/website/docs/r/search_service.html.markdown @@ -10,7 +10,7 @@ description: |- Manages a Search Service. -## Example Usage +## Example Usage (supporting API Keys) ```hcl resource "azurerm_resource_group" "example" { @@ -26,6 +26,43 @@ resource "azurerm_search_service" "example" { } ``` +## Example Usage (using both AzureAD and API Keys) + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_search_service" "example" { + name = "example-resource" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "standard" + + local_authentication_enabled = true + authentication_failure_mode = "http403" +} +``` + +## Example Usage (supporting only AzureAD Authentication) + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_search_service" "example" { + name = "example-resource" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "standard" + + local_authentication_enabled = false +} +``` + ## Arguments Reference The following arguments are supported: @@ -44,37 +81,34 @@ The following arguments are supported: --- -* `authentication_failure_mode` - (Optional) Describes what response the Search Service should return for requests that fail authentication. Possible values include `http401WithBearerChallenge` or `http403`. - -~> **NOTE:** The `authentication_failure_mode` field is only valid if the Search Service is in the `Role-based access control and API Key` mode. For more information on how to correctly configure the Search Services `API Access Control` settings, please see the `Setting API Access Control` section below. +* `allowed_ips` - (Optional) Specifies a list of inbound IPv4 or CIDRs that are allowed to access the Search Service. If the incoming IP request is from an IP address which is not included in the `allowed_ips` it will be blocked by the Search Services firewall. -* `customer_managed_key_enforcement_enabled` - (Optional) Should the Search Service enforce having non customer encrypted resources? Possible values include `true` or `false`. If `true` the Search Service will be marked as `non-compliant` if there are one or more non customer encrypted resources, if `false` no enforcement will be made and the Search Service can contain one or more non customer encrypted resources. Defaults to `false`. +-> **NOTE:** The `allowed_ips` are only applied if the `public_network_access_enabled` field has been set to `true`, else all traffic over the public interface will be rejected, even if the `allowed_ips` field has been defined. When the `public_network_access_enabled` field has been set to `false` the private endpoint connections are the only allowed access point to the Search Service. -* `hosting_mode` - (Optional) Enable high density partitions that allow for up to a 1000 indexes. Possible values are `highDensity` or `default`. Defaults to `default`. Changing this forces a new Search Service to be created. +* `authentication_failure_mode` - (Optional) Specifies the response that the Search Service should return for requests that fail authentication. Possible values include `http401WithBearerChallenge` or `http403`. --> **NOTE:** When the Search Service is in `highDensity` mode the maximum number of partitions allowed is `3`, to enable `hosting_mode` you must use a `standard3` SKU. +-> **NOTE:** `authentication_failure_mode` can only be configured when using `local_authentication_enabled` is set to `true` - which when set together specifies that both API Keys and AzureAD Authentication should be supported. -* `local_authentication_disabled` - (Optional) Should tha Search Service *not* be allowed to use API keys for authentication? Possible values include `true` or `false`. Defaults to `false`. +* `customer_managed_key_enforcement_enabled` - (Optional) Specifies whether the Search Service should enforce that non-customer resources are encrypted. Defaults to `false`. --> **NOTE:** For more information on how to correctly configure the Search Services `API Access Control` settings, please see the `Setting API Access Control` section below. +* `hosting_mode` - (Optional) Specifies the Hosting Mode, which allows for High Density partitions (that allow for up to 1000 indexes) should be supported. Possible values are `highDensity` or `default`. Defaults to `default`. Changing this forces a new Search Service to be created. -* `public_network_access_enabled` - (Optional) Whether or not public network access is allowed for this resource. Defaults to `true`. +-> **NOTE:** `hosting_mode` can only be configured when `sku` is set to `standard3`. -* `partition_count` - (Optional) The number of partitions which should be created. Possible values include `1`, `2`, `3`, `4`, `6`, or `12`. Defaults to `1`. --> **NOTE:** `partition_count` cannot be configured when using a `free` or `basic` SKU. For more information please to the [product documentation](https://learn.microsoft.com/azure/search/search-sku-tier). +* `identity` - (Optional) An `identity` block as defined below. -* `replica_count` - (Optional) The number of replica's which should be created. +* `local_authentication_enabled` - (Optional) Specifies whether the Search Service allows authenticating using API Keys? Defaults to `false`. --> **NOTE:** `replica_count` cannot be configured when using a `free` SKU. For more information please to the [product documentation](https://learn.microsoft.com/azure/search/search-sku-tier). +* `partition_count` - (Optional) Specifies the number of partitions which should be created. This field cannot be set when using a `free` or `basic` sku ([see the Microsoft documentation](https://learn.microsoft.com/azure/search/search-sku-tier)). Possible values include `1`, `2`, `3`, `4`, `6`, or `12`. Defaults to `1`. -* `allowed_ips` - (Optional) A list of inbound IPv4 or CIDRs that are allowed to access the Search Service. If the incoming IP request is from an IP address which is not included in the `allowed_ips` it will be blocked by the Search Services firewall. +-> **NOTE:** when `hosting_mode` is set to `highDensity` the maximum number of partitions allowed is `3`. --> **NOTE:** The `allowed_ips` are only applied if the `public_network_access_enabled` field has been set to `true`, else all traffic over the public interface will be rejected, even if the `allowed_ips` field has been defined. When the `public_network_access_enabled` field has been set to `false` the private endpoint connections are the only allowed access point to the Search Service. +* `public_network_access_enabled` - (Optional) Specifies whether Public Network Access is allowed for this resource. Defaults to `true`. -* `identity` - (Optional) An `identity` block as defined below. +* `replica_count` - (Optional) Specifies the number of Replica's which should be created for this Search Service. This field cannot be set when using a `free` sku ([see the Microsoft documentation](https://learn.microsoft.com/azure/search/search-sku-tier)). -* `tags` - (Optional) A mapping of tags which should be assigned to the Search Service. +* `tags` - (Optional) Specifies a mapping of tags which should be assigned to this Search Service. --- @@ -90,8 +124,6 @@ In addition to the Arguments listed above - the following Attributes are exporte * `id` - The ID of the Search Service. -* `customer_managed_key_enforcement_compliance` - Describes whether the Search Service is `compliant` or not with respect to having non customer encrypted resources. - * `primary_key` - The Primary Key used for Search Service Administration. * `query_keys` - A `query_keys` block as defined below. @@ -116,20 +148,6 @@ An `identity` block exports the following: --- -## Setting API Access Control: - -The values of the `local_authentication_disabled` and `authentication_failure_mode` fields will determin which `API Access Control` mode the Search Service will operate under. To correctly configure your Search Service to your desired `API Access Control` mode please configure your Search Service based on the field values in the below table. - -| Field Name | Value | API Access Control Mode | -|------------------------------------------------------------------|:---------------------------------------------------------:|:----------------------------------------:| -| `local_authentication_disabled`
`authentication_failure_mode` | `false`
`` | `API key` (`Default`) | -| `local_authentication_disabled`
`authentication_failure_mode` | `false`
`authentication_failure_mode` as defined above | `Role-based access control and API Key` | -| `local_authentication_disabled`
`authentication_failure_mode` | `true`
`` | `Role-based access control` | - --> **NOTE:** When the Search Service is in `Role-based access contol` (e.g. `local_authentication_disabled` is `true`) the `authentication_failure_mode` field *cannot* be defined. - ---- - ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: