diff --git a/.changelog/17723.txt b/.changelog/17723.txt new file mode 100644 index 000000000000..6f0e5f6d2373 --- /dev/null +++ b/.changelog/17723.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_cloudsearch_domain +``` + +```release-note:new-resource +aws_cloudsearch_domain_service_access_policy +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 646c1cc115e8..f74edaf2c507 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -33,6 +33,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/cloudformation" "github.com/hashicorp/terraform-provider-aws/internal/service/cloudfront" "github.com/hashicorp/terraform-provider-aws/internal/service/cloudhsmv2" + "github.com/hashicorp/terraform-provider-aws/internal/service/cloudsearch" "github.com/hashicorp/terraform-provider-aws/internal/service/cloudtrail" "github.com/hashicorp/terraform-provider-aws/internal/service/cloudwatch" "github.com/hashicorp/terraform-provider-aws/internal/service/cloudwatchlogs" @@ -881,6 +882,9 @@ func Provider() *schema.Provider { "aws_cloudhsm_v2_cluster": cloudhsmv2.ResourceCluster(), "aws_cloudhsm_v2_hsm": cloudhsmv2.ResourceHSM(), + "aws_cloudsearch_domain": cloudsearch.ResourceDomain(), + "aws_cloudsearch_domain_service_access_policy": cloudsearch.ResourceDomainServiceAccessPolicy(), + "aws_cloudtrail": cloudtrail.ResourceCloudTrail(), "aws_cloudwatch_composite_alarm": cloudwatch.ResourceCompositeAlarm(), diff --git a/internal/service/cloudsearch/README.md b/internal/service/cloudsearch/README.md new file mode 100644 index 000000000000..69adee1ed4a7 --- /dev/null +++ b/internal/service/cloudsearch/README.md @@ -0,0 +1,10 @@ +# Terraform AWS Provider CloudSearch Package + +This area is primarily for AWS provider contributors and maintainers. For information on _using_ Terraform and the AWS provider, see the links below. + + +## Handy Links +* [Find out about contributing](../../../docs/contributing) to the AWS provider! +* AWS Provider Docs: [Home](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) +* AWS Provider Docs: [One of the Budgets resources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudsearch_domain) +* AWS Docs: [AWS SDK for Go Budgets](https://docs.aws.amazon.com/sdk-for-go/api/service/cloudsearch/) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go new file mode 100644 index 000000000000..7c3534bc2397 --- /dev/null +++ b/internal/service/cloudsearch/domain.go @@ -0,0 +1,1283 @@ +package cloudsearch + +import ( + "fmt" + "log" + "regexp" + "strconv" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudsearch" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func ResourceDomain() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudSearchDomainCreate, + Read: resourceCloudSearchDomainRead, + Update: resourceCloudSearchDomainUpdate, + Delete: resourceCloudSearchDomainDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "document_service_endpoint": { + Type: schema.TypeString, + Computed: true, + }, + "domain_id": { + Type: schema.TypeString, + Computed: true, + }, + "endpoint_options": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enforce_https": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "tls_security_policy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(cloudsearch.TLSSecurityPolicy_Values(), false), + }, + }, + }, + }, + // The index_field schema is based on the AWS Console screen, not the API model. + "index_field": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "analysis_scheme": { + Type: schema.TypeString, + Optional: true, + }, + "default_value": { + Type: schema.TypeString, + Optional: true, + }, + "facet": { + Type: schema.TypeBool, + Optional: true, + }, + "highlight": { + Type: schema.TypeBool, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateIndexName, + }, + "return": { + Type: schema.TypeBool, + Optional: true, + }, + "search": { + Type: schema.TypeBool, + Optional: true, + }, + "sort": { + Type: schema.TypeBool, + Optional: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(cloudsearch.IndexFieldType_Values(), false), + }, + }, + }, + }, + "multi_az": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[a-z]([a-z0-9-]){2,27}$`), "Search domain names must start with a lowercase letter (a-z) and be at least 3 and no more than 28 lower-case letters, digits or hyphens"), + }, + "scaling_parameters": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "desired_instance_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(cloudsearch.PartitionInstanceType_Values(), false), + }, + "desired_partition_count": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "desired_replication_count": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + }, + }, + "search_service_endpoint": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn + + name := d.Get("name").(string) + input := cloudsearch.CreateDomainInput{ + DomainName: aws.String(name), + } + + log.Printf("[DEBUG] Creating CloudSearch Domain: %s", input) + _, err := conn.CreateDomain(&input) + + if err != nil { + return fmt.Errorf("error creating CloudSearch Domain (%s): %w", name, err) + } + + d.SetId(name) + + if v, ok := d.GetOk("scaling_parameters"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input := &cloudsearch.UpdateScalingParametersInput{ + DomainName: aws.String(d.Id()), + ScalingParameters: expandScalingParameters(v.([]interface{})[0].(map[string]interface{})), + } + + log.Printf("[DEBUG] Updating CloudSearch Domain scaling parameters: %s", input) + _, err := conn.UpdateScalingParameters(input) + + if err != nil { + return fmt.Errorf("error updating CloudSearch Domain (%s) scaling parameters: %w", d.Id(), err) + } + } + + if v, ok := d.GetOk("multi_az"); ok { + input := &cloudsearch.UpdateAvailabilityOptionsInput{ + DomainName: aws.String(d.Id()), + MultiAZ: aws.Bool(v.(bool)), + } + + log.Printf("[DEBUG] Updating CloudSearch Domain availability options: %s", input) + _, err := conn.UpdateAvailabilityOptions(input) + + if err != nil { + return fmt.Errorf("error updating CloudSearch Domain (%s) availability options: %w", d.Id(), err) + } + } + + if v, ok := d.GetOk("endpoint_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input := &cloudsearch.UpdateDomainEndpointOptionsInput{ + DomainEndpointOptions: expandDomainEndpointOptions(v.([]interface{})[0].(map[string]interface{})), + DomainName: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Updating CloudSearch Domain endpoint options: %s", input) + _, err := conn.UpdateDomainEndpointOptions(input) + + if err != nil { + return fmt.Errorf("error updating CloudSearch Domain (%s) endpoint options: %w", d.Id(), err) + } + } + + if v, ok := d.GetOk("index_field"); ok && v.(*schema.Set).Len() > 0 { + err := defineIndexFields(conn, d.Id(), v.(*schema.Set).List()) + + if err != nil { + return err + } + + _, err = conn.IndexDocuments(&cloudsearch.IndexDocumentsInput{ + DomainName: aws.String(d.Id()), + }) + + if err != nil { + return fmt.Errorf("error indexing CloudSearch Domain (%s) documents: %w", d.Id(), err) + } + } + + // TODO: Status.RequiresIndexDocuments = true? + + _, err = waitDomainActive(conn, d.Id(), d.Timeout(schema.TimeoutCreate)) + + if err != nil { + return fmt.Errorf("error waiting for CloudSearch Domain (%s) create: %w", d.Id(), err) + } + + return resourceCloudSearchDomainRead(d, meta) +} + +func resourceCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn + + domainStatus, err := FindDomainStatusByName(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] CloudSearch Domain (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading CloudSearch Domain (%s): %w", d.Id(), err) + } + + d.Set("arn", domainStatus.ARN) + d.Set("domain_id", domainStatus.DomainId) + d.Set("name", domainStatus.DomainName) + + if domainStatus.DocService != nil { + d.Set("document_service_endpoint", domainStatus.DocService.Endpoint) + } else { + d.Set("document_service_endpoint", nil) + } + if domainStatus.SearchService != nil { + d.Set("search_service_endpoint", domainStatus.SearchService.Endpoint) + } else { + d.Set("search_service_endpoint", nil) + } + + availabilityOptionStatus, err := findAvailabilityOptionsStatusByName(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error reading CloudSearch Domain (%s) availability options: %w", d.Id(), err) + } + + d.Set("multi_az", availabilityOptionStatus.Options) + + endpointOptions, err := findDomainEndpointOptionsByName(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error reading CloudSearch Domain (%s) endpoint options: %w", d.Id(), err) + } + + if err := d.Set("endpoint_options", []interface{}{flattenDomainEndpointOptions(endpointOptions)}); err != nil { + return fmt.Errorf("error setting endpoint_options: %w", err) + } + + scalingParameters, err := findScalingParametersByName(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error reading CloudSearch Domain (%s) scaling parameters: %w", d.Id(), err) + } + + if err := d.Set("scaling_parameters", []interface{}{flattenScalingParameters(scalingParameters)}); err != nil { + return fmt.Errorf("error setting scaling_parameters: %w", err) + } + + indexResults, err := conn.DescribeIndexFields(&cloudsearch.DescribeIndexFieldsInput{ + DomainName: aws.String(d.Get("name").(string)), + }) + + if err != nil { + return fmt.Errorf("error reading CloudSearch Domain (%s) index fields: %w", d.Id(), err) + } + + if tfList, err := flattenIndexFieldStatuses(indexResults.IndexFields); err != nil { + return err + } else if err := d.Set("index_field", tfList); err != nil { + return fmt.Errorf("error setting index_field: %w", err) + } + + return nil +} + +func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn + requiresIndexDocuments := false + + if d.HasChange("scaling_parameters") { + input := &cloudsearch.UpdateScalingParametersInput{ + DomainName: aws.String(d.Id()), + } + + if v, ok := d.GetOk("scaling_parameters"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.ScalingParameters = expandScalingParameters(v.([]interface{})[0].(map[string]interface{})) + } else { + input.ScalingParameters = &cloudsearch.ScalingParameters{} + } + + log.Printf("[DEBUG] Updating CloudSearch Domain scaling parameters: %s", input) + output, err := conn.UpdateScalingParameters(input) + + if err != nil { + return fmt.Errorf("error updating CloudSearch Domain (%s) scaling parameters: %w", d.Id(), err) + } + + if output != nil && output.ScalingParameters != nil && output.ScalingParameters.Status != nil && aws.StringValue(output.ScalingParameters.Status.State) == cloudsearch.OptionStateRequiresIndexDocuments { + requiresIndexDocuments = true + } + } + + if d.HasChange("multi_az") { + input := &cloudsearch.UpdateAvailabilityOptionsInput{ + DomainName: aws.String(d.Id()), + MultiAZ: aws.Bool(d.Get("multi_az").(bool)), + } + + log.Printf("[DEBUG] Updating CloudSearch Domain availability options: %s", input) + output, err := conn.UpdateAvailabilityOptions(input) + + if err != nil { + return fmt.Errorf("error updating CloudSearch Domain (%s) availability options: %w", d.Id(), err) + } + + if output != nil && output.AvailabilityOptions != nil && output.AvailabilityOptions.Status != nil && aws.StringValue(output.AvailabilityOptions.Status.State) == cloudsearch.OptionStateRequiresIndexDocuments { + requiresIndexDocuments = true + } + } + + if d.HasChange("endpoint_options") { + input := &cloudsearch.UpdateDomainEndpointOptionsInput{ + DomainName: aws.String(d.Id()), + } + + if v, ok := d.GetOk("endpoint_options"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.DomainEndpointOptions = expandDomainEndpointOptions(v.([]interface{})[0].(map[string]interface{})) + } else { + input.DomainEndpointOptions = &cloudsearch.DomainEndpointOptions{} + } + + log.Printf("[DEBUG] Updating CloudSearch Domain endpoint options: %s", input) + output, err := conn.UpdateDomainEndpointOptions(input) + + if err != nil { + return fmt.Errorf("error updating CloudSearch Domain (%s) endpoint options: %w", d.Id(), err) + } + + if output != nil && output.DomainEndpointOptions != nil && output.DomainEndpointOptions.Status != nil && aws.StringValue(output.DomainEndpointOptions.Status.State) == cloudsearch.OptionStateRequiresIndexDocuments { + requiresIndexDocuments = true + } + } + + if d.HasChange("index_field") { + o, n := d.GetChange("index_field") + old := o.(*schema.Set) + new := n.(*schema.Set) + + for _, tfMapRaw := range old.Difference(new).List() { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + fieldName, _ := tfMap["name"].(string) + + if fieldName == "" { + continue + } + + _, err := conn.DeleteIndexField(&cloudsearch.DeleteIndexFieldInput{ + DomainName: aws.String(d.Id()), + IndexFieldName: aws.String(fieldName), + }) + + if err != nil { + return fmt.Errorf("error deleting CloudSearch Domain (%s) index field (%s): %w", d.Id(), fieldName, err) + } + + requiresIndexDocuments = true + } + + if v := new.Difference(old); v.Len() > 0 { + if err := defineIndexFields(conn, d.Id(), v.List()); err != nil { + return err + } + + requiresIndexDocuments = true + } + } + + if requiresIndexDocuments { + _, err := conn.IndexDocuments(&cloudsearch.IndexDocumentsInput{ + DomainName: aws.String(d.Id()), + }) + + if err != nil { + return fmt.Errorf("error indexing CloudSearch Domain (%s) documents: %w", d.Id(), err) + } + } + + _, err := waitDomainActive(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("error waiting for CloudSearch Domain (%s) update: %w", d.Id(), err) + } + + return resourceCloudSearchDomainRead(d, meta) +} + +func resourceCloudSearchDomainDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn + + log.Printf("[DEBUG] Deleting CloudSearch Domain: %s", d.Id()) + + _, err := conn.DeleteDomain(&cloudsearch.DeleteDomainInput{ + DomainName: aws.String(d.Id()), + }) + + if err != nil { + return fmt.Errorf("error deleting CloudSearch Domain (%s): %w", d.Id(), err) + } + + _, err = waitDomainDeleted(conn, d.Id(), d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return fmt.Errorf("error waiting for CloudSearch Domain (%s) delete: %w", d.Id(), err) + } + + return nil +} + +func validateIndexName(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + + if !regexp.MustCompile(`^(\*?[a-z][a-z0-9_]{2,63}|[a-z][a-z0-9_]{2,63}\*?)$`).MatchString(value) { + es = append(es, fmt.Errorf( + "%q must begin with a letter and be at least 3 and no more than 64 characters long", k)) + } + + if value == "score" { + es = append(es, fmt.Errorf("'score' is a reserved field name and cannot be used")) + } + + return +} + +func defineIndexFields(conn *cloudsearch.CloudSearch, domainName string, tfList []interface{}) error { + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject, err := expandIndexField(tfMap) + + if err != nil { + return err + } + + if apiObject == nil { + continue + } + + _, err = conn.DefineIndexField(&cloudsearch.DefineIndexFieldInput{ + DomainName: aws.String(domainName), + IndexField: apiObject, + }) + + if err != nil { + return fmt.Errorf("error defining CloudSearch Domain (%s) index field (%s): %w", domainName, aws.StringValue(apiObject.IndexFieldName), err) + } + } + + return nil +} + +func FindDomainStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.DomainStatus, error) { + input := &cloudsearch.DescribeDomainsInput{ + DomainNames: aws.StringSlice([]string{name}), + } + + output, err := conn.DescribeDomains(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.DomainStatusList) == 0 || output.DomainStatusList[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.DomainStatusList); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.DomainStatusList[0], nil +} + +func findAvailabilityOptionsStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.AvailabilityOptionsStatus, error) { + input := &cloudsearch.DescribeAvailabilityOptionsInput{ + DomainName: aws.String(name), + } + + output, err := conn.DescribeAvailabilityOptions(input) + + if tfawserr.ErrCodeEquals(err, cloudsearch.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.AvailabilityOptions == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.AvailabilityOptions, nil +} + +func findDomainEndpointOptionsByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.DomainEndpointOptions, error) { + output, err := findDomainEndpointOptionsStatusByName(conn, name) + + if err != nil { + return nil, err + } + + if output.Options == nil { + return nil, tfresource.NewEmptyResultError(name) + } + + return output.Options, nil +} + +func findDomainEndpointOptionsStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.DomainEndpointOptionsStatus, error) { + input := &cloudsearch.DescribeDomainEndpointOptionsInput{ + DomainName: aws.String(name), + } + + output, err := conn.DescribeDomainEndpointOptions(input) + + if tfawserr.ErrCodeEquals(err, cloudsearch.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.DomainEndpointOptions == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.DomainEndpointOptions, nil +} + +func findScalingParametersByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.ScalingParameters, error) { + output, err := findScalingParametersStatusByName(conn, name) + + if err != nil { + return nil, err + } + + if output.Options == nil { + return nil, tfresource.NewEmptyResultError(name) + } + + return output.Options, nil +} + +func findScalingParametersStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.ScalingParametersStatus, error) { + input := &cloudsearch.DescribeScalingParametersInput{ + DomainName: aws.String(name), + } + + output, err := conn.DescribeScalingParameters(input) + + if tfawserr.ErrCodeEquals(err, cloudsearch.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.ScalingParameters == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ScalingParameters, nil +} + +func statusDomainDeleting(conn *cloudsearch.CloudSearch, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindDomainStatusByName(conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, strconv.FormatBool(aws.BoolValue(output.Deleted)), nil + } +} + +func statusDomainProcessing(conn *cloudsearch.CloudSearch, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindDomainStatusByName(conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, strconv.FormatBool(aws.BoolValue(output.Processing)), nil + } +} + +func waitDomainActive(conn *cloudsearch.CloudSearch, name string, timeout time.Duration) (*cloudsearch.DomainStatus, error) { //nolint:unparam + stateConf := &resource.StateChangeConf{ + Pending: []string{"true"}, + Target: []string{"false"}, + Refresh: statusDomainProcessing(conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*cloudsearch.DomainStatus); ok { + return output, err + } + + return nil, err +} + +func waitDomainDeleted(conn *cloudsearch.CloudSearch, name string, timeout time.Duration) (*cloudsearch.DomainStatus, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{"true"}, + Target: []string{}, + Refresh: statusDomainDeleting(conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*cloudsearch.DomainStatus); ok { + return output, err + } + + return nil, err +} + +func expandDomainEndpointOptions(tfMap map[string]interface{}) *cloudsearch.DomainEndpointOptions { + if tfMap == nil { + return nil + } + + apiObject := &cloudsearch.DomainEndpointOptions{} + + if v, ok := tfMap["enforce_https"].(bool); ok { + apiObject.EnforceHTTPS = aws.Bool(v) + } + + if v, ok := tfMap["tls_security_policy"].(string); ok && v != "" { + apiObject.TLSSecurityPolicy = aws.String(v) + } + + return apiObject +} + +func flattenDomainEndpointOptions(apiObject *cloudsearch.DomainEndpointOptions) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.EnforceHTTPS; v != nil { + tfMap["enforce_https"] = aws.BoolValue(v) + } + + if v := apiObject.TLSSecurityPolicy; v != nil { + tfMap["tls_security_policy"] = aws.StringValue(v) + } + + return tfMap +} + +func expandIndexField(tfMap map[string]interface{}) (*cloudsearch.IndexField, error) { + if tfMap == nil { + return nil, nil + } + + apiObject := &cloudsearch.IndexField{} + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.IndexFieldName = aws.String(v) + } + + fieldType, ok := tfMap["type"].(string) + if ok && fieldType != "" { + apiObject.IndexFieldType = aws.String(fieldType) + } + + analysisScheme, _ := tfMap["analysis_scheme"].(string) + facetEnabled, _ := tfMap["facet"].(bool) + highlightEnabled, _ := tfMap["highlight"].(bool) + returnEnabled, _ := tfMap["return"].(bool) + searchEnabled, _ := tfMap["search"].(bool) + sortEnabled, _ := tfMap["sort"].(bool) + + switch fieldType { + case cloudsearch.IndexFieldTypeDate: + options := &cloudsearch.DateOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + SortEnabled: aws.Bool(sortEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + options.DefaultValue = aws.String(v) + } + + apiObject.DateOptions = options + + case cloudsearch.IndexFieldTypeDateArray: + options := &cloudsearch.DateArrayOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + options.DefaultValue = aws.String(v) + } + + apiObject.DateArrayOptions = options + + case cloudsearch.IndexFieldTypeDouble: + options := &cloudsearch.DoubleOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + SortEnabled: aws.Bool(sortEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + v, err := strconv.ParseFloat(v, 64) + + if err != nil { + return nil, err + } + + options.DefaultValue = aws.Float64(v) + } + + apiObject.DoubleOptions = options + + case cloudsearch.IndexFieldTypeDoubleArray: + options := &cloudsearch.DoubleArrayOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + v, err := strconv.ParseFloat(v, 64) + + if err != nil { + return nil, err + } + + options.DefaultValue = aws.Float64(v) + } + + apiObject.DoubleArrayOptions = options + + case cloudsearch.IndexFieldTypeInt: + options := &cloudsearch.IntOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + SortEnabled: aws.Bool(sortEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + v, err := strconv.Atoi(v) + + if err != nil { + return nil, err + } + + options.DefaultValue = aws.Int64(int64(v)) + } + + apiObject.IntOptions = options + + case cloudsearch.IndexFieldTypeIntArray: + options := &cloudsearch.IntArrayOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + v, err := strconv.Atoi(v) + + if err != nil { + return nil, err + } + + options.DefaultValue = aws.Int64(int64(v)) + } + + apiObject.IntArrayOptions = options + + case cloudsearch.IndexFieldTypeLatlon: + options := &cloudsearch.LatLonOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + SortEnabled: aws.Bool(sortEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + options.DefaultValue = aws.String(v) + } + + apiObject.LatLonOptions = options + + case cloudsearch.IndexFieldTypeLiteral: + options := &cloudsearch.LiteralOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + SortEnabled: aws.Bool(sortEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + options.DefaultValue = aws.String(v) + } + + apiObject.LiteralOptions = options + + case cloudsearch.IndexFieldTypeLiteralArray: + options := &cloudsearch.LiteralArrayOptions{ + FacetEnabled: aws.Bool(facetEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SearchEnabled: aws.Bool(searchEnabled), + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + options.DefaultValue = aws.String(v) + } + + apiObject.LiteralArrayOptions = options + + case cloudsearch.IndexFieldTypeText: + options := &cloudsearch.TextOptions{ + HighlightEnabled: aws.Bool(highlightEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + SortEnabled: aws.Bool(sortEnabled), + } + + if analysisScheme != "" { + options.AnalysisScheme = aws.String(analysisScheme) + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + options.DefaultValue = aws.String(v) + } + + apiObject.TextOptions = options + + case cloudsearch.IndexFieldTypeTextArray: + options := &cloudsearch.TextArrayOptions{ + HighlightEnabled: aws.Bool(highlightEnabled), + ReturnEnabled: aws.Bool(returnEnabled), + } + + if analysisScheme != "" { + options.AnalysisScheme = aws.String(analysisScheme) + } + + if v, ok := tfMap["default_value"].(string); ok && v != "" { + options.DefaultValue = aws.String(v) + } + + apiObject.TextArrayOptions = options + + default: + return nil, fmt.Errorf("unsupported index_field type: %s", fieldType) + } + + return apiObject, nil +} + +func flattenIndexFieldStatus(apiObject *cloudsearch.IndexFieldStatus) (map[string]interface{}, error) { + if apiObject == nil || apiObject.Options == nil || apiObject.Status == nil { + return nil, nil + } + + // Don't read in any fields that are pending deletion. + if aws.BoolValue(apiObject.Status.PendingDeletion) { + return nil, nil + } + + field := apiObject.Options + tfMap := map[string]interface{}{} + + if v := field.IndexFieldName; v != nil { + tfMap["name"] = aws.StringValue(v) + } + + fieldType := field.IndexFieldType + if fieldType != nil { + tfMap["type"] = aws.StringValue(fieldType) + } + + switch fieldType := aws.StringValue(fieldType); fieldType { + case cloudsearch.IndexFieldTypeDate: + options := field.DateOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = aws.StringValue(v) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + if v := options.SortEnabled; v != nil { + tfMap["sort"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeDateArray: + options := field.DateArrayOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = aws.StringValue(v) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeDouble: + options := field.DoubleOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = strconv.FormatFloat(aws.Float64Value(v), 'f', -1, 64) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + if v := options.SortEnabled; v != nil { + tfMap["sort"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeDoubleArray: + options := field.DoubleArrayOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = strconv.FormatFloat(aws.Float64Value(v), 'f', -1, 64) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeInt: + options := field.IntOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = strconv.FormatInt(aws.Int64Value(v), 10) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + if v := options.SortEnabled; v != nil { + tfMap["sort"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeIntArray: + options := field.IntArrayOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = strconv.FormatInt(aws.Int64Value(v), 10) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeLatlon: + options := field.LatLonOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = aws.StringValue(v) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + if v := options.SortEnabled; v != nil { + tfMap["sort"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeLiteral: + options := field.LiteralOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = aws.StringValue(v) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + if v := options.SortEnabled; v != nil { + tfMap["sort"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeLiteralArray: + options := field.LiteralArrayOptions + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = aws.StringValue(v) + } + + if v := options.FacetEnabled; v != nil { + tfMap["facet"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SearchEnabled; v != nil { + tfMap["search"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeText: + options := field.TextOptions + + if v := options.AnalysisScheme; v != nil { + tfMap["analysis_scheme"] = aws.StringValue(v) + } + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = aws.StringValue(v) + } + + if v := options.HighlightEnabled; v != nil { + tfMap["highlight"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + if v := options.SortEnabled; v != nil { + tfMap["sort"] = aws.BoolValue(v) + } + + case cloudsearch.IndexFieldTypeTextArray: + options := field.TextArrayOptions + + if v := options.AnalysisScheme; v != nil { + tfMap["analysis_scheme"] = aws.StringValue(v) + } + + if v := options.DefaultValue; v != nil { + tfMap["default_value"] = aws.StringValue(v) + } + + if v := options.HighlightEnabled; v != nil { + tfMap["highlight"] = aws.BoolValue(v) + } + + if v := options.ReturnEnabled; v != nil { + tfMap["return"] = aws.BoolValue(v) + } + + default: + return nil, fmt.Errorf("unsupported index_field type: %s", fieldType) + } + + return tfMap, nil +} + +func flattenIndexFieldStatuses(apiObjects []*cloudsearch.IndexFieldStatus) ([]interface{}, error) { + if len(apiObjects) == 0 { + return nil, nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfMap, err := flattenIndexFieldStatus(apiObject) + + if err != nil { + return nil, err + } + + tfList = append(tfList, tfMap) + } + + return tfList, nil +} + +func expandScalingParameters(tfMap map[string]interface{}) *cloudsearch.ScalingParameters { + if tfMap == nil { + return nil + } + + apiObject := &cloudsearch.ScalingParameters{} + + if v, ok := tfMap["desired_instance_type"].(string); ok && v != "" { + apiObject.DesiredInstanceType = aws.String(v) + } + + if v, ok := tfMap["desired_partition_count"].(int); ok && v != 0 { + apiObject.DesiredPartitionCount = aws.Int64(int64(v)) + } + + if v, ok := tfMap["desired_replication_count"].(int); ok && v != 0 { + apiObject.DesiredReplicationCount = aws.Int64(int64(v)) + } + + return apiObject +} + +func flattenScalingParameters(apiObject *cloudsearch.ScalingParameters) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.DesiredInstanceType; v != nil { + tfMap["desired_instance_type"] = aws.StringValue(v) + } + + if v := apiObject.DesiredPartitionCount; v != nil { + tfMap["desired_partition_count"] = aws.Int64Value(v) + } + + if v := apiObject.DesiredReplicationCount; v != nil { + tfMap["desired_replication_count"] = aws.Int64Value(v) + } + + return tfMap +} diff --git a/internal/service/cloudsearch/domain_service_access_policy.go b/internal/service/cloudsearch/domain_service_access_policy.go new file mode 100644 index 000000000000..1d49e6c7bc32 --- /dev/null +++ b/internal/service/cloudsearch/domain_service_access_policy.go @@ -0,0 +1,216 @@ +package cloudsearch + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudsearch" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceDomainServiceAccessPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceDomainServiceAccessPolicyPut, + Read: resourceDomainServiceAccessPolicyRead, + Update: resourceDomainServiceAccessPolicyPut, + Delete: resourceDomainServiceAccessPolicyDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "access_policy": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, + ValidateFunc: validation.StringIsJSON, + StateFunc: func(v interface{}) string { + json, _ := structure.NormalizeJsonString(v) + return json + }, + }, + "domain_name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceDomainServiceAccessPolicyPut(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn + + domainName := d.Get("domain_name").(string) + input := &cloudsearch.UpdateServiceAccessPoliciesInput{ + DomainName: aws.String(domainName), + } + + accessPolicy := d.Get("access_policy").(string) + policy, err := structure.NormalizeJsonString(accessPolicy) + + if err != nil { + return fmt.Errorf("policy (%s) is invalid JSON: %w", accessPolicy, err) + } + + input.AccessPolicies = aws.String(policy) + + log.Printf("[DEBUG] Updating CloudSearch Domain access policies: %s", input) + _, err = conn.UpdateServiceAccessPolicies(input) + + if err != nil { + return fmt.Errorf("error updating CloudSearch Domain Service Access Policy (%s): %w", domainName, err) + } + + d.SetId(domainName) + + _, err = waitAccessPolicyActive(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("error waiting for CloudSearch Domain Service Access Policy (%s) to become active: %w", d.Id(), err) + } + + return resourceDomainServiceAccessPolicyRead(d, meta) +} + +func resourceDomainServiceAccessPolicyRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn + + accessPolicy, err := FindAccessPolicyByName(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] CloudSearch Domain Service Access Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading CloudSearch Domain Service Access Policy (%s): %w", d.Id(), err) + } + + policyToSet, err := verify.PolicyToSet(d.Get("access_policy").(string), accessPolicy) + + if err != nil { + return err + } + + d.Set("access_policy", policyToSet) + d.Set("domain_name", d.Id()) + + return nil +} + +func resourceDomainServiceAccessPolicyDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn + + input := &cloudsearch.UpdateServiceAccessPoliciesInput{ + AccessPolicies: aws.String(""), + DomainName: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting CloudSearch Domain Service Access Policy: %s", d.Id()) + _, err := conn.UpdateServiceAccessPolicies(input) + + if tfawserr.ErrCodeEquals(err, cloudsearch.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting CloudSearch Domain Service Access Policy (%s): %w", d.Id(), err) + } + + _, err = waitAccessPolicyActive(conn, d.Id(), d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return fmt.Errorf("error waiting for CloudSearch Domain Service Access Policy (%s) to delete: %w", d.Id(), err) + } + + return nil +} + +func FindAccessPolicyByName(conn *cloudsearch.CloudSearch, name string) (string, error) { + output, err := findAccessPoliciesStatusByName(conn, name) + + if err != nil { + return "", err + } + + accessPolicy := aws.StringValue(output.Options) + + if accessPolicy == "" { + return "", tfresource.NewEmptyResultError(name) + } + + return accessPolicy, nil +} + +func findAccessPoliciesStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.AccessPoliciesStatus, error) { + input := &cloudsearch.DescribeServiceAccessPoliciesInput{ + DomainName: aws.String(name), + } + + output, err := conn.DescribeServiceAccessPolicies(input) + + if tfawserr.ErrCodeEquals(err, cloudsearch.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.AccessPolicies == nil || output.AccessPolicies.Status == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.AccessPolicies, nil +} + +func statusAccessPolicyState(conn *cloudsearch.CloudSearch, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findAccessPoliciesStatusByName(conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status.State), nil + } +} + +func waitAccessPolicyActive(conn *cloudsearch.CloudSearch, name string, timeout time.Duration) (*cloudsearch.AccessPoliciesStatus, error) { //nolint:unparam + stateConf := &resource.StateChangeConf{ + Pending: []string{cloudsearch.OptionStateProcessing}, + Target: []string{cloudsearch.OptionStateActive}, + Refresh: statusAccessPolicyState(conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*cloudsearch.AccessPoliciesStatus); ok { + return output, err + } + + return nil, err +} diff --git a/internal/service/cloudsearch/domain_service_access_policy_test.go b/internal/service/cloudsearch/domain_service_access_policy_test.go new file mode 100644 index 000000000000..0d52e9d82df3 --- /dev/null +++ b/internal/service/cloudsearch/domain_service_access_policy_test.go @@ -0,0 +1,173 @@ +package cloudsearch_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/cloudsearch" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfcloudsearch "github.com/hashicorp/terraform-provider-aws/internal/service/cloudsearch" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccCloudSearchDomainServiceAccessPolicy_basic(t *testing.T) { + resourceName := "aws_cloudsearch_domain_service_access_policy.test" + rName := acctest.ResourcePrefix + "-" + sdkacctest.RandString(28-(len(acctest.ResourcePrefix)+1)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(cloudsearch.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, cloudsearch.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCloudSearchDomainServiceAccessPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDomainServiceAccessPolicyConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCloudSearchDomainServiceAccessPolicyExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "access_policy"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCloudSearchDomainServiceAccessPolicy_update(t *testing.T) { + resourceName := "aws_cloudsearch_domain_service_access_policy.test" + rName := acctest.ResourcePrefix + "-" + sdkacctest.RandString(28-(len(acctest.ResourcePrefix)+1)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(cloudsearch.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, cloudsearch.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCloudSearchDomainServiceAccessPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDomainServiceAccessPolicyConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCloudSearchDomainServiceAccessPolicyExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "access_policy"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDomainServiceAccessPolicyConfigUpdated(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCloudSearchDomainServiceAccessPolicyExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "access_policy"), + ), + }, + }, + }) +} + +func testAccCloudSearchDomainServiceAccessPolicyExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No CloudSearch Domain Service Access Policy ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudSearchConn + + _, err := tfcloudsearch.FindAccessPolicyByName(conn, rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccCloudSearchDomainServiceAccessPolicyDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudsearch_domain_service_access_policy" { + continue + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudSearchConn + + _, err := tfcloudsearch.FindAccessPolicyByName(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CloudSearch Domain Service Access Policy %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccDomainServiceAccessPolicyConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_cloudsearch_domain" "test" { + name = %[1]q +} + +resource "aws_cloudsearch_domain_service_access_policy" "test" { + domain_name = aws_cloudsearch_domain.test.id + + access_policy = <