From 7b3634226e27d91b0a9fe235b245488f35457994 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Thu, 18 Feb 2021 18:45:20 +0000 Subject: [PATCH 01/50] first stab at CloudSearch support --- resource_aws_cloudsearch_domain.go | 664 ++++++++++++++++++++++++ resource_aws_cloudsearch_domain_test.go | 270 ++++++++++ 2 files changed, 934 insertions(+) create mode 100644 resource_aws_cloudsearch_domain.go create mode 100644 resource_aws_cloudsearch_domain_test.go diff --git a/resource_aws_cloudsearch_domain.go b/resource_aws_cloudsearch_domain.go new file mode 100644 index 000000000000..a68d741e716e --- /dev/null +++ b/resource_aws_cloudsearch_domain.go @@ -0,0 +1,664 @@ +package aws + +import ( + "fmt" + "log" + "reflect" + "regexp" + "strconv" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudsearch" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceAwsCloudSearchDomain() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCloudSearchDomainCreate, + Read: resourceAwsCloudSearchDomainRead, + Update: resourceAwsCloudSearchDomainUpdate, + Delete: resourceAwsCloudSearchDomainDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validateDomainName, + }, + + "instance_type": { + Type: schema.TypeString, + Optional: true, + Default: "search.small", + }, + + "replication_count": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + }, + + "partition_count": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + }, + + "multi_az": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "access_policy": { + Type: schema.TypeString, + ValidateFunc: validateIAMPolicyJson, + Required: true, + DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, + }, + + "index": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateIndexName, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateIndexType, + }, + + "search": { + Type: schema.TypeBool, + Required: true, + }, + + "facet": { + Type: schema.TypeBool, + Optional: true, + }, + + "return": { + Type: schema.TypeBool, + Required: true, + }, + + "sort": { + Type: schema.TypeBool, + Optional: true, + }, + + "highlight": { + Type: schema.TypeBool, + Optional: true, + }, + + "analysis_scheme": { + Type: schema.TypeString, + Optional: true, + }, + + "default_value": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + + "wait_for_endpoints": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "id": { + Type: schema.TypeString, + Computed: true, + }, + + "document_endpoint": { + Type: schema.TypeString, + Computed: true, + }, + + "search_endpoint": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceAwsCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudsearchconn + + input := cloudsearch.CreateDomainInput{ + DomainName: aws.String(d.Get("name").(string)), + } + + output, err := conn.CreateDomain(&input) + if err != nil { + log.Printf("[DEBUG] Creating CloudSearch Domain: %#v", input) + return fmt.Errorf("%s %q", err, d.Get("name").(string)) + } + + d.SetId(*output.DomainStatus.ARN) + return resourceAwsCloudSearchDomainUpdate(d, meta) +} + +func resourceAwsCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudsearchconn + + domainlist := cloudsearch.DescribeDomainsInput{ + DomainNames: []*string{ + aws.String(d.Get("name").(string)), + }, + } + + resp, err1 := conn.DescribeDomains(&domainlist) + if err1 != nil { + return err1 + } + domain := resp.DomainStatusList[0] + d.Set("id", domain.DomainId) + + if domain.DocService.Endpoint != nil { + d.Set("document_endpoint", domain.DocService.Endpoint) + } + if domain.SearchService.Endpoint != nil { + d.Set("search_endpoint", domain.SearchService.Endpoint) + } + + input := cloudsearch.DescribeIndexFieldsInput{ + DomainName: aws.String(d.Get("name").(string)), + } + + _, err := conn.DescribeIndexFields(&input) + // if err != nil { + // log.Printf("[DEBUG] Reading CloudWatch Index fields: %#v", input) + // return fmt.Errorf("%s %q", err, d.Get("domain_name").(string)) + // } + + return err +} + +func resourceAwsCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudsearchconn + + err := updateAccessPolicy(d, meta, conn) + if err != nil { + return err + } + + err = updateScalingParameters(d, meta, conn) + if err != nil { + return err + } + + updated, err := defineIndexFields(d, meta, conn) + if err != nil { + return err + } + + // When you add fields or modify existing fields, you must explicitly issue a request to re-index your data + // when you are done making configuration changes. + // https://docs.aws.amazon.com/cloudsearch/latest/developerguide/configuring-index-fields.html + if updated { + _, err := conn.IndexDocuments(&cloudsearch.IndexDocumentsInput{ + DomainName: aws.String(d.Get("name").(string)), + }) + if err != nil { + return err + } + } + + if d.Get("wait_for_endpoints").(bool) { + domainlist := cloudsearch.DescribeDomainsInput{ + DomainNames: []*string{ + aws.String(d.Get("name").(string)), + }, + } + err2 := waitForSearchDomainToBeAvailable(d, conn, domainlist) + if err2 != nil { + return fmt.Errorf("%s %q", err2, d.Get("name").(string)) + } + } + return nil +} + +func resourceAwsCloudSearchDomainDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudsearchconn + + dm := d.Get("name").(string) + input := cloudsearch.DeleteDomainInput{ + DomainName: aws.String(dm), + } + + _, err := conn.DeleteDomain(&input) + + return err +} + +func updateAccessPolicy(d *schema.ResourceData, meta interface{}, conn *cloudsearch.CloudSearch) error { + input := cloudsearch.UpdateServiceAccessPoliciesInput{ + DomainName: aws.String(d.Get("name").(string)), + AccessPolicies: aws.String(d.Get("access_policy").(string)), + } + + _, err := conn.UpdateServiceAccessPolicies(&input) + return err +} + +func defineIndexFields(d *schema.ResourceData, meta interface{}, conn *cloudsearch.CloudSearch) (bool, error) { + if d.HasChange("indexes") { + old := make(map[string]interface{}) + new := make(map[string]interface{}) + + o, n := d.GetChange("indexes") + + for _, ot := range o.([]interface{}) { + os := ot.(map[string]interface{}) + old[os["name"].(string)] = os + } + + for _, nt := range n.([]interface{}) { + ns := nt.(map[string]interface{}) + new[ns["name"].(string)] = ns + } + + // Handle Removal + for k := range old { + if _, ok := new[k]; !ok { + deleteIndexField(d.Get("name").(string), k, conn) + } + } + + for _, v := range new { + // Handle replaces & additions + err := defineIndexField(d.Get("name").(string), v.(map[string]interface{}), conn) + if err != nil { + return true, err + } + } + return true, nil + } + return false, nil +} + +func defineIndexField(domainName string, index map[string]interface{}, conn *cloudsearch.CloudSearch) error { + i, err := genIndexFieldInput(index) + if err != nil { + return err + } + + input := cloudsearch.DefineIndexFieldInput{ + DomainName: aws.String(domainName), + IndexField: i, + } + + _, err = conn.DefineIndexField(&input) + return err +} + +func deleteIndexField(domainName string, indexName string, conn *cloudsearch.CloudSearch) error { + input := cloudsearch.DeleteIndexFieldInput{ + DomainName: aws.String(domainName), + IndexFieldName: aws.String(indexName), + } + + _, err := conn.DeleteIndexField(&input) + return err +} + +var parseError = func(d string, t string) error { + return fmt.Errorf("can't convert default_value '%s' of type '%s' to int", d, t) +} + +/* +extractFromMapToType extracts a specific value from map[string]interface{} into an interface of type +expects: map[string]interface{}, string, interface{} +returns: error +*/ +func extractFromMapToType(index map[string]interface{}, prop string, t interface{}) error { + v, ok := index[prop] + if !ok { + return fmt.Errorf("%s is not a valid property of an index", prop) + } + + if "default_value" == prop { + switch t.(type) { + case *int: + { + d, err := strconv.Atoi(v.(string)) + if err != nil { + return parseError(v.(string), "int") + } + + reflect.ValueOf(t).Elem().Set(reflect.ValueOf(d)) + } + case *float64: + { + f, err := strconv.ParseFloat(v.(string), 64) + if err != nil { + return parseError(v.(string), "double") + } + + reflect.ValueOf(t).Elem().Set(reflect.ValueOf(f)) + } + default: + { + if v.(string) != "" { + reflect.ValueOf(t).Elem().Set(reflect.ValueOf(v)) + } + } + } + return nil + } + + reflect.ValueOf(t).Elem().Set(reflect.ValueOf(v)) + return nil +} + +func genIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexField, error) { + input := &cloudsearch.IndexField{ + IndexFieldName: aws.String(index["name"].(string)), + IndexFieldType: aws.String(index["type"].(string)), + } + + var facet bool + var returnV bool + var search bool + var sort bool + var highlight bool + var analysisScheme string + + extractFromMapToType(index, "facet", &facet) + extractFromMapToType(index, "return", &returnV) + extractFromMapToType(index, "search", &search) + extractFromMapToType(index, "sort", &sort) + extractFromMapToType(index, "highlight", &highlight) + extractFromMapToType(index, "analysis_scheme", &analysisScheme) + + switch index["type"] { + case "int": + { + input.IntOptions = &cloudsearch.IntOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), + } + + if index["default_value"].(string) != "" { + var defaultValue int + extractFromMapToType(index, "default_value", &defaultValue) + input.IntOptions.DefaultValue = aws.Int64(int64(defaultValue)) + } + } + case "int-array": + { + input.IntArrayOptions = &cloudsearch.IntArrayOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + } + + if index["default_value"].(string) != "" { + var defaultValue int + extractFromMapToType(index, "default_value", &defaultValue) + input.IntArrayOptions.DefaultValue = aws.Int64(int64(defaultValue)) + } + } + case "double": + { + input.DoubleOptions = &cloudsearch.DoubleOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), + } + + if index["default_value"].(string) != "" { + var defaultValue float64 + extractFromMapToType(index, "default_value", &defaultValue) + input.DoubleOptions.DefaultValue = aws.Float64(float64(defaultValue)) + } + } + case "double-array": + { + input.DoubleArrayOptions = &cloudsearch.DoubleArrayOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + } + + if index["default_value"].(string) != "" { + var defaultValue float64 + extractFromMapToType(index, "default_value", &defaultValue) + input.DoubleArrayOptions.DefaultValue = aws.Float64(float64(defaultValue)) + } + } + case "literal": + { + input.LiteralOptions = &cloudsearch.LiteralOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), + } + + if index["default_value"].(string) != "" { + input.LiteralOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + } + case "literal-array": + { + input.LiteralArrayOptions = &cloudsearch.LiteralArrayOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + } + + if index["default_value"].(string) != "" { + input.LiteralArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + } + case "text": + { + input.TextOptions = &cloudsearch.TextOptions{ + SortEnabled: aws.Bool(sort), + ReturnEnabled: aws.Bool(returnV), + HighlightEnabled: aws.Bool(highlight), + AnalysisScheme: aws.String(analysisScheme), + } + + if index["default_value"].(string) != "" { + input.TextOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + } + case "text-array": + { + input.TextArrayOptions = &cloudsearch.TextArrayOptions{ + ReturnEnabled: aws.Bool(returnV), + HighlightEnabled: aws.Bool(highlight), + AnalysisScheme: aws.String(analysisScheme), + } + + if index["default_value"].(string) != "" { + input.TextArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + } + case "date": + { + input.DateOptions = &cloudsearch.DateOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), + } + + if index["default_value"].(string) != "" { + input.DateOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + } + case "date-array": + { + input.DateArrayOptions = &cloudsearch.DateArrayOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + } + + if index["default_value"].(string) != "" { + input.DateArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + } + case "latlon": + { + input.LatLonOptions = &cloudsearch.LatLonOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), + } + + if index["default_value"].(string) != "" { + input.LatLonOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + } + default: + return input, fmt.Errorf("invalid index field type %s", index["type"]) + } + + return input, nil +} + +func updateScalingParameters(d *schema.ResourceData, meta interface{}, conn *cloudsearch.CloudSearch) error { + input := cloudsearch.UpdateScalingParametersInput{ + DomainName: aws.String(d.Get("name").(string)), + ScalingParameters: &cloudsearch.ScalingParameters{ + DesiredInstanceType: aws.String(d.Get("instance_type").(string)), + DesiredReplicationCount: aws.Int64(int64(d.Get("replication_count").(int))), + }, + } + + // TODO: check instance type + if d.Get("instance_type").(string) == "search.2xlarge" { + input.ScalingParameters.DesiredPartitionCount = aws.Int64(int64(d.Get("partition_count").(int))) + } + + _, err := conn.UpdateScalingParameters(&input) + // if err != nil { + // log.Printf("[DEBUG] Updating Scaling Parameters: %#v", input) + // return fmt.Errorf("%s %q", err, d.Get("domain_name").(string)) + // } + return err +} + +func validateDomainName(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + if !regexp.MustCompile(`^[a-z]([a-z0-9-]){2,27}$`).MatchString(value) { + es = append(es, fmt.Errorf( + "%q must begin with a lower-case letter, contain only [a-z0-9-] and be at least 3 and at most 28 characters", k)) + } + return +} + +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 validateIndexType(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + found := false + for _, t := range cloudsearch.IndexFieldType_Values() { + if t == value { + found = true + continue + } + } + + if !found { + es = append(es, fmt.Errorf("%q is not a valid index type", v)) + } + + return +} + +func waitForSearchDomainToBeAvailable(d *schema.ResourceData, conn *cloudsearch.CloudSearch, domainlist cloudsearch.DescribeDomainsInput) error { + log.Printf("[INFO] cloudsearch (%#v) waiting for domain endpoint. This usually takes 10 minutes.", domainlist.DomainNames[0]) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Waiting"}, + Target: []string{"OK"}, + Timeout: 30 * time.Minute, + MinTimeout: 5 * time.Second, + Refresh: func() (interface{}, string, error) { + resp, err := conn.DescribeDomains(&domainlist) + log.Printf("[DEBUG] Checking %v", domainlist.DomainNames[0]) + if err != nil { + log.Printf("[ERROR] Could not find domain (%v). %s", domainlist.DomainNames[0], err) + return nil, "", err + } + // Not good enough to wait for processing, have to check for search endpoint. + domain := resp.DomainStatusList[0] + log.Printf("[DEBUG] GLEN: Domain = %s", domain) + processing := strconv.FormatBool(*domain.Processing) + log.Printf("[DEBUG] GLEN: Processing = %s", processing) + if domain.SearchService.Endpoint != nil { + log.Printf("[DEBUG] GLEN: type: %T", domain.SearchService.Endpoint) + log.Printf("[DEBUG] GLEN: SearchServiceEndpoint = %s", *domain.SearchService.Endpoint) + } + if domain.SearchService.Endpoint == nil || *domain.SearchService.Endpoint == "" { + return resp, "Waiting", nil + } + return resp, "OK", nil + + }, + } + + log.Printf("[DEBUG] Waiting for CloudSearch domain to finish processing: %v", domainlist.DomainNames[0]) + _, err := stateConf.WaitForState() + + // Search service was blank. + resp, err1 := conn.DescribeDomains(&domainlist) + if err1 != nil { + return err1 + } + + domain := resp.DomainStatusList[0] + d.Set("id", domain.DomainId) + d.Set("document_endpoint", domain.DocService.Endpoint) + d.Set("search_endpoint", domain.SearchService.Endpoint) + + if err != nil { + return fmt.Errorf("Error waiting for CloudSearch domain (%#v) to finish processing: %s", domainlist.DomainNames[0], err) + } + return err +} diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go new file mode 100644 index 000000000000..7ab7a6db0615 --- /dev/null +++ b/resource_aws_cloudsearch_domain_test.go @@ -0,0 +1,270 @@ +package aws + +import ( + "fmt" + "regexp" + "strconv" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudsearch" + "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" +) + +func TestAccAWSCloudSearchDomain_basic(t *testing.T) { + var domains cloudsearch.DescribeDomainsOutput + resourceName := "aws_cloudsearch_domain.test" + rString := acctest.RandString(8) + domainName := fmt.Sprintf("tf-acc-%s", rString) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudSearchDomainConfig_basic(domainName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), + resource.TestCheckResourceAttr(resourceName, "name", domainName), + ), + }, + // { + // ResourceName: resourceName, + // ImportState: false, + // ImportStateVerify: false, + // }, + }, + }) +} + +func TestAccAWSCloudSearchDomain_badName(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudSearchDomainConfig_basic("-this-is-a-bad-name"), + ExpectError: regexp.MustCompile(`.*"name" must begin with a.*`), + }, + }, + }) +} + +func TestAccAWSCloudSearchDomain_badInstanceType(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudSearchDomainConfig_withInstanceType("bad-instance-type", "nope.small"), + ExpectError: regexp.MustCompile(`.*failed to satisfy constraint.*`), + }, + }, + }) +} + +func TestAccAWSCloudSearchDomain_badIndexFieldNames(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-name", "HELLO", "text"), + ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), + }, + { + Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-name", "w-a", "text"), + ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), + }, + { + Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-name", "jfjdbfjdhsjakhfdhsajkfhdjksahfdsbfkjchndsjkhafbjdkshafjkdshjfhdsjkahfjkdsha", "text"), + ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), + }, + { + Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-name", "w", "text"), + ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), + }, + }, + }) +} + +func TestAccAWSCloudSearchDomain_badIndexFieldType(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + // CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-type", "name", "not-a-type"), + ExpectError: regexp.MustCompile(`.*is not a valid index type.*`), + }, + }, + }) +} + +func testAccCheckAWSCloudSearchDomainExists(n string, domains *cloudsearch.DescribeDomainsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := testAccProvider.Meta().(*AWSClient).cloudsearchconn + + domainList := cloudsearch.DescribeDomainsInput{ + DomainNames: []*string{ + aws.String(rs.Primary.Attributes["name"]), + }, + } + + resp, err := conn.DescribeDomains(&domainList) + if err != nil { + return err + } + + *domains = *resp + + return nil + } +} + +func testAccCheckAWSCloudSearchDomainDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudsearch_domain" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).cloudsearchconn + // Wait for the resource to start being deleted, which is marked as "Deleted" from the API. + stateConf := &resource.StateChangeConf{ + Pending: []string{"false"}, + Target: []string{"true"}, + Timeout: 20 * time.Minute, + NotFoundChecks: 100, + Refresh: func() (interface{}, string, error) { + domainlist := cloudsearch.DescribeDomainsInput{ + DomainNames: []*string{ + aws.String(rs.Primary.Attributes["name"]), + }, + } + + resp, err := conn.DescribeDomains(&domainlist) + if err != nil { + return nil, "false", err + } + + domain := resp.DomainStatusList[0] + + // If we see that the domain has been deleted, go ahead and return true. + if *domain.Deleted { + return domain, "true", nil + } + + if domain.Deleted != nil { + return nil, strconv.FormatBool(*domain.Deleted), nil + } + + return nil, "false", nil + }, + } + _, err := stateConf.WaitForState() + return err + } + + return nil +} + +func testAccAWSCloudSearchDomainConfig_basic(name string) string { + return fmt.Sprintf(` +resource "aws_cloudsearch_domain" "test" { + name = "%s" + + index { + name = "headline" + type = "text" + search = true + return = true + sort = true + highlight = false + analysis_scheme = "_en_default_" + } + + wait_for_endpoints = false + + access_policy = < Date: Fri, 19 Feb 2021 17:14:54 +0000 Subject: [PATCH 02/50] adding test sweeper --- resource_aws_cloudsearch_domain_test.go | 56 ++++++++++++++++++++----- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index 7ab7a6db0615..67dfd42313c7 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strconv" + "strings" "testing" "time" @@ -14,11 +15,41 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) +func init() { + resource.AddTestSweepers("aws_cloudsearch_domain", &resource.Sweeper{ + Name: "aws_cloudsearch_domain", + F: func(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).cloudsearchconn + + domains, err := conn.DescribeDomains(&cloudsearch.DescribeDomainsInput{}) + if err != nil { + return fmt.Errorf("error describing CloudSearch domains: %s", err) + } + + for _, domain := range domains.DomainStatusList { + if !strings.HasPrefix(*domain.DomainName, "tf-acc-") { + continue + } + _, err := conn.DeleteDomain(&cloudsearch.DeleteDomainInput{ + DomainName: domain.DomainName, + }) + if err != nil { + return fmt.Errorf("error deleting CloudSearch domain: %s", err) + } + } + return nil + }, + }) +} + func TestAccAWSCloudSearchDomain_basic(t *testing.T) { var domains cloudsearch.DescribeDomainsOutput resourceName := "aws_cloudsearch_domain.test" - rString := acctest.RandString(8) - domainName := fmt.Sprintf("tf-acc-%s", rString) + domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -55,37 +86,41 @@ func TestAccAWSCloudSearchDomain_badName(t *testing.T) { } func TestAccAWSCloudSearchDomain_badInstanceType(t *testing.T) { + domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAWSCloudSearchDomainConfig_withInstanceType("bad-instance-type", "nope.small"), - ExpectError: regexp.MustCompile(`.*failed to satisfy constraint.*`), + Config: testAccAWSCloudSearchDomainConfig_withInstanceType(domainName, "nope.small"), + ExpectError: regexp.MustCompile(`.*is not a valid instance type.*`), }, }, }) } func TestAccAWSCloudSearchDomain_badIndexFieldNames(t *testing.T) { + domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-name", "HELLO", "text"), + Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "HELLO", "text"), ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), }, { - Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-name", "w-a", "text"), + Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "w-a", "text"), ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), }, { - Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-name", "jfjdbfjdhsjakhfdhsajkfhdjksahfdsbfkjchndsjkhafbjdkshafjkdshjfhdsjkahfjkdsha", "text"), + Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "jfjdbfjdhsjakhfdhsajkfhdjksahfdsbfkjchndsjkhafbjdkshafjkdshjfhdsjkahfjkdsha", "text"), ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), }, { - Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-name", "w", "text"), + Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "w", "text"), ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), }, }, @@ -93,13 +128,14 @@ func TestAccAWSCloudSearchDomain_badIndexFieldNames(t *testing.T) { } func TestAccAWSCloudSearchDomain_badIndexFieldType(t *testing.T) { + domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - // CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudSearchDomainConfig_withIndex("bad-index-type", "name", "not-a-type"), + Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "name", "not-a-type"), ExpectError: regexp.MustCompile(`.*is not a valid index type.*`), }, }, From ba50fc3f0b99547ffbcdfad8f6c63d64ceeeadd7 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Fri, 19 Feb 2021 22:11:10 +0000 Subject: [PATCH 03/50] implementing full CloudSearch domain resource support --- resource_aws_cloudsearch_domain.go | 804 ++++++++++++++---------- resource_aws_cloudsearch_domain_test.go | 201 +++++- 2 files changed, 659 insertions(+), 346 deletions(-) diff --git a/resource_aws_cloudsearch_domain.go b/resource_aws_cloudsearch_domain.go index a68d741e716e..3aefd150788b 100644 --- a/resource_aws_cloudsearch_domain.go +++ b/resource_aws_cloudsearch_domain.go @@ -6,9 +6,11 @@ import ( "reflect" "regexp" "strconv" + "strings" "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/cloudsearch" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -21,7 +23,7 @@ func resourceAwsCloudSearchDomain() *schema.Resource { Update: resourceAwsCloudSearchDomainUpdate, Delete: resourceAwsCloudSearchDomainDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + State: resourceAwsCloudSearchDomainImport, }, Schema: map[string]*schema.Schema{ @@ -34,9 +36,10 @@ func resourceAwsCloudSearchDomain() *schema.Resource { }, "instance_type": { - Type: schema.TypeString, - Optional: true, - Default: "search.small", + Type: schema.TypeString, + Optional: true, + Default: "search.small", + ValidateFunc: validateInstanceType, }, "replication_count": { @@ -57,7 +60,7 @@ func resourceAwsCloudSearchDomain() *schema.Resource { Default: false, }, - "access_policy": { + "service_access_policies": { Type: schema.TypeString, ValidateFunc: validateIAMPolicyJson, Required: true, @@ -83,27 +86,32 @@ func resourceAwsCloudSearchDomain() *schema.Resource { "search": { Type: schema.TypeBool, - Required: true, + Optional: true, + Default: false, }, "facet": { Type: schema.TypeBool, Optional: true, + Default: false, }, "return": { Type: schema.TypeBool, - Required: true, + Optional: true, + Default: false, }, "sort": { Type: schema.TypeBool, Optional: true, + Default: false, }, "highlight": { Type: schema.TypeBool, Optional: true, + Default: false, }, "analysis_scheme": { @@ -120,9 +128,10 @@ func resourceAwsCloudSearchDomain() *schema.Resource { }, "wait_for_endpoints": { - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + Default: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { return true }, }, "id": { @@ -145,6 +154,7 @@ func resourceAwsCloudSearchDomain() *schema.Resource { } } +// Terraform CRUD Functions func resourceAwsCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).cloudsearchconn @@ -171,9 +181,9 @@ func resourceAwsCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) }, } - resp, err1 := conn.DescribeDomains(&domainlist) - if err1 != nil { - return err1 + resp, err := conn.DescribeDomains(&domainlist) + if err != nil { + return err } domain := resp.DomainStatusList[0] d.Set("id", domain.DomainId) @@ -185,31 +195,79 @@ func resourceAwsCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) d.Set("search_endpoint", domain.SearchService.Endpoint) } - input := cloudsearch.DescribeIndexFieldsInput{ + // Read index fields. + indexResults, err := conn.DescribeIndexFields(&cloudsearch.DescribeIndexFieldsInput{ DomainName: aws.String(d.Get("name").(string)), + }) + if err != nil { + return err } - _, err := conn.DescribeIndexFields(&input) - // if err != nil { - // log.Printf("[DEBUG] Reading CloudWatch Index fields: %#v", input) - // return fmt.Errorf("%s %q", err, d.Get("domain_name").(string)) - // } + result := make([]map[string]interface{}, 0, len(indexResults.IndexFields)) - return err -} + for _, raw := range indexResults.IndexFields { + // Don't read in any fields that are pending deletion. + if *raw.Status.PendingDeletion { + continue + } -func resourceAwsCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).cloudsearchconn + result = append(result, readIndexField(raw.Options)) + } + d.Set("index", result) - err := updateAccessPolicy(d, meta, conn) + // Read service access policies. + policyResult, err := conn.DescribeServiceAccessPolicies(&cloudsearch.DescribeServiceAccessPoliciesInput{ + DomainName: aws.String(d.Get("name").(string)), + }) if err != nil { return err } + d.Set("service_access_policies", policyResult.AccessPolicies.Options) - err = updateScalingParameters(d, meta, conn) + // Read availability options (i.e. multi-az). + availabilityResult, err := conn.DescribeAvailabilityOptions(&cloudsearch.DescribeAvailabilityOptionsInput{ + DomainName: aws.String(d.Get("name").(string)), + }) if err != nil { return err } + d.Set("multi_az", availabilityResult.AvailabilityOptions.Options) + + // Read scaling parameters. + scalingResult, err := conn.DescribeScalingParameters(&cloudsearch.DescribeScalingParametersInput{ + DomainName: aws.String(d.Get("name").(string)), + }) + if err != nil { + return err + } + d.Set("instance_type", scalingResult.ScalingParameters.Options.DesiredInstanceType) + d.Set("partition_count", scalingResult.ScalingParameters.Options.DesiredPartitionCount) + d.Set("replication_count", scalingResult.ScalingParameters.Options.DesiredReplicationCount) + + return err +} + +func resourceAwsCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudsearchconn + + _, err := conn.UpdateServiceAccessPolicies(&cloudsearch.UpdateServiceAccessPoliciesInput{ + DomainName: aws.String(d.Get("name").(string)), + AccessPolicies: aws.String(d.Get("service_access_policies").(string)), + }) + + _, err = conn.UpdateScalingParameters(&cloudsearch.UpdateScalingParametersInput{ + DomainName: aws.String(d.Get("name").(string)), + ScalingParameters: &cloudsearch.ScalingParameters{ + DesiredInstanceType: aws.String(d.Get("instance_type").(string)), + DesiredReplicationCount: aws.Int64(int64(d.Get("replication_count").(int))), + DesiredPartitionCount: aws.Int64(int64(d.Get("partition_count").(int))), + }, + }) + + _, err = conn.UpdateAvailabilityOptions(&cloudsearch.UpdateAvailabilityOptionsInput{ + DomainName: aws.String(d.Get("name").(string)), + MultiAZ: aws.Bool(d.Get("multi_az").(bool)), + }) updated, err := defineIndexFields(d, meta, conn) if err != nil { @@ -229,14 +287,15 @@ func resourceAwsCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{} } if d.Get("wait_for_endpoints").(bool) { - domainlist := cloudsearch.DescribeDomainsInput{ + domainsList := cloudsearch.DescribeDomainsInput{ DomainNames: []*string{ aws.String(d.Get("name").(string)), }, } - err2 := waitForSearchDomainToBeAvailable(d, conn, domainlist) - if err2 != nil { - return fmt.Errorf("%s %q", err2, d.Get("name").(string)) + + err = waitForSearchDomainToBeAvailable(d, conn, domainsList) + if err != nil { + return fmt.Errorf("%s %q", err, d.Get("name").(string)) } } return nil @@ -255,79 +314,182 @@ func resourceAwsCloudSearchDomainDelete(d *schema.ResourceData, meta interface{} return err } -func updateAccessPolicy(d *schema.ResourceData, meta interface{}, conn *cloudsearch.CloudSearch) error { - input := cloudsearch.UpdateServiceAccessPoliciesInput{ - DomainName: aws.String(d.Get("name").(string)), - AccessPolicies: aws.String(d.Get("access_policy").(string)), +// Import Function +func resourceAwsCloudSearchDomainImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + d.SetId(d.Id()) + + arn, err := arn.Parse(d.Id()) + if err != nil { + return nil, err } - _, err := conn.UpdateServiceAccessPolicies(&input) - return err -} + parts := strings.Split(arn.Resource, "/") + if len(parts) != 2 { + return nil, fmt.Errorf("resource component of ARN is not properly formatted") + } -func defineIndexFields(d *schema.ResourceData, meta interface{}, conn *cloudsearch.CloudSearch) (bool, error) { - if d.HasChange("indexes") { - old := make(map[string]interface{}) - new := make(map[string]interface{}) + d.Set("name", parts[1]) - o, n := d.GetChange("indexes") + return []*schema.ResourceData{d}, nil +} - for _, ot := range o.([]interface{}) { - os := ot.(map[string]interface{}) - old[os["name"].(string)] = os - } +// Validation Functions +func validateDomainName(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + if !regexp.MustCompile(`^[a-z]([a-z0-9-]){2,27}$`).MatchString(value) { + es = append(es, fmt.Errorf( + "%q must begin with a lower-case letter, contain only [a-z0-9-] and be at least 3 and at most 28 characters", k)) + } + return +} - for _, nt := range n.([]interface{}) { - ns := nt.(map[string]interface{}) - new[ns["name"].(string)] = ns +func validateInstanceType(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + found := false + for _, t := range cloudsearch.PartitionInstanceType_Values() { + if t == value { + found = true + continue } + } - // Handle Removal - for k := range old { - if _, ok := new[k]; !ok { - deleteIndexField(d.Get("name").(string), k, conn) - } - } + if !found { + es = append(es, fmt.Errorf("%q is not a valid instance type", v)) + } - for _, v := range new { - // Handle replaces & additions - err := defineIndexField(d.Get("name").(string), v.(map[string]interface{}), conn) - if err != nil { - return true, err - } - } - return true, nil + return +} + +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)) } - return false, nil + + if value == "score" { + es = append(es, fmt.Errorf("'score' is a reserved field name and cannot be used")) + } + + return } -func defineIndexField(domainName string, index map[string]interface{}, conn *cloudsearch.CloudSearch) error { - i, err := genIndexFieldInput(index) - if err != nil { - return err +func validateIndexType(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + found := false + for _, t := range cloudsearch.IndexFieldType_Values() { + if t == value { + found = true + continue + } } - input := cloudsearch.DefineIndexFieldInput{ - DomainName: aws.String(domainName), - IndexField: i, + if !found { + es = append(es, fmt.Errorf("%q is not a valid index type", v)) } - _, err = conn.DefineIndexField(&input) - return err + return } -func deleteIndexField(domainName string, indexName string, conn *cloudsearch.CloudSearch) error { - input := cloudsearch.DeleteIndexFieldInput{ - DomainName: aws.String(domainName), - IndexFieldName: aws.String(indexName), +// Waiters +func waitForSearchDomainToBeAvailable(d *schema.ResourceData, conn *cloudsearch.CloudSearch, domainlist cloudsearch.DescribeDomainsInput) error { + log.Printf("[INFO] cloudsearch (%#v) waiting for domain endpoint. This usually takes 10 minutes.", domainlist.DomainNames[0]) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Waiting"}, + Target: []string{"OK"}, + Timeout: 30 * time.Minute, + MinTimeout: 5 * time.Second, + Refresh: func() (interface{}, string, error) { + resp, err := conn.DescribeDomains(&domainlist) + log.Printf("[DEBUG] Checking %v", domainlist.DomainNames[0]) + if err != nil { + log.Printf("[ERROR] Could not find domain (%v). %s", domainlist.DomainNames[0], err) + return nil, "", err + } + // Not good enough to wait for processing, have to check for search endpoint. + domain := resp.DomainStatusList[0] + log.Printf("[DEBUG] GLEN: Domain = %s", domain) + processing := strconv.FormatBool(*domain.Processing) + log.Printf("[DEBUG] GLEN: Processing = %s", processing) + if domain.SearchService.Endpoint != nil { + log.Printf("[DEBUG] GLEN: type: %T", domain.SearchService.Endpoint) + log.Printf("[DEBUG] GLEN: SearchServiceEndpoint = %s", *domain.SearchService.Endpoint) + } + if domain.SearchService.Endpoint == nil || *domain.SearchService.Endpoint == "" { + return resp, "Waiting", nil + } + return resp, "OK", nil + + }, + } + + log.Printf("[DEBUG] Waiting for CloudSearch domain to finish processing: %v", domainlist.DomainNames[0]) + _, err := stateConf.WaitForState() + + // Search service was blank. + resp, err1 := conn.DescribeDomains(&domainlist) + if err1 != nil { + return err1 } - _, err := conn.DeleteIndexField(&input) + domain := resp.DomainStatusList[0] + d.Set("id", domain.DomainId) + d.Set("document_endpoint", domain.DocService.Endpoint) + d.Set("search_endpoint", domain.SearchService.Endpoint) + + if err != nil { + return fmt.Errorf("Error waiting for CloudSearch domain (%#v) to finish processing: %s", domainlist.DomainNames[0], err) + } return err } -var parseError = func(d string, t string) error { - return fmt.Errorf("can't convert default_value '%s' of type '%s' to int", d, t) +// Miscellaneous helper functions +func defineIndexFields(d *schema.ResourceData, meta interface{}, conn *cloudsearch.CloudSearch) (bool, error) { + // Early return if we don't have a change. + if !d.HasChange("index") { + return false, nil + } + + o, n := d.GetChange("index") + + old := o.(*schema.Set) + new := n.(*schema.Set) + + // Returns a set of only old fields, to be deleted. + toDelete := old.Difference(new) + for _, index := range toDelete.List() { + v, _ := index.(map[string]interface{}) + + _, err := conn.DeleteIndexField(&cloudsearch.DeleteIndexFieldInput{ + DomainName: aws.String(d.Get("name").(string)), + IndexFieldName: aws.String(v["name"].(string)), + }) + if err != nil { + return true, err + } + } + + // Returns a set of only fields that needs to be added or updated (upserted). + toUpsert := new.Difference(old) + for _, index := range toUpsert.List() { + v, _ := index.(map[string]interface{}) + + field, err := generateIndexFieldInput(v) + if err != nil { + return true, err + } + + _, err = conn.DefineIndexField(&cloudsearch.DefineIndexFieldInput{ + DomainName: aws.String(d.Get("name").(string)), + IndexField: field, + }) + if err != nil { + return true, err + } + } + + return true, nil } /* @@ -335,37 +497,31 @@ extractFromMapToType extracts a specific value from map[string]interface{} into expects: map[string]interface{}, string, interface{} returns: error */ -func extractFromMapToType(index map[string]interface{}, prop string, t interface{}) error { - v, ok := index[prop] +func extractFromMapToType(index map[string]interface{}, property string, t interface{}) error { + v, ok := index[property] if !ok { - return fmt.Errorf("%s is not a valid property of an index", prop) + return fmt.Errorf("%s is not a valid property of an index", property) } - if "default_value" == prop { + if "default_value" == property { switch t.(type) { case *int: - { - d, err := strconv.Atoi(v.(string)) - if err != nil { - return parseError(v.(string), "int") - } - - reflect.ValueOf(t).Elem().Set(reflect.ValueOf(d)) + d, err := strconv.Atoi(v.(string)) + if err != nil { + return parseError(v.(string), "int") } - case *float64: - { - f, err := strconv.ParseFloat(v.(string), 64) - if err != nil { - return parseError(v.(string), "double") - } - reflect.ValueOf(t).Elem().Set(reflect.ValueOf(f)) + reflect.ValueOf(t).Elem().Set(reflect.ValueOf(d)) + case *float64: + f, err := strconv.ParseFloat(v.(string), 64) + if err != nil { + return parseError(v.(string), "double") } + + reflect.ValueOf(t).Elem().Set(reflect.ValueOf(f)) default: - { - if v.(string) != "" { - reflect.ValueOf(t).Elem().Set(reflect.ValueOf(v)) - } + if v.(string) != "" { + reflect.ValueOf(t).Elem().Set(reflect.ValueOf(v)) } } return nil @@ -375,7 +531,11 @@ func extractFromMapToType(index map[string]interface{}, prop string, t interface return nil } -func genIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexField, error) { +var parseError = func(d string, t string) error { + return fmt.Errorf("can't convert default_value '%s' of type '%s' to int", d, t) +} + +func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexField, error) { input := &cloudsearch.IndexField{ IndexFieldName: aws.String(index["name"].(string)), IndexFieldType: aws.String(index["type"].(string)), @@ -395,152 +555,135 @@ func genIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexField, extractFromMapToType(index, "highlight", &highlight) extractFromMapToType(index, "analysis_scheme", &analysisScheme) + // NOTE: only way I know of to set a default for this field since not all index fields can use it. + if analysisScheme == "" { + analysisScheme = "_en_default_" + } + switch index["type"] { - case "int": - { - input.IntOptions = &cloudsearch.IntOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } + case "date": + input.DateOptions = &cloudsearch.DateOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), + } - if index["default_value"].(string) != "" { - var defaultValue int - extractFromMapToType(index, "default_value", &defaultValue) - input.IntOptions.DefaultValue = aws.Int64(int64(defaultValue)) - } + if index["default_value"].(string) != "" { + input.DateOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + case "date-array": + input.DateArrayOptions = &cloudsearch.DateArrayOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), } - case "int-array": - { - input.IntArrayOptions = &cloudsearch.IntArrayOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - } - if index["default_value"].(string) != "" { - var defaultValue int - extractFromMapToType(index, "default_value", &defaultValue) - input.IntArrayOptions.DefaultValue = aws.Int64(int64(defaultValue)) - } + if index["default_value"].(string) != "" { + input.DateArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) } case "double": - { - input.DoubleOptions = &cloudsearch.DoubleOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } + input.DoubleOptions = &cloudsearch.DoubleOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), + } - if index["default_value"].(string) != "" { - var defaultValue float64 - extractFromMapToType(index, "default_value", &defaultValue) - input.DoubleOptions.DefaultValue = aws.Float64(float64(defaultValue)) - } + if index["default_value"].(string) != "" { + var defaultValue float64 + extractFromMapToType(index, "default_value", &defaultValue) + input.DoubleOptions.DefaultValue = aws.Float64(float64(defaultValue)) } case "double-array": - { - input.DoubleArrayOptions = &cloudsearch.DoubleArrayOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - } + input.DoubleArrayOptions = &cloudsearch.DoubleArrayOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + } - if index["default_value"].(string) != "" { - var defaultValue float64 - extractFromMapToType(index, "default_value", &defaultValue) - input.DoubleArrayOptions.DefaultValue = aws.Float64(float64(defaultValue)) - } + if index["default_value"].(string) != "" { + var defaultValue float64 + extractFromMapToType(index, "default_value", &defaultValue) + input.DoubleArrayOptions.DefaultValue = aws.Float64(float64(defaultValue)) + } + case "int": + input.IntOptions = &cloudsearch.IntOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), } - case "literal": - { - input.LiteralOptions = &cloudsearch.LiteralOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } - if index["default_value"].(string) != "" { - input.LiteralOptions.DefaultValue = aws.String(index["default_value"].(string)) - } + if index["default_value"].(string) != "" { + var defaultValue int + extractFromMapToType(index, "default_value", &defaultValue) + input.IntOptions.DefaultValue = aws.Int64(int64(defaultValue)) + } + case "int-array": + input.IntArrayOptions = &cloudsearch.IntArrayOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), } - case "literal-array": - { - input.LiteralArrayOptions = &cloudsearch.LiteralArrayOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - } - if index["default_value"].(string) != "" { - input.LiteralArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) - } + if index["default_value"].(string) != "" { + var defaultValue int + extractFromMapToType(index, "default_value", &defaultValue) + input.IntArrayOptions.DefaultValue = aws.Int64(int64(defaultValue)) + } + case "latlon": + input.LatLonOptions = &cloudsearch.LatLonOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), } - case "text": - { - input.TextOptions = &cloudsearch.TextOptions{ - SortEnabled: aws.Bool(sort), - ReturnEnabled: aws.Bool(returnV), - HighlightEnabled: aws.Bool(highlight), - AnalysisScheme: aws.String(analysisScheme), - } - if index["default_value"].(string) != "" { - input.TextOptions.DefaultValue = aws.String(index["default_value"].(string)) - } + if index["default_value"].(string) != "" { + input.LatLonOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + case "literal": + input.LiteralOptions = &cloudsearch.LiteralOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), + SortEnabled: aws.Bool(sort), } - case "text-array": - { - input.TextArrayOptions = &cloudsearch.TextArrayOptions{ - ReturnEnabled: aws.Bool(returnV), - HighlightEnabled: aws.Bool(highlight), - AnalysisScheme: aws.String(analysisScheme), - } - if index["default_value"].(string) != "" { - input.TextArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) - } + if index["default_value"].(string) != "" { + input.LiteralOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + case "literal-array": + input.LiteralArrayOptions = &cloudsearch.LiteralArrayOptions{ + FacetEnabled: aws.Bool(facet), + ReturnEnabled: aws.Bool(returnV), + SearchEnabled: aws.Bool(search), } - case "date": - { - input.DateOptions = &cloudsearch.DateOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } - if index["default_value"].(string) != "" { - input.DateOptions.DefaultValue = aws.String(index["default_value"].(string)) - } + if index["default_value"].(string) != "" { + input.LiteralArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + case "text": + input.TextOptions = &cloudsearch.TextOptions{ + SortEnabled: aws.Bool(sort), + ReturnEnabled: aws.Bool(returnV), + HighlightEnabled: aws.Bool(highlight), + AnalysisScheme: aws.String(analysisScheme), } - case "date-array": - { - input.DateArrayOptions = &cloudsearch.DateArrayOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - } - if index["default_value"].(string) != "" { - input.DateArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) - } + if index["default_value"].(string) != "" { + input.TextOptions.DefaultValue = aws.String(index["default_value"].(string)) + } + case "text-array": + input.TextArrayOptions = &cloudsearch.TextArrayOptions{ + ReturnEnabled: aws.Bool(returnV), + HighlightEnabled: aws.Bool(highlight), + AnalysisScheme: aws.String(analysisScheme), } - case "latlon": - { - input.LatLonOptions = &cloudsearch.LatLonOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } - if index["default_value"].(string) != "" { - input.LatLonOptions.DefaultValue = aws.String(index["default_value"].(string)) - } + if index["default_value"].(string) != "" { + input.TextArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) } default: return input, fmt.Errorf("invalid index field type %s", index["type"]) @@ -549,116 +692,115 @@ func genIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexField, return input, nil } -func updateScalingParameters(d *schema.ResourceData, meta interface{}, conn *cloudsearch.CloudSearch) error { - input := cloudsearch.UpdateScalingParametersInput{ - DomainName: aws.String(d.Get("name").(string)), - ScalingParameters: &cloudsearch.ScalingParameters{ - DesiredInstanceType: aws.String(d.Get("instance_type").(string)), - DesiredReplicationCount: aws.Int64(int64(d.Get("replication_count").(int))), - }, - } - - // TODO: check instance type - if d.Get("instance_type").(string) == "search.2xlarge" { - input.ScalingParameters.DesiredPartitionCount = aws.Int64(int64(d.Get("partition_count").(int))) +func readIndexField(raw *cloudsearch.IndexField) map[string]interface{} { + index := map[string]interface{}{ + "name": raw.IndexFieldName, + "type": raw.IndexFieldType, } - _, err := conn.UpdateScalingParameters(&input) - // if err != nil { - // log.Printf("[DEBUG] Updating Scaling Parameters: %#v", input) - // return fmt.Errorf("%s %q", err, d.Get("domain_name").(string)) - // } - return err -} - -func validateDomainName(v interface{}, k string) (ws []string, es []error) { - value := v.(string) - if !regexp.MustCompile(`^[a-z]([a-z0-9-]){2,27}$`).MatchString(value) { - es = append(es, fmt.Errorf( - "%q must begin with a lower-case letter, contain only [a-z0-9-] and be at least 3 and at most 28 characters", k)) - } - return -} - -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 validateIndexType(v interface{}, k string) (ws []string, es []error) { - value := v.(string) - found := false - for _, t := range cloudsearch.IndexFieldType_Values() { - if t == value { - found = true - continue - } - } - - if !found { - es = append(es, fmt.Errorf("%q is not a valid index type", v)) - } - - return -} - -func waitForSearchDomainToBeAvailable(d *schema.ResourceData, conn *cloudsearch.CloudSearch, domainlist cloudsearch.DescribeDomainsInput) error { - log.Printf("[INFO] cloudsearch (%#v) waiting for domain endpoint. This usually takes 10 minutes.", domainlist.DomainNames[0]) - stateConf := &resource.StateChangeConf{ - Pending: []string{"Waiting"}, - Target: []string{"OK"}, - Timeout: 30 * time.Minute, - MinTimeout: 5 * time.Second, - Refresh: func() (interface{}, string, error) { - resp, err := conn.DescribeDomains(&domainlist) - log.Printf("[DEBUG] Checking %v", domainlist.DomainNames[0]) - if err != nil { - log.Printf("[ERROR] Could not find domain (%v). %s", domainlist.DomainNames[0], err) - return nil, "", err - } - // Not good enough to wait for processing, have to check for search endpoint. - domain := resp.DomainStatusList[0] - log.Printf("[DEBUG] GLEN: Domain = %s", domain) - processing := strconv.FormatBool(*domain.Processing) - log.Printf("[DEBUG] GLEN: Processing = %s", processing) - if domain.SearchService.Endpoint != nil { - log.Printf("[DEBUG] GLEN: type: %T", domain.SearchService.Endpoint) - log.Printf("[DEBUG] GLEN: SearchServiceEndpoint = %s", *domain.SearchService.Endpoint) - } - if domain.SearchService.Endpoint == nil || *domain.SearchService.Endpoint == "" { - return resp, "Waiting", nil - } - return resp, "OK", nil - - }, - } - - log.Printf("[DEBUG] Waiting for CloudSearch domain to finish processing: %v", domainlist.DomainNames[0]) - _, err := stateConf.WaitForState() + switch *raw.IndexFieldType { + case "date": + index["default_value"] = raw.DateOptions.DefaultValue + index["facet"] = raw.DateOptions.FacetEnabled + index["return"] = raw.DateOptions.ReturnEnabled + index["search"] = raw.DateOptions.SearchEnabled + index["sort"] = raw.DateOptions.SortEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + case "date-array": + index["default_value"] = raw.DateArrayOptions.DefaultValue + index["facet"] = raw.DateArrayOptions.FacetEnabled + index["return"] = raw.DateArrayOptions.ReturnEnabled + index["search"] = raw.DateArrayOptions.SearchEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + index["sort"] = false + case "double": + index["default_value"] = raw.DoubleOptions.DefaultValue + index["facet"] = raw.DoubleOptions.FacetEnabled + index["return"] = raw.DoubleOptions.ReturnEnabled + index["search"] = raw.DoubleOptions.SearchEnabled + index["sort"] = raw.DoubleOptions.SortEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + case "double-array": + index["default_value"] = raw.DoubleArrayOptions.DefaultValue + index["facet"] = raw.DoubleArrayOptions.FacetEnabled + index["return"] = raw.DoubleArrayOptions.ReturnEnabled + index["search"] = raw.DoubleArrayOptions.SearchEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + index["sort"] = false + case "int": + index["default_value"] = raw.IntOptions.DefaultValue + index["facet"] = raw.IntOptions.FacetEnabled + index["return"] = raw.IntOptions.ReturnEnabled + index["search"] = raw.IntOptions.SearchEnabled + index["sort"] = raw.IntOptions.SortEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + case "int-array": + index["default_value"] = raw.IntArrayOptions.DefaultValue + index["facet"] = raw.IntArrayOptions.FacetEnabled + index["return"] = raw.IntArrayOptions.ReturnEnabled + index["search"] = raw.IntArrayOptions.SearchEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + index["sort"] = false + case "latlon": + index["default_value"] = raw.LatLonOptions.DefaultValue + index["facet"] = raw.LatLonOptions.FacetEnabled + index["return"] = raw.LatLonOptions.ReturnEnabled + index["search"] = raw.LatLonOptions.SearchEnabled + index["sort"] = raw.LatLonOptions.SortEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + case "literal": + index["default_value"] = raw.LiteralOptions.DefaultValue + index["facet"] = raw.LiteralOptions.FacetEnabled + index["return"] = raw.LiteralOptions.ReturnEnabled + index["search"] = raw.LiteralOptions.SearchEnabled + index["sort"] = raw.LiteralOptions.SortEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + case "literal-array": + index["default_value"] = raw.LiteralArrayOptions.DefaultValue + index["facet"] = raw.LiteralArrayOptions.FacetEnabled + index["return"] = raw.LiteralArrayOptions.ReturnEnabled + index["search"] = raw.LiteralArrayOptions.SearchEnabled + + // Options that aren't valid for this type. + index["highlight"] = false + index["sort"] = false + case "text": + index["default_value"] = raw.TextOptions.DefaultValue + index["analysis_scheme"] = raw.TextOptions.AnalysisScheme + index["highlight"] = raw.TextOptions.HighlightEnabled + index["return"] = raw.TextOptions.ReturnEnabled + index["sort"] = raw.TextOptions.SortEnabled + + // Options that aren't valid for this type. + index["facet"] = false + index["search"] = false + case "text-array": + index["default_value"] = raw.TextArrayOptions.DefaultValue + index["analysis_scheme"] = raw.TextArrayOptions.AnalysisScheme + index["highlight"] = raw.TextArrayOptions.HighlightEnabled + index["return"] = raw.TextArrayOptions.ReturnEnabled - // Search service was blank. - resp, err1 := conn.DescribeDomains(&domainlist) - if err1 != nil { - return err1 + // Options that aren't valid for this type. + index["facet"] = false + index["search"] = false + index["sort"] = false } - domain := resp.DomainStatusList[0] - d.Set("id", domain.DomainId) - d.Set("document_endpoint", domain.DocService.Endpoint) - d.Set("search_endpoint", domain.SearchService.Endpoint) - - if err != nil { - return fmt.Errorf("Error waiting for CloudSearch domain (%#v) to finish processing: %s", domainlist.DomainNames[0], err) - } - return err + return index } diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index 67dfd42313c7..f45fcdeefcb3 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -63,11 +63,23 @@ func TestAccAWSCloudSearchDomain_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", domainName), ), }, - // { - // ResourceName: resourceName, - // ImportState: false, - // ImportStateVerify: false, - // }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSCloudSearchDomainConfig_basicIndexMix(domainName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), + resource.TestCheckResourceAttr(resourceName, "name", domainName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -135,7 +147,7 @@ func TestAccAWSCloudSearchDomain_badIndexFieldType(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "name", "not-a-type"), + Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "directory", "not-a-type"), ExpectError: regexp.MustCompile(`.*is not a valid index type.*`), }, }, @@ -220,18 +232,176 @@ resource "aws_cloudsearch_domain" "test" { name = "%s" index { - name = "headline" + name = "date_test" + type = "date" + facet = true + search = true + return = true + sort = true + } + + index { + name = "date_array_test" + type = "date-array" + facet = true + search = true + return = true + } + + index { + name = "double_test" + type = "double" + facet = true + search = true + return = true + sort = true + } + + index { + name = "double_array_test" + type = "double-array" + facet = true + search = true + return = true + } + + index { + name = "int_test" + type = "int" + facet = true + search = true + return = true + sort = true + } + + index { + name = "int_array_test" + type = "int-array" + facet = true + search = true + return = true + } + + index { + name = "latlon_test" + type = "latlon" + facet = true + search = true + return = true + sort = true + } + + index { + name = "literal_test" + type = "literal" + facet = true + search = true + return = true + sort = true + } + + index { + name = "literal_array_test" + type = "literal-array" + facet = true + search = true + return = true + } + + index { + name = "text_test" type = "text" - search = true + analysis_scheme = "_en_default_" + highlight = true return = true sort = true - highlight = false + } + + index { + name = "text_array_test" + type = "text-array" analysis_scheme = "_en_default_" + highlight = true + return = true } wait_for_endpoints = false + service_access_policies = < Date: Fri, 19 Feb 2021 22:17:49 +0000 Subject: [PATCH 04/50] adding documentation --- website/allowed-subcategories.txt | 1 + .../docs/r/cloudsearch_domain.html.markdown | 101 ++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 website/docs/r/cloudsearch_domain.html.markdown diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index 6a45e185fdee..d88b0a31bf78 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -26,6 +26,7 @@ Cloud Control API CloudFormation CloudFront CloudHSM v2 +CloudSearch CloudTrail CloudWatch CodeArtifact diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown new file mode 100644 index 000000000000..98e06fdbb72a --- /dev/null +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -0,0 +1,101 @@ +--- +subcategory: "CloudSearch" +layout: "aws" +page_title: "AWS: aws_cloudsearch_domain" +description: |- + Provides an CloudSearch domain resource. +--- + +# Resource: aws_cloudsearch_domain + +Provides an CloudSearch domain resource. + +## Example Usage + +```hcl +resource "aws_cloudsearch_domain" "my_domain" { + domain_name = "test-domain" + instance_type = "search.medium" + + index { + name = "headline" + type = "text" + search = true + return = true + sort = true + highlight = false + analysis_scheme = "_en_default_" + } + + index { + name = "price" + type = "double" + search = true + facet = true + return = true + sort = true + } + service_access_policies = data.aws_iam_policy_document.cloudsearch_access_policy.json +} + +data "aws_iam_policy_document" "cloudsearch_access_policy" { + statement { + principals { + type = "AWS" + identifiers = ["*"] + } + actions = [ + "cloudsearch:search", + "cloudsearch:suggest" + ] + } +} +``` + + +## Using aws_cloudsearch_domain with API gateway. + +When you are using a cloudsearch domain with an aws_api_gateway_integration you need to set uri of the AWS cloudsearch service, to a specially formatted uri, that includes the first part of the search endpoint that is returned from this provider. The below will help. +``` +data "aws_region" "current" {} +resource "aws_api_gateway_integration" "sample" { + ... + type = "AWS" + uri = "arn:aws:apigateway:${data.aws_region.current.name}:${replace(aws_cloudsearch_domain.my_domain.search_endpoint, ".${data.aws_region.current.name}.cloudsearch.amazonaws.com", "")}.cloudsearch:path/2013-01-01/search" + ... +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the CloudSearch domain. +* `instance_type` - (Optional) The type of instance to start. +* `replication_count` - (Optional) The amount of replicas. +* `partition_count` - (Optional) The amount of partitions on each instance. Currently only supported by `search.2xlarge`. +* `index` - (Required) See [Indices](#indices) below for details. +* `service_access_policies` - (Required) The AWS IAM access policy. +* `wait_for_endpoints` - (Optional) - Default true, wait for the search service end point. If you set this to false, the search and document endpoints won't be available to use as an attribute during the first run. + +### Indices + +Each of the `index` entities represents an index field of the domain. + +* `name` - (Required) Represents the property field name. +* `type` - (Required) Represents the property type. It can be one of `int,int-array,double,double-array,literal,literal-array,text,text-array,date,date-array,lation` [AWS's Docs](http://docs.aws.amazon.com/cloudsearch/latest/developerguide/configuring-index-fields.html) +* `search` - (Required) You can set whether this index should be searchable or not. (index of type text ist always searchable) +* `facet` - (Optional) You can get facet information by enabling this. +* `return` - (Required) You can enable returning the value of all searchable fields. +* `sort` - (Optional) You can enable the property to be sortable. +* `highlight` - (Optional) You can highlight information. +* `analysis_scheme` - (Optional) Only needed with type `text`. [AWS's Docs - supported languages](http://docs.aws.amazon.com/cloudsearch/latest/developerguide/text-processing.html) +* `default_value` - (Optional) The default value. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `document_endpoint` - The doc service end point - see wait_for_endpoints parameter +* `search_endpoint` - The search service end point - see wait_for_endpoints parameter +* `domain_id` - The domain id From f2436a49fbab7b1ecb557d5ac3061a55969daeaa Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Fri, 19 Feb 2021 23:30:30 +0000 Subject: [PATCH 05/50] updating tests with `analysis_scheme` restriction --- resource_aws_cloudsearch_domain.go | 15 ++- resource_aws_cloudsearch_domain_test.go | 171 +++++++++++++++++------- 2 files changed, 128 insertions(+), 58 deletions(-) diff --git a/resource_aws_cloudsearch_domain.go b/resource_aws_cloudsearch_domain.go index 3aefd150788b..531089f83a09 100644 --- a/resource_aws_cloudsearch_domain.go +++ b/resource_aws_cloudsearch_domain.go @@ -555,11 +555,6 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi extractFromMapToType(index, "highlight", &highlight) extractFromMapToType(index, "analysis_scheme", &analysisScheme) - // NOTE: only way I know of to set a default for this field since not all index fields can use it. - if analysisScheme == "" { - analysisScheme = "_en_default_" - } - switch index["type"] { case "date": input.DateOptions = &cloudsearch.DateOptions{ @@ -669,7 +664,10 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi SortEnabled: aws.Bool(sort), ReturnEnabled: aws.Bool(returnV), HighlightEnabled: aws.Bool(highlight), - AnalysisScheme: aws.String(analysisScheme), + } + + if analysisScheme != "" { + input.TextOptions.AnalysisScheme = aws.String(analysisScheme) } if index["default_value"].(string) != "" { @@ -679,7 +677,10 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi input.TextArrayOptions = &cloudsearch.TextArrayOptions{ ReturnEnabled: aws.Bool(returnV), HighlightEnabled: aws.Bool(highlight), - AnalysisScheme: aws.String(analysisScheme), + } + + if analysisScheme != "" { + input.TextArrayOptions.AnalysisScheme = aws.String(analysisScheme) } if index["default_value"].(string) != "" { diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index f45fcdeefcb3..811242d6a7a6 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -68,8 +68,41 @@ func TestAccAWSCloudSearchDomain_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + // { + // Config: testAccAWSCloudSearchDomainConfig_basicIndexMix(domainName), + // Check: resource.ComposeTestCheckFunc( + // testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), + // resource.TestCheckResourceAttr(resourceName, "name", domainName), + // ), + // }, + // { + // ResourceName: resourceName, + // ImportState: true, + // ImportStateVerify: true, + // }, + }, + }) +} + +func TestAccAWSCloudSearchDomain_textAnalysisScheme(t *testing.T) { + var domains cloudsearch.DescribeDomainsOutput + resourceName := "aws_cloudsearch_domain.test" + domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + Steps: []resource.TestStep{ { - Config: testAccAWSCloudSearchDomainConfig_basicIndexMix(domainName), + Config: testAccAWSCloudSearchDomainConfig_textAnalysisScheme(domainName, "_en_default_"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), + resource.TestCheckResourceAttr(resourceName, "name", domainName), + ), + }, + { + Config: testAccAWSCloudSearchDomainConfig_textAnalysisScheme(domainName, "_fr_default_"), Check: resource.ComposeTestCheckFunc( testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), resource.TestCheckResourceAttr(resourceName, "name", domainName), @@ -347,58 +380,94 @@ func testAccAWSCloudSearchDomainConfig_basicIndexMix(name string) string { resource "aws_cloudsearch_domain" "test" { name = "%s" + index { + name = "how_about_one_up_here" + type = "text" + analysis_scheme = "_en_default_" + } + + index { + name = "date_test" + type = "date" + facet = true + search = true + return = true + sort = true + } + + index { + name = "double_test_2" + type = "double" + facet = true + search = true + return = true + sort = true + } + + index { + name = "double_array_test" + type = "double-array" + facet = true + search = true + return = true + } + + index { + name = "just_another_index_name" + type = "literal-array" + facet = true + search = true + return = true + } + + index { + name = "text_test" + type = "text" + analysis_scheme = "_en_default_" + highlight = true + return = true + sort = true + } + + index { + name = "captain_janeway_is_pretty_cool" + type = "double" + } + + wait_for_endpoints = false + service_access_policies = < Date: Fri, 19 Feb 2021 23:34:16 +0000 Subject: [PATCH 06/50] delinting Markdown --- website/docs/r/cloudsearch_domain.html.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index 98e06fdbb72a..776cf999fbbd 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -53,9 +53,10 @@ data "aws_iam_policy_document" "cloudsearch_access_policy" { ``` -## Using aws_cloudsearch_domain with API gateway. +## Using aws_cloudsearch_domain with API gateway When you are using a cloudsearch domain with an aws_api_gateway_integration you need to set uri of the AWS cloudsearch service, to a specially formatted uri, that includes the first part of the search endpoint that is returned from this provider. The below will help. + ``` data "aws_region" "current" {} resource "aws_api_gateway_integration" "sample" { From f28d61244c94280b422e356da64d4a819fd32ac0 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Fri, 19 Feb 2021 23:35:53 +0000 Subject: [PATCH 07/50] delinting Golang --- resource_aws_cloudsearch_domain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource_aws_cloudsearch_domain.go b/resource_aws_cloudsearch_domain.go index 531089f83a09..8da57aaeb031 100644 --- a/resource_aws_cloudsearch_domain.go +++ b/resource_aws_cloudsearch_domain.go @@ -168,7 +168,7 @@ func resourceAwsCloudSearchDomainCreate(d *schema.ResourceData, meta interface{} return fmt.Errorf("%s %q", err, d.Get("name").(string)) } - d.SetId(*output.DomainStatus.ARN) + d.SetId(aws.StringValue(output.DomainStatus.ARN)) return resourceAwsCloudSearchDomainUpdate(d, meta) } From 6de4e199ccca5bcdcef4af4da2d5c76380a623ed Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Fri, 19 Feb 2021 23:41:47 +0000 Subject: [PATCH 08/50] converting tabs to spaces for terrafmt --- resource_aws_cloudsearch_domain_test.go | 468 ++++++++++++------------ 1 file changed, 234 insertions(+), 234 deletions(-) diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index 811242d6a7a6..0368c71b3725 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -262,115 +262,115 @@ func testAccCheckAWSCloudSearchDomainDestroy(s *terraform.State) error { func testAccAWSCloudSearchDomainConfig_basic(name string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { - name = "%s" - - index { - name = "date_test" - type = "date" - facet = true - search = true - return = true - sort = true - } - - index { - name = "date_array_test" - type = "date-array" - facet = true - search = true - return = true - } - - index { - name = "double_test" - type = "double" - facet = true - search = true - return = true - sort = true - } - - index { - name = "double_array_test" - type = "double-array" - facet = true - search = true - return = true - } - - index { - name = "int_test" - type = "int" - facet = true - search = true - return = true - sort = true - } - - index { - name = "int_array_test" - type = "int-array" - facet = true - search = true - return = true - } - - index { - name = "latlon_test" - type = "latlon" - facet = true - search = true - return = true - sort = true - } - - index { - name = "literal_test" - type = "literal" - facet = true - search = true - return = true - sort = true - } - - index { - name = "literal_array_test" - type = "literal-array" - facet = true - search = true - return = true - } - - index { - name = "text_test" - type = "text" - analysis_scheme = "_en_default_" - highlight = true - return = true - sort = true - } - - index { - name = "text_array_test" - type = "text-array" - analysis_scheme = "_en_default_" - highlight = true - return = true - } - - wait_for_endpoints = false - service_access_policies = < Date: Fri, 19 Feb 2021 23:42:54 +0000 Subject: [PATCH 09/50] adding check destroys --- resource_aws_cloudsearch_domain_test.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index 0368c71b3725..d21b70e58671 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -119,8 +119,9 @@ func TestAccAWSCloudSearchDomain_textAnalysisScheme(t *testing.T) { func TestAccAWSCloudSearchDomain_badName(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_basic("-this-is-a-bad-name"), @@ -134,8 +135,9 @@ func TestAccAWSCloudSearchDomain_badInstanceType(t *testing.T) { domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withInstanceType(domainName, "nope.small"), @@ -149,8 +151,9 @@ func TestAccAWSCloudSearchDomain_badIndexFieldNames(t *testing.T) { domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "HELLO", "text"), @@ -176,8 +179,9 @@ func TestAccAWSCloudSearchDomain_badIndexFieldType(t *testing.T) { domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "directory", "not-a-type"), From ddd9fb4bd10fc13fb0a2eab80834aff2589c63d7 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Fri, 19 Feb 2021 23:46:53 +0000 Subject: [PATCH 10/50] delinting Golang code --- resource_aws_cloudsearch_domain.go | 54 ++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/resource_aws_cloudsearch_domain.go b/resource_aws_cloudsearch_domain.go index 8da57aaeb031..48bdce756c88 100644 --- a/resource_aws_cloudsearch_domain.go +++ b/resource_aws_cloudsearch_domain.go @@ -254,6 +254,9 @@ func resourceAwsCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{} DomainName: aws.String(d.Get("name").(string)), AccessPolicies: aws.String(d.Get("service_access_policies").(string)), }) + if err != nil { + return err + } _, err = conn.UpdateScalingParameters(&cloudsearch.UpdateScalingParametersInput{ DomainName: aws.String(d.Get("name").(string)), @@ -263,13 +266,19 @@ func resourceAwsCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{} DesiredPartitionCount: aws.Int64(int64(d.Get("partition_count").(int))), }, }) + if err != nil { + return err + } _, err = conn.UpdateAvailabilityOptions(&cloudsearch.UpdateAvailabilityOptionsInput{ DomainName: aws.String(d.Get("name").(string)), MultiAZ: aws.Bool(d.Get("multi_az").(bool)), }) + if err != nil { + return err + } - updated, err := defineIndexFields(d, meta, conn) + updated, err := defineIndexFields(d, conn) if err != nil { return err } @@ -445,7 +454,7 @@ func waitForSearchDomainToBeAvailable(d *schema.ResourceData, conn *cloudsearch. } // Miscellaneous helper functions -func defineIndexFields(d *schema.ResourceData, meta interface{}, conn *cloudsearch.CloudSearch) (bool, error) { +func defineIndexFields(d *schema.ResourceData, conn *cloudsearch.CloudSearch) (bool, error) { // Early return if we don't have a change. if !d.HasChange("index") { return false, nil @@ -541,6 +550,7 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi IndexFieldType: aws.String(index["type"].(string)), } + // TODO: clean this up, this very likely could be written in a much cleaner way than this. var facet bool var returnV bool var search bool @@ -548,12 +558,36 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi var highlight bool var analysisScheme string - extractFromMapToType(index, "facet", &facet) - extractFromMapToType(index, "return", &returnV) - extractFromMapToType(index, "search", &search) - extractFromMapToType(index, "sort", &sort) - extractFromMapToType(index, "highlight", &highlight) - extractFromMapToType(index, "analysis_scheme", &analysisScheme) + err := extractFromMapToType(index, "facet", &facet) + if err != nil { + return nil, err + } + + err = extractFromMapToType(index, "return", &returnV) + if err != nil { + return nil, err + } + + err = extractFromMapToType(index, "search", &search) + if err != nil { + return nil, err + } + + err = extractFromMapToType(index, "sort", &sort) + if err != nil { + return nil, err + } + + err = extractFromMapToType(index, "highlight", &highlight) + if err != nil { + return nil, err + } + + err = extractFromMapToType(index, "analysis_scheme", &analysisScheme) + if err != nil { + return nil, err + } + switch index["type"] { case "date": @@ -588,7 +622,7 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi if index["default_value"].(string) != "" { var defaultValue float64 extractFromMapToType(index, "default_value", &defaultValue) - input.DoubleOptions.DefaultValue = aws.Float64(float64(defaultValue)) + input.DoubleOptions.DefaultValue = aws.Float64(defaultValue) } case "double-array": input.DoubleArrayOptions = &cloudsearch.DoubleArrayOptions{ @@ -600,7 +634,7 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi if index["default_value"].(string) != "" { var defaultValue float64 extractFromMapToType(index, "default_value", &defaultValue) - input.DoubleArrayOptions.DefaultValue = aws.Float64(float64(defaultValue)) + input.DoubleArrayOptions.DefaultValue = aws.Float64(defaultValue) } case "int": input.IntOptions = &cloudsearch.IntOptions{ From 4555598a29f20c9ed8df3b88a13a413e96951d05 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Fri, 19 Feb 2021 23:52:34 +0000 Subject: [PATCH 11/50] formatting Terraform --- resource_aws_cloudsearch_domain_test.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index d21b70e58671..9d0a3079292a 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -301,7 +301,7 @@ resource "aws_cloudsearch_domain" "test" { search = true return = true } - + index { name = "int_test" type = "int" @@ -362,7 +362,7 @@ resource "aws_cloudsearch_domain" "test" { return = true } - wait_for_endpoints = false + wait_for_endpoints = false service_access_policies = < Date: Sat, 20 Feb 2021 11:19:29 +0000 Subject: [PATCH 12/50] delinting Golang code --- resource_aws_cloudsearch_domain.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/resource_aws_cloudsearch_domain.go b/resource_aws_cloudsearch_domain.go index 48bdce756c88..b6be853f91be 100644 --- a/resource_aws_cloudsearch_domain.go +++ b/resource_aws_cloudsearch_domain.go @@ -588,7 +588,6 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi return nil, err } - switch index["type"] { case "date": input.DateOptions = &cloudsearch.DateOptions{ @@ -621,7 +620,10 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi if index["default_value"].(string) != "" { var defaultValue float64 - extractFromMapToType(index, "default_value", &defaultValue) + err = extractFromMapToType(index, "default_value", &defaultValue) + if err != nil { + return nil, err + } input.DoubleOptions.DefaultValue = aws.Float64(defaultValue) } case "double-array": @@ -633,7 +635,10 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi if index["default_value"].(string) != "" { var defaultValue float64 - extractFromMapToType(index, "default_value", &defaultValue) + err = extractFromMapToType(index, "default_value", &defaultValue) + if err != nil { + return nil, err + } input.DoubleArrayOptions.DefaultValue = aws.Float64(defaultValue) } case "int": @@ -646,7 +651,10 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi if index["default_value"].(string) != "" { var defaultValue int - extractFromMapToType(index, "default_value", &defaultValue) + err = extractFromMapToType(index, "default_value", &defaultValue) + if err != nil { + return nil, err + } input.IntOptions.DefaultValue = aws.Int64(int64(defaultValue)) } case "int-array": @@ -658,7 +666,10 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi if index["default_value"].(string) != "" { var defaultValue int - extractFromMapToType(index, "default_value", &defaultValue) + err = extractFromMapToType(index, "default_value", &defaultValue) + if err != nil { + return nil, err + } input.IntArrayOptions.DefaultValue = aws.Int64(int64(defaultValue)) } case "latlon": From bbaea040e3d320cfc7656f69329b3a965b669591 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Sat, 20 Feb 2021 11:19:36 +0000 Subject: [PATCH 13/50] readding test steps --- resource_aws_cloudsearch_domain_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index 9d0a3079292a..96845765ab59 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -68,18 +68,18 @@ func TestAccAWSCloudSearchDomain_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - // { - // Config: testAccAWSCloudSearchDomainConfig_basicIndexMix(domainName), - // Check: resource.ComposeTestCheckFunc( - // testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), - // resource.TestCheckResourceAttr(resourceName, "name", domainName), - // ), - // }, - // { - // ResourceName: resourceName, - // ImportState: true, - // ImportStateVerify: true, - // }, + { + Config: testAccAWSCloudSearchDomainConfig_basicIndexMix(domainName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), + resource.TestCheckResourceAttr(resourceName, "name", domainName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } From 7cbb8fd75e54eb9b0ef0e6bd8f33b494b711f947 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Tue, 30 Mar 2021 17:20:36 +0000 Subject: [PATCH 14/50] cleaning up Markdown file --- website/docs/r/cloudsearch_domain.html.markdown | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index 776cf999fbbd..00a874b6d40e 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -12,7 +12,7 @@ Provides an CloudSearch domain resource. ## Example Usage -```hcl +```terraform resource "aws_cloudsearch_domain" "my_domain" { domain_name = "test-domain" instance_type = "search.medium" @@ -52,21 +52,6 @@ data "aws_iam_policy_document" "cloudsearch_access_policy" { } ``` - -## Using aws_cloudsearch_domain with API gateway - -When you are using a cloudsearch domain with an aws_api_gateway_integration you need to set uri of the AWS cloudsearch service, to a specially formatted uri, that includes the first part of the search endpoint that is returned from this provider. The below will help. - -``` -data "aws_region" "current" {} -resource "aws_api_gateway_integration" "sample" { - ... - type = "AWS" - uri = "arn:aws:apigateway:${data.aws_region.current.name}:${replace(aws_cloudsearch_domain.my_domain.search_endpoint, ".${data.aws_region.current.name}.cloudsearch.amazonaws.com", "")}.cloudsearch:path/2013-01-01/search" - ... -} -``` - ## Argument Reference The following arguments are supported: From f532d7a4875e49219f0d91f2eb86975cbcdc1e10 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Tue, 30 Mar 2021 17:34:01 +0000 Subject: [PATCH 15/50] adding a skeleton for an error check, just to see if I can get movement on that --- resource_aws_cloudsearch_domain_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index 96845765ab59..db85a0f32652 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -55,6 +55,7 @@ func TestAccAWSCloudSearchDomain_basic(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + ErrorCheck: func(err error) error {return err}, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_basic(domainName), @@ -93,6 +94,7 @@ func TestAccAWSCloudSearchDomain_textAnalysisScheme(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + ErrorCheck: func(err error) error {return err}, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_textAnalysisScheme(domainName, "_en_default_"), @@ -122,6 +124,7 @@ func TestAccAWSCloudSearchDomain_badName(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + ErrorCheck: func(err error) error {return err}, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_basic("-this-is-a-bad-name"), @@ -138,6 +141,7 @@ func TestAccAWSCloudSearchDomain_badInstanceType(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + ErrorCheck: func(err error) error {return err}, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withInstanceType(domainName, "nope.small"), @@ -154,6 +158,7 @@ func TestAccAWSCloudSearchDomain_badIndexFieldNames(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + ErrorCheck: func(err error) error {return err}, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "HELLO", "text"), @@ -182,6 +187,7 @@ func TestAccAWSCloudSearchDomain_badIndexFieldType(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, + ErrorCheck: func(err error) error {return err}, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "directory", "not-a-type"), From d524a5b9ac57149d9e0c84b02bde5164d91f1938 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Tue, 30 Mar 2021 17:34:43 +0000 Subject: [PATCH 16/50] fixing typo --- website/docs/r/cloudsearch_domain.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index 00a874b6d40e..d6090c0bdb79 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -78,7 +78,7 @@ Each of the `index` entities represents an index field of the domain. * `analysis_scheme` - (Optional) Only needed with type `text`. [AWS's Docs - supported languages](http://docs.aws.amazon.com/cloudsearch/latest/developerguide/text-processing.html) * `default_value` - (Optional) The default value. -## Attribute Reference +## Attributes Reference In addition to all arguments above, the following attributes are exported: From 9a0d20f5aa89ac7625cdc7c8b7ebb6b42712d3c1 Mon Sep 17 00:00:00 2001 From: Christopher Busby Date: Tue, 30 Mar 2021 17:47:50 +0000 Subject: [PATCH 17/50] formatting Gocode --- resource_aws_cloudsearch_domain_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resource_aws_cloudsearch_domain_test.go b/resource_aws_cloudsearch_domain_test.go index db85a0f32652..615337789d90 100644 --- a/resource_aws_cloudsearch_domain_test.go +++ b/resource_aws_cloudsearch_domain_test.go @@ -55,7 +55,7 @@ func TestAccAWSCloudSearchDomain_basic(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error {return err}, + ErrorCheck: func(err error) error { return err }, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_basic(domainName), @@ -94,7 +94,7 @@ func TestAccAWSCloudSearchDomain_textAnalysisScheme(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error {return err}, + ErrorCheck: func(err error) error { return err }, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_textAnalysisScheme(domainName, "_en_default_"), @@ -124,7 +124,7 @@ func TestAccAWSCloudSearchDomain_badName(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error {return err}, + ErrorCheck: func(err error) error { return err }, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_basic("-this-is-a-bad-name"), @@ -141,7 +141,7 @@ func TestAccAWSCloudSearchDomain_badInstanceType(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error {return err}, + ErrorCheck: func(err error) error { return err }, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withInstanceType(domainName, "nope.small"), @@ -158,7 +158,7 @@ func TestAccAWSCloudSearchDomain_badIndexFieldNames(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error {return err}, + ErrorCheck: func(err error) error { return err }, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "HELLO", "text"), @@ -187,7 +187,7 @@ func TestAccAWSCloudSearchDomain_badIndexFieldType(t *testing.T) { PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error {return err}, + ErrorCheck: func(err error) error { return err }, Steps: []resource.TestStep{ { Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "directory", "not-a-type"), From 72becc6d3a6ede0e45d1f87e00efa16402196545 Mon Sep 17 00:00:00 2001 From: celestialorb <321475+celestialorb@users.noreply.github.com> Date: Wed, 21 Apr 2021 11:41:17 +0000 Subject: [PATCH 18/50] Update website/docs/r/cloudsearch_domain.html.markdown Fixing outdated example in documentation. Co-authored-by: Sam Hall --- website/docs/r/cloudsearch_domain.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index d6090c0bdb79..aa0f7f7a214f 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -14,7 +14,7 @@ Provides an CloudSearch domain resource. ```terraform resource "aws_cloudsearch_domain" "my_domain" { - domain_name = "test-domain" + name = "test-domain" instance_type = "search.medium" index { From 7ca3ad4a3082f0b6d4030a04ff619f7cfd6021ea Mon Sep 17 00:00:00 2001 From: celestialorb <321475+celestialorb@users.noreply.github.com> Date: Wed, 21 Apr 2021 11:42:40 +0000 Subject: [PATCH 19/50] Update cloudsearch_domain.html.markdown Correcting formatting. --- website/docs/r/cloudsearch_domain.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index aa0f7f7a214f..97bbdef00440 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -14,7 +14,7 @@ Provides an CloudSearch domain resource. ```terraform resource "aws_cloudsearch_domain" "my_domain" { - name = "test-domain" + name = "test-domain" instance_type = "search.medium" index { From 1961ec657da124b8c4276d1a617847e3e2cb3c66 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 16:46:59 -0500 Subject: [PATCH 20/50] Probe... From 79cb0a414bb02bd503267b84d3470c8aa4f882de Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 16:55:57 -0500 Subject: [PATCH 21/50] Move r/aws_cloudsearch_domain to internal service package. --- .../service/cloudsearch/domain.go | 0 .../service/cloudsearch/domain_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename resource_aws_cloudsearch_domain.go => internal/service/cloudsearch/domain.go (100%) rename resource_aws_cloudsearch_domain_test.go => internal/service/cloudsearch/domain_test.go (100%) diff --git a/resource_aws_cloudsearch_domain.go b/internal/service/cloudsearch/domain.go similarity index 100% rename from resource_aws_cloudsearch_domain.go rename to internal/service/cloudsearch/domain.go diff --git a/resource_aws_cloudsearch_domain_test.go b/internal/service/cloudsearch/domain_test.go similarity index 100% rename from resource_aws_cloudsearch_domain_test.go rename to internal/service/cloudsearch/domain_test.go From 192fde32906f9714bf33d670ba5a711873c1ff9e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 16:58:25 -0500 Subject: [PATCH 22/50] Add CloudSearch service pcakage README. --- internal/service/cloudsearch/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 internal/service/cloudsearch/README.md 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/) From d2a75c8957efadcf328681223804552ab5787088 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 17:20:09 -0500 Subject: [PATCH 23/50] r/aws_cloudsearch_domain: First compiling version after service packages rebase. --- internal/service/cloudsearch/domain.go | 83 +++------ internal/service/cloudsearch/domain_test.go | 184 ++++---------------- 2 files changed, 65 insertions(+), 202 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index b6be853f91be..d4004a52c722 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -1,4 +1,4 @@ -package aws +package cloudsearch import ( "fmt" @@ -14,16 +14,20 @@ import ( "github.com/aws/aws-sdk-go/service/cloudsearch" "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" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -func resourceAwsCloudSearchDomain() *schema.Resource { +func ResourceDomain() *schema.Resource { return &schema.Resource{ - Create: resourceAwsCloudSearchDomainCreate, - Read: resourceAwsCloudSearchDomainRead, - Update: resourceAwsCloudSearchDomainUpdate, - Delete: resourceAwsCloudSearchDomainDelete, + Create: resourceCloudSearchDomainCreate, + Read: resourceCloudSearchDomainRead, + Update: resourceCloudSearchDomainUpdate, + Delete: resourceCloudSearchDomainDelete, Importer: &schema.ResourceImporter{ - State: resourceAwsCloudSearchDomainImport, + State: resourceCloudSearchDomainImport, }, Schema: map[string]*schema.Schema{ @@ -39,7 +43,7 @@ func resourceAwsCloudSearchDomain() *schema.Resource { Type: schema.TypeString, Optional: true, Default: "search.small", - ValidateFunc: validateInstanceType, + ValidateFunc: validation.StringInSlice(cloudsearch.PartitionInstanceType_Values(), false), }, "replication_count": { @@ -62,9 +66,9 @@ func resourceAwsCloudSearchDomain() *schema.Resource { "service_access_policies": { Type: schema.TypeString, - ValidateFunc: validateIAMPolicyJson, Required: true, - DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, + DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, + ValidateFunc: validation.StringIsJSON, }, "index": { @@ -81,7 +85,7 @@ func resourceAwsCloudSearchDomain() *schema.Resource { "type": { Type: schema.TypeString, Required: true, - ValidateFunc: validateIndexType, + ValidateFunc: validation.StringInSlice(cloudsearch.IndexFieldType_Values(), false), }, "search": { @@ -149,14 +153,15 @@ func resourceAwsCloudSearchDomain() *schema.Resource { Computed: true, }, - "tags": tagsSchema(), + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), }, } } // Terraform CRUD Functions -func resourceAwsCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).cloudsearchconn +func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn input := cloudsearch.CreateDomainInput{ DomainName: aws.String(d.Get("name").(string)), @@ -169,11 +174,11 @@ func resourceAwsCloudSearchDomainCreate(d *schema.ResourceData, meta interface{} } d.SetId(aws.StringValue(output.DomainStatus.ARN)) - return resourceAwsCloudSearchDomainUpdate(d, meta) + return resourceCloudSearchDomainUpdate(d, meta) } -func resourceAwsCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).cloudsearchconn +func resourceCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn domainlist := cloudsearch.DescribeDomainsInput{ DomainNames: []*string{ @@ -247,8 +252,8 @@ func resourceAwsCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) return err } -func resourceAwsCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).cloudsearchconn +func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn _, err := conn.UpdateServiceAccessPolicies(&cloudsearch.UpdateServiceAccessPoliciesInput{ DomainName: aws.String(d.Get("name").(string)), @@ -310,8 +315,8 @@ func resourceAwsCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{} return nil } -func resourceAwsCloudSearchDomainDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).cloudsearchconn +func resourceCloudSearchDomainDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).CloudSearchConn dm := d.Get("name").(string) input := cloudsearch.DeleteDomainInput{ @@ -324,7 +329,7 @@ func resourceAwsCloudSearchDomainDelete(d *schema.ResourceData, meta interface{} } // Import Function -func resourceAwsCloudSearchDomainImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { +func resourceCloudSearchDomainImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { d.SetId(d.Id()) arn, err := arn.Parse(d.Id()) @@ -352,23 +357,6 @@ func validateDomainName(v interface{}, k string) (ws []string, es []error) { return } -func validateInstanceType(v interface{}, k string) (ws []string, es []error) { - value := v.(string) - found := false - for _, t := range cloudsearch.PartitionInstanceType_Values() { - if t == value { - found = true - continue - } - } - - if !found { - es = append(es, fmt.Errorf("%q is not a valid instance type", v)) - } - - return -} - func validateIndexName(v interface{}, k string) (ws []string, es []error) { value := v.(string) @@ -384,23 +372,6 @@ func validateIndexName(v interface{}, k string) (ws []string, es []error) { return } -func validateIndexType(v interface{}, k string) (ws []string, es []error) { - value := v.(string) - found := false - for _, t := range cloudsearch.IndexFieldType_Values() { - if t == value { - found = true - continue - } - } - - if !found { - es = append(es, fmt.Errorf("%q is not a valid index type", v)) - } - - return -} - // Waiters func waitForSearchDomainToBeAvailable(d *schema.ResourceData, conn *cloudsearch.CloudSearch, domainlist cloudsearch.DescribeDomainsInput) error { log.Printf("[INFO] cloudsearch (%#v) waiting for domain endpoint. This usually takes 10 minutes.", domainlist.DomainNames[0]) diff --git a/internal/service/cloudsearch/domain_test.go b/internal/service/cloudsearch/domain_test.go index 615337789d90..38ecb8e80563 100644 --- a/internal/service/cloudsearch/domain_test.go +++ b/internal/service/cloudsearch/domain_test.go @@ -1,67 +1,36 @@ -package aws +package cloudsearch_test import ( "fmt" - "regexp" "strconv" - "strings" "testing" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudsearch" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + 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" ) -func init() { - resource.AddTestSweepers("aws_cloudsearch_domain", &resource.Sweeper{ - Name: "aws_cloudsearch_domain", - F: func(region string) error { - client, err := sharedClientForRegion(region) - if err != nil { - return fmt.Errorf("error getting client: %s", err) - } - conn := client.(*AWSClient).cloudsearchconn - - domains, err := conn.DescribeDomains(&cloudsearch.DescribeDomainsInput{}) - if err != nil { - return fmt.Errorf("error describing CloudSearch domains: %s", err) - } - - for _, domain := range domains.DomainStatusList { - if !strings.HasPrefix(*domain.DomainName, "tf-acc-") { - continue - } - _, err := conn.DeleteDomain(&cloudsearch.DeleteDomainInput{ - DomainName: domain.DomainName, - }) - if err != nil { - return fmt.Errorf("error deleting CloudSearch domain: %s", err) - } - } - return nil - }, - }) -} - -func TestAccAWSCloudSearchDomain_basic(t *testing.T) { +func TestAccCloudSearchDomain_basic(t *testing.T) { var domains cloudsearch.DescribeDomainsOutput resourceName := "aws_cloudsearch_domain.test" - domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error { return err }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(cloudsearch.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, cloudsearch.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCloudSearchDomainDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudSearchDomainConfig_basic(domainName), + Config: testAccCloudSearchDomainConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), - resource.TestCheckResourceAttr(resourceName, "name", domainName), + testAccCloudSearchDomainExists(resourceName, &domains), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, { @@ -70,10 +39,10 @@ func TestAccAWSCloudSearchDomain_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSCloudSearchDomainConfig_basicIndexMix(domainName), + Config: testAccCloudSearchDomainConfig_basicIndexMix(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), - resource.TestCheckResourceAttr(resourceName, "name", domainName), + testAccCloudSearchDomainExists(resourceName, &domains), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, { @@ -85,29 +54,29 @@ func TestAccAWSCloudSearchDomain_basic(t *testing.T) { }) } -func TestAccAWSCloudSearchDomain_textAnalysisScheme(t *testing.T) { +func TestAccCloudSearchDomain_textAnalysisScheme(t *testing.T) { var domains cloudsearch.DescribeDomainsOutput resourceName := "aws_cloudsearch_domain.test" - domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error { return err }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(cloudsearch.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, cloudsearch.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCloudSearchDomainDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudSearchDomainConfig_textAnalysisScheme(domainName, "_en_default_"), + Config: testAccCloudSearchDomainConfig_textAnalysisScheme(rName, "_en_default_"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), - resource.TestCheckResourceAttr(resourceName, "name", domainName), + testAccCloudSearchDomainExists(resourceName, &domains), + resource.TestCheckResourceAttr(resourceName, "name", acctest.RandomFQDomainName()), ), }, { - Config: testAccAWSCloudSearchDomainConfig_textAnalysisScheme(domainName, "_fr_default_"), + Config: testAccCloudSearchDomainConfig_textAnalysisScheme(rName, "_fr_default_"), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSCloudSearchDomainExists(resourceName, &domains), - resource.TestCheckResourceAttr(resourceName, "name", domainName), + testAccCloudSearchDomainExists(resourceName, &domains), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, { @@ -119,92 +88,14 @@ func TestAccAWSCloudSearchDomain_textAnalysisScheme(t *testing.T) { }) } -func TestAccAWSCloudSearchDomain_badName(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error { return err }, - Steps: []resource.TestStep{ - { - Config: testAccAWSCloudSearchDomainConfig_basic("-this-is-a-bad-name"), - ExpectError: regexp.MustCompile(`.*"name" must begin with a.*`), - }, - }, - }) -} - -func TestAccAWSCloudSearchDomain_badInstanceType(t *testing.T) { - domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error { return err }, - Steps: []resource.TestStep{ - { - Config: testAccAWSCloudSearchDomainConfig_withInstanceType(domainName, "nope.small"), - ExpectError: regexp.MustCompile(`.*is not a valid instance type.*`), - }, - }, - }) -} - -func TestAccAWSCloudSearchDomain_badIndexFieldNames(t *testing.T) { - domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error { return err }, - Steps: []resource.TestStep{ - { - Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "HELLO", "text"), - ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), - }, - { - Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "w-a", "text"), - ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), - }, - { - Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "jfjdbfjdhsjakhfdhsajkfhdjksahfdsbfkjchndsjkhafbjdkshafjkdshjfhdsjkahfjkdsha", "text"), - ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), - }, - { - Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "w", "text"), - ExpectError: regexp.MustCompile(`.*must begin with a letter and be at least 3 and no more than 64 characters long.*`), - }, - }, - }) -} - -func TestAccAWSCloudSearchDomain_badIndexFieldType(t *testing.T) { - domainName := fmt.Sprintf("tf-acc-%s", acctest.RandString(8)) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSCloudSearchDomainDestroy, - ErrorCheck: func(err error) error { return err }, - Steps: []resource.TestStep{ - { - Config: testAccAWSCloudSearchDomainConfig_withIndex(domainName, "directory", "not-a-type"), - ExpectError: regexp.MustCompile(`.*is not a valid index type.*`), - }, - }, - }) -} - -func testAccCheckAWSCloudSearchDomainExists(n string, domains *cloudsearch.DescribeDomainsOutput) resource.TestCheckFunc { +func testAccCloudSearchDomainExists(n string, domains *cloudsearch.DescribeDomainsOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := testAccProvider.Meta().(*AWSClient).cloudsearchconn + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudSearchConn domainList := cloudsearch.DescribeDomainsInput{ DomainNames: []*string{ @@ -223,13 +114,14 @@ func testAccCheckAWSCloudSearchDomainExists(n string, domains *cloudsearch.Descr } } -func testAccCheckAWSCloudSearchDomainDestroy(s *terraform.State) error { +func testAccCloudSearchDomainDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudSearchConn + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_cloudsearch_domain" { continue } - conn := testAccProvider.Meta().(*AWSClient).cloudsearchconn // Wait for the resource to start being deleted, which is marked as "Deleted" from the API. stateConf := &resource.StateChangeConf{ Pending: []string{"false"}, @@ -269,7 +161,7 @@ func testAccCheckAWSCloudSearchDomainDestroy(s *terraform.State) error { return nil } -func testAccAWSCloudSearchDomainConfig_basic(name string) string { +func testAccCloudSearchDomainConfig_basic(name string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { name = "%s" @@ -385,7 +277,7 @@ EOF `, name) } -func testAccAWSCloudSearchDomainConfig_basicIndexMix(name string) string { +func testAccCloudSearchDomainConfig_basicIndexMix(name string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { name = "%s" @@ -463,7 +355,7 @@ EOF // NOTE: I'd like to get text and text arrays field to work properly without having to explicitly set the // `analysis_scheme` field, but I cannot find a way to suppress the diff Terraform ends up generating as a result. -func testAccAWSCloudSearchDomainConfig_textAnalysisScheme(name string, scheme string) string { +func testAccCloudSearchDomainConfig_textAnalysisScheme(name string, scheme string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { name = "%s" @@ -496,7 +388,7 @@ EOF `, name, scheme) } -func testAccAWSCloudSearchDomainConfig_withInstanceType(name string, instance_type string) string { +func testAccCloudSearchDomainConfig_withInstanceType(name string, instance_type string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { name = "%s" @@ -520,7 +412,7 @@ EOF `, name, instance_type) } -func testAccAWSCloudSearchDomainConfig_withIndex(name string, index_name string, index_type string) string { +func testAccCloudSearchDomainConfig_withIndex(name string, index_name string, index_type string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { name = "%s" From fb107a07970051804e7f103cc3a0c7c39987571d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 17:23:54 -0500 Subject: [PATCH 24/50] Add 'aws_cloudsearch_domain' to supported resources. --- internal/provider/provider.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 646c1cc115e8..e831e9cf690f 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,8 @@ func Provider() *schema.Provider { "aws_cloudhsm_v2_cluster": cloudhsmv2.ResourceCluster(), "aws_cloudhsm_v2_hsm": cloudhsmv2.ResourceHSM(), + "aws_cloudsearch_domain": cloudsearch.ResourceDomain(), + "aws_cloudtrail": cloudtrail.ResourceCloudTrail(), "aws_cloudwatch_composite_alarm": cloudwatch.ResourceCompositeAlarm(), From 2a44f394913b5e7a6cb704d2bced69b1e5a1c5e8 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 17:31:25 -0500 Subject: [PATCH 25/50] r/aws_cloudsearch_domain: Sweeper. --- internal/service/cloudsearch/sweep.go | 45 +++++++++++++++++++++++++++ internal/sweep/sweep_test.go | 1 + 2 files changed, 46 insertions(+) create mode 100644 internal/service/cloudsearch/sweep.go diff --git a/internal/service/cloudsearch/sweep.go b/internal/service/cloudsearch/sweep.go new file mode 100644 index 000000000000..b50a78d44815 --- /dev/null +++ b/internal/service/cloudsearch/sweep.go @@ -0,0 +1,45 @@ +//go:build sweep +// +build sweep + +package cloudsearch + +import ( + "fmt" + "strings" + + "github.com/aws/aws-sdk-go/service/cloudsearch" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/sweep" +) + +func init() { + resource.AddTestSweepers("aws_cloudsearch_domain", &resource.Sweeper{ + Name: "aws_cloudsearch_domain", + F: func(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.(*conns.AWSClient).CloudSearchConn + + domains, err := conn.DescribeDomains(&cloudsearch.DescribeDomainsInput{}) + if err != nil { + return fmt.Errorf("error describing CloudSearch domains: %s", err) + } + + for _, domain := range domains.DomainStatusList { + if !strings.HasPrefix(*domain.DomainName, "tf-acc-") { + continue + } + _, err := conn.DeleteDomain(&cloudsearch.DeleteDomainInput{ + DomainName: domain.DomainName, + }) + if err != nil { + return fmt.Errorf("error deleting CloudSearch domain: %s", err) + } + } + return nil + }, + }) +} diff --git a/internal/sweep/sweep_test.go b/internal/sweep/sweep_test.go index b41ebdce0be7..2d0fa7aac78d 100644 --- a/internal/sweep/sweep_test.go +++ b/internal/sweep/sweep_test.go @@ -26,6 +26,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" From 7356497c3939df068c8af86931e5680a8660981a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 17:55:38 -0500 Subject: [PATCH 26/50] Fix semgrep error: prefer-aws-go-sdk-pointer-conversion-conditional. --- internal/service/cloudsearch/domain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index d4004a52c722..c5d4b68caba7 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -396,7 +396,7 @@ func waitForSearchDomainToBeAvailable(d *schema.ResourceData, conn *cloudsearch. log.Printf("[DEBUG] GLEN: type: %T", domain.SearchService.Endpoint) log.Printf("[DEBUG] GLEN: SearchServiceEndpoint = %s", *domain.SearchService.Endpoint) } - if domain.SearchService.Endpoint == nil || *domain.SearchService.Endpoint == "" { + if aws.StringValue(domain.SearchService.Endpoint) == "" { return resp, "Waiting", nil } return resp, "OK", nil From fdfcc9e8784ea7c04ad92d6655316b5f9a667dd3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 17:57:28 -0500 Subject: [PATCH 27/50] Add CHANGELOG entry. --- .changelog/17723.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/17723.txt diff --git a/.changelog/17723.txt b/.changelog/17723.txt new file mode 100644 index 000000000000..bea8120c0cb7 --- /dev/null +++ b/.changelog/17723.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_cloudsearch_domain +``` \ No newline at end of file From 9c88a14900673872415986b2b570fca89341803d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 8 Dec 2021 18:06:38 -0500 Subject: [PATCH 28/50] First passing acceptance test: % make testacc PKG=cloudsearch TESTS=TestAccCloudSearchDomain_basic ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/cloudsearch/... -v -count 1 -parallel 20 -run='TestAccCloudSearchDomain_basic' -timeout 180m === RUN TestAccCloudSearchDomain_basic === PAUSE TestAccCloudSearchDomain_basic === CONT TestAccCloudSearchDomain_basic --- PASS: TestAccCloudSearchDomain_basic (62.22s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/cloudsearch 66.117s --- internal/service/cloudsearch/domain.go | 6 +----- internal/service/cloudsearch/domain_test.go | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index c5d4b68caba7..620eb6347f46 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -16,7 +16,6 @@ import ( "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" - tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -42,7 +41,7 @@ func ResourceDomain() *schema.Resource { "instance_type": { Type: schema.TypeString, Optional: true, - Default: "search.small", + Default: cloudsearch.PartitionInstanceTypeSearchSmall, ValidateFunc: validation.StringInSlice(cloudsearch.PartitionInstanceType_Values(), false), }, @@ -152,9 +151,6 @@ func ResourceDomain() *schema.Resource { Type: schema.TypeString, Computed: true, }, - - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), }, } } diff --git a/internal/service/cloudsearch/domain_test.go b/internal/service/cloudsearch/domain_test.go index 38ecb8e80563..c38167aa1cd1 100644 --- a/internal/service/cloudsearch/domain_test.go +++ b/internal/service/cloudsearch/domain_test.go @@ -18,7 +18,7 @@ import ( func TestAccCloudSearchDomain_basic(t *testing.T) { var domains cloudsearch.DescribeDomainsOutput resourceName := "aws_cloudsearch_domain.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + 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) }, @@ -57,7 +57,7 @@ func TestAccCloudSearchDomain_basic(t *testing.T) { func TestAccCloudSearchDomain_textAnalysisScheme(t *testing.T) { var domains cloudsearch.DescribeDomainsOutput resourceName := "aws_cloudsearch_domain.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + 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) }, From 3a1a8d932ebe634a09051fc11e75550aecb8dc48 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 9 Dec 2021 16:01:45 -0500 Subject: [PATCH 29/50] Intermediate commit - New and renamed attributes. --- internal/service/cloudsearch/domain.go | 312 +++++++++++++----- internal/service/cloudsearch/sweep.go | 70 ++-- .../docs/r/cloudsearch_domain.html.markdown | 35 +- 3 files changed, 313 insertions(+), 104 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index 620eb6347f46..110489716234 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -6,16 +6,16 @@ import ( "reflect" "regexp" "strconv" - "strings" "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/cloudsearch" "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" ) @@ -26,48 +26,75 @@ func ResourceDomain() *schema.Resource { Update: resourceCloudSearchDomainUpdate, Delete: resourceCloudSearchDomainDelete, Importer: &schema.ResourceImporter{ - State: resourceCloudSearchDomainImport, + State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: validateDomainName, - }, - - "instance_type": { - Type: schema.TypeString, - Optional: true, - Default: cloudsearch.PartitionInstanceTypeSearchSmall, - ValidateFunc: validation.StringInSlice(cloudsearch.PartitionInstanceType_Values(), false), + // TODO: Separate access policy resource? + // TODO: Is it Required? + "access_policies": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, + StateFunc: func(v interface{}) string { + json, _ := structure.NormalizeJsonString(v) + return json + }, }, - - "replication_count": { - Type: schema.TypeInt, - Optional: true, - Default: 0, + "arn": { + Type: schema.TypeString, + Computed: true, }, - - "partition_count": { - Type: schema.TypeInt, + "endpoint_options": { + Type: schema.TypeList, + MaxItems: 1, Optional: true, - Default: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enforce_https": { + Type: schema.TypeBool, + Optional: true, + }, + "tls_security_policy": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(cloudsearch.TLSSecurityPolicy_Values(), false), + }, + }, + }, }, - "multi_az": { Type: schema.TypeBool, Optional: true, - Default: false, }, - - "service_access_policies": { - Type: schema.TypeString, - Required: true, - DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, - ValidateFunc: validation.StringIsJSON, + "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, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "desired_instance_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(cloudsearch.PartitionInstanceType_Values(), false), + }, + "desired_partition_count": { + Type: schema.TypeInt, + Optional: true, + }, + "desired_replication_count": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, }, "index": { @@ -137,11 +164,6 @@ func ResourceDomain() *schema.Resource { DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { return true }, }, - "id": { - Type: schema.TypeString, - Computed: true, - }, - "document_endpoint": { Type: schema.TypeString, Computed: true, @@ -159,17 +181,72 @@ func ResourceDomain() *schema.Resource { func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudSearchConn + name := d.Get("name").(string) input := cloudsearch.CreateDomainInput{ - DomainName: aws.String(d.Get("name").(string)), + 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) } - output, err := conn.CreateDomain(&input) + d.SetId(name) + + log.Printf("[DEBUG] Updating CloudSearch Domain (%s) access policies", name) + _, err = conn.UpdateServiceAccessPolicies(&cloudsearch.UpdateServiceAccessPoliciesInput{ + DomainName: aws.String(d.Id()), + AccessPolicies: aws.String(d.Get("access_policies").(string)), + }) + if err != nil { - log.Printf("[DEBUG] Creating CloudSearch Domain: %#v", input) - return fmt.Errorf("%s %q", err, d.Get("name").(string)) + return fmt.Errorf("error updating CloudSearch Domain (%s) service access policies: %w", name, err) + } + + 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", name, 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", name, err) + } + } + + if v, ok := d.GetOk("scaling_parameters"); 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", name, err) + } } - d.SetId(aws.StringValue(output.DomainStatus.ARN)) return resourceCloudSearchDomainUpdate(d, meta) } @@ -314,43 +391,17 @@ func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) e func resourceCloudSearchDomainDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudSearchConn - dm := d.Get("name").(string) - input := cloudsearch.DeleteDomainInput{ - DomainName: aws.String(dm), - } - - _, err := conn.DeleteDomain(&input) - - return err -} + log.Printf("[DEBUG] Deleting CloudSearch Domain: %s", d.Id()) -// Import Function -func resourceCloudSearchDomainImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - d.SetId(d.Id()) + _, err := conn.DeleteDomain(&cloudsearch.DeleteDomainInput{ + DomainName: aws.String(d.Id()), + }) - arn, err := arn.Parse(d.Id()) if err != nil { - return nil, err + return fmt.Errorf("error deleting CloudSearch Domain (%s): %w", d.Id(), err) } - parts := strings.Split(arn.Resource, "/") - if len(parts) != 2 { - return nil, fmt.Errorf("resource component of ARN is not properly formatted") - } - - d.Set("name", parts[1]) - - return []*schema.ResourceData{d}, nil -} - -// Validation Functions -func validateDomainName(v interface{}, k string) (ws []string, es []error) { - value := v.(string) - if !regexp.MustCompile(`^[a-z]([a-z0-9-]){2,27}$`).MatchString(value) { - es = append(es, fmt.Errorf( - "%q must begin with a lower-case letter, contain only [a-z0-9-] and be at least 3 and at most 28 characters", k)) - } - return + return nil } func validateIndexName(v interface{}, k string) (ws []string, es []error) { @@ -817,3 +868,114 @@ func readIndexField(raw *cloudsearch.IndexField) map[string]interface{} { return index } + +func FindDomainByName(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) + } + + domainStatus := output.DomainStatusList[0] + + if aws.BoolValue(domainStatus.Deleted) { + return nil, &resource.NotFoundError{ + Message: "deleted", + LastRequest: input, + } + } + + return domainStatus, nil +} + +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 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/sweep.go b/internal/service/cloudsearch/sweep.go index b50a78d44815..b9cbed9b0810 100644 --- a/internal/service/cloudsearch/sweep.go +++ b/internal/service/cloudsearch/sweep.go @@ -5,8 +5,9 @@ package cloudsearch import ( "fmt" - "strings" + "log" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudsearch" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -16,30 +17,47 @@ import ( func init() { resource.AddTestSweepers("aws_cloudsearch_domain", &resource.Sweeper{ Name: "aws_cloudsearch_domain", - F: func(region string) error { - client, err := sweep.SharedRegionalSweepClient(region) - if err != nil { - return fmt.Errorf("error getting client: %w", err) - } - conn := client.(*conns.AWSClient).CloudSearchConn - - domains, err := conn.DescribeDomains(&cloudsearch.DescribeDomainsInput{}) - if err != nil { - return fmt.Errorf("error describing CloudSearch domains: %s", err) - } - - for _, domain := range domains.DomainStatusList { - if !strings.HasPrefix(*domain.DomainName, "tf-acc-") { - continue - } - _, err := conn.DeleteDomain(&cloudsearch.DeleteDomainInput{ - DomainName: domain.DomainName, - }) - if err != nil { - return fmt.Errorf("error deleting CloudSearch domain: %s", err) - } - } - return nil - }, + F: sweepDomains, }) } + +func sweepDomains(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).CloudSearchConn + input := &cloudsearch.DescribeDomainsInput{} + sweepResources := make([]*sweep.SweepResource, 0) + + domains, err := conn.DescribeDomains(input) + + for _, domain := range domains.DomainStatusList { + if aws.BoolValue(domain.Deleted) { + continue + } + + r := ResourceDomain() + d := r.Data(nil) + d.SetId(aws.StringValue(domain.DomainName)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping CloudSearch Domain sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing CloudSearch Domains (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping CloudSearch Domains (%s): %w", region, err) + } + + return nil +} diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index 97bbdef00440..6e75d5d07639 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -13,7 +13,7 @@ Provides an CloudSearch domain resource. ## Example Usage ```terraform -resource "aws_cloudsearch_domain" "my_domain" { +resource "aws_cloudsearch_domain" "example" { name = "test-domain" instance_type = "search.medium" @@ -35,7 +35,7 @@ resource "aws_cloudsearch_domain" "my_domain" { return = true sort = true } - service_access_policies = data.aws_iam_policy_document.cloudsearch_access_policy.json + access_policies = data.aws_iam_policy_document.cloudsearch_access_policy.json } data "aws_iam_policy_document" "cloudsearch_access_policy" { @@ -56,14 +56,33 @@ data "aws_iam_policy_document" "cloudsearch_access_policy" { The following arguments are supported: +* `access_policies` - (Required) The AWS IAM access policy for the domain. See the [AWS documentation](https://docs.aws.amazon.com/cloudsearch/latest/developerguide/configuring-access.html#cloudsearch-access-policies) for more details. +* `endpoint_options` - (Optional) Domain endpoint options. Documented below. +* `multi_az` - (Optional) Whether or not to maintain extra instances for the domain in a second Availability Zone to ensure high availability. * `name` - (Required) The name of the CloudSearch domain. +* `scaling_parameters` - (Optional) Domain scaling parameters. Documented below. + * `instance_type` - (Optional) The type of instance to start. * `replication_count` - (Optional) The amount of replicas. * `partition_count` - (Optional) The amount of partitions on each instance. Currently only supported by `search.2xlarge`. * `index` - (Required) See [Indices](#indices) below for details. -* `service_access_policies` - (Required) The AWS IAM access policy. * `wait_for_endpoints` - (Optional) - Default true, wait for the search service end point. If you set this to false, the search and document endpoints won't be available to use as an attribute during the first run. +### endpoint_options + +This configuration block supports the following attributes: + +* `enforce_https` - (Optional) Enables or disables the requirement that all requests to the domain arrive over HTTPS. +* `tls_security_policy` - (Optional) The minimum required TLS version. See the [AWS documentation](https://docs.aws.amazon.com/cloudsearch/latest/developerguide/API_DomainEndpointOptions.html) for valid values. + +### scaling_parameters + +This configuration block supports the following attributes: + +* `desired_instance_type` - (Optional) The instance type that you want to preconfigure for your domain. See the [AWS documentation](https://docs.aws.amazon.com/cloudsearch/latest/developerguide/API_ScalingParameters.html) for valid values. +* `desired_partition_count` - (Optional) The number of partitions you want to preconfigure for your domain. Only valid when you select `search.2xlarge` as the instance type. +* `desired_replication_count` - (Optional) The number of replicas you want to preconfigure for each index partition. + ### Indices Each of the `index` entities represents an index field of the domain. @@ -82,6 +101,16 @@ Each of the `index` entities represents an index field of the domain. In addition to all arguments above, the following attributes are exported: +* `arn` - The domain's ARN. + * `document_endpoint` - The doc service end point - see wait_for_endpoints parameter * `search_endpoint` - The search service end point - see wait_for_endpoints parameter * `domain_id` - The domain id + +## Import + +CloudSearch Domains can be imported using the `name`, e.g., + +``` +$ terraform import aws_cloudsearch_domain.example test-domain +``` \ No newline at end of file From 570d65dafd1841dd1d7c7a241059bfdc9c66c631 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 10 Dec 2021 16:33:44 -0500 Subject: [PATCH 30/50] r/aws_cloudsearch_domain: Wait on domain creation and deletion. --- website/docs/r/cloudsearch_domain.html.markdown | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index 6e75d5d07639..4350d83ec453 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -102,10 +102,18 @@ Each of the `index` entities represents an index field of the domain. In addition to all arguments above, the following attributes are exported: * `arn` - The domain's ARN. +* `document_service_endpoint` - The service endpoint for updating documents in a search domain. +* `domain_id` - An internally generated unique identifier for the domain. +* `search_service_endpoint` - The service endpoint for requesting search results from a search domain. -* `document_endpoint` - The doc service end point - see wait_for_endpoints parameter -* `search_endpoint` - The search service end point - see wait_for_endpoints parameter -* `domain_id` - The domain id +## Timeouts + +`aws_cloudsearch_domain` provides the following +[Timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) configuration options: + +* `create` - (Default `20 minutes`) How long to wait for the CloudSearch Domain to be created. +* `update` - (Default `20 minutes`) How long to wait for the CloudSearch Domain to be updated. +* `delete` - (Default `10 minutes`) How long to wait for the CloudSearch Domain to be deleted. ## Import From a019e26a6e86aded2f13061b24d4bb3c8ea3deb3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 Dec 2021 07:49:55 -0500 Subject: [PATCH 31/50] r/aws_cloudsearch_domain: Modify 'TestAccCloudSearchDomain_basic' and add 'TestAccCloudSearchDomain_disappears'. --- internal/service/cloudsearch/domain.go | 443 +++++++++++++------- internal/service/cloudsearch/domain_test.go | 154 ++++--- 2 files changed, 403 insertions(+), 194 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index 110489716234..3131f9e15cf9 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -10,6 +10,7 @@ import ( "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" @@ -29,12 +30,18 @@ func ResourceDomain() *schema.Resource { State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + Schema: map[string]*schema.Schema{ // TODO: Separate access policy resource? // TODO: Is it Required? "access_policies": { Type: schema.TypeString, - Required: true, + Optional: true, ValidateFunc: validation.StringIsJSON, DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, StateFunc: func(v interface{}) string { @@ -46,19 +53,30 @@ func ResourceDomain() *schema.Resource { 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), }, }, @@ -67,6 +85,7 @@ func ResourceDomain() *schema.Resource { "multi_az": { Type: schema.TypeBool, Optional: true, + Computed: true, }, "name": { Type: schema.TypeString, @@ -78,24 +97,32 @@ func ResourceDomain() *schema.Resource { 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, + }, "index": { Type: schema.TypeSet, @@ -156,28 +183,10 @@ func ResourceDomain() *schema.Resource { }, }, }, - - "wait_for_endpoints": { - Type: schema.TypeBool, - Optional: true, - Default: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { return true }, - }, - - "document_endpoint": { - Type: schema.TypeString, - Computed: true, - }, - - "search_endpoint": { - Type: schema.TypeString, - Computed: true, - }, }, } } -// Terraform CRUD Functions func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudSearchConn @@ -195,15 +204,16 @@ func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) e d.SetId(name) - log.Printf("[DEBUG] Updating CloudSearch Domain (%s) access policies", name) - _, err = conn.UpdateServiceAccessPolicies(&cloudsearch.UpdateServiceAccessPoliciesInput{ - DomainName: aws.String(d.Id()), - AccessPolicies: aws.String(d.Get("access_policies").(string)), - }) + // TODO: Separate domain access policy resource? + // log.Printf("[DEBUG] Updating CloudSearch Domain (%s) access policies", name) + // _, err = conn.UpdateServiceAccessPolicies(&cloudsearch.UpdateServiceAccessPoliciesInput{ + // DomainName: aws.String(d.Id()), + // AccessPolicies: aws.String(d.Get("access_policies").(string)), + // }) - if err != nil { - return fmt.Errorf("error updating CloudSearch Domain (%s) service access policies: %w", name, err) - } + // if err != nil { + // return fmt.Errorf("error updating CloudSearch Domain (%s) service access policies: %w", d.Id(), err) + // } if v, ok := d.GetOk("scaling_parameters"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input := &cloudsearch.UpdateScalingParametersInput{ @@ -215,7 +225,7 @@ func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) e _, err := conn.UpdateScalingParameters(input) if err != nil { - return fmt.Errorf("error updating CloudSearch Domain (%s) scaling parameters: %w", name, err) + return fmt.Errorf("error updating CloudSearch Domain (%s) scaling parameters: %w", d.Id(), err) } } @@ -229,11 +239,11 @@ func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) e _, err := conn.UpdateAvailabilityOptions(input) if err != nil { - return fmt.Errorf("error updating CloudSearch Domain (%s) availability options: %w", name, err) + return fmt.Errorf("error updating CloudSearch Domain (%s) availability options: %w", d.Id(), err) } } - if v, ok := d.GetOk("scaling_parameters"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + 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()), @@ -243,36 +253,106 @@ func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) e _, err := conn.UpdateDomainEndpointOptions(input) if err != nil { - return fmt.Errorf("error updating CloudSearch Domain (%s) endpoint options: %w", name, err) + return fmt.Errorf("error updating CloudSearch Domain (%s) endpoint options: %w", d.Id(), err) } } - return resourceCloudSearchDomainUpdate(d, meta) + if v, ok := d.GetOk("index"); ok && v.(*schema.Set).Len() > 0 { + for _, v := range v.(*schema.Set).List() { + field, err := generateIndexFieldInput(v.(map[string]interface{})) + + if err != nil { + return err + } + + _, err = conn.DefineIndexField(&cloudsearch.DefineIndexFieldInput{ + DomainName: aws.String(d.Id()), + IndexField: field, + }) + + if err != nil { + return fmt.Errorf("error defining CloudSearch Domain (%s) index field (%s): %w", d.Id(), aws.StringValue(field.IndexFieldName), 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) + } + } + + _, 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 - domainlist := cloudsearch.DescribeDomainsInput{ - DomainNames: []*string{ - aws.String(d.Get("name").(string)), - }, + 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 } - resp, err := conn.DescribeDomains(&domainlist) if err != nil { - return err + return fmt.Errorf("error reading CloudSearch Domain (%s): %w", d.Id(), err) } - domain := resp.DomainStatusList[0] - d.Set("id", domain.DomainId) - if domain.DocService.Endpoint != nil { - d.Set("document_endpoint", domain.DocService.Endpoint) + 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 domain.SearchService.Endpoint != nil { - d.Set("search_endpoint", domain.SearchService.Endpoint) + + if err := d.Set("scaling_parameters", []interface{}{flattenScalingParameters(scalingParameters)}); err != nil { + return fmt.Errorf("error setting scaling_parameters: %w", err) } + // TODO... + // Read index fields. indexResults, err := conn.DescribeIndexFields(&cloudsearch.DescribeIndexFieldsInput{ DomainName: aws.String(d.Get("name").(string)), @@ -294,33 +374,13 @@ func resourceCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) err d.Set("index", result) // Read service access policies. - policyResult, err := conn.DescribeServiceAccessPolicies(&cloudsearch.DescribeServiceAccessPoliciesInput{ - DomainName: aws.String(d.Get("name").(string)), - }) - if err != nil { - return err - } - d.Set("service_access_policies", policyResult.AccessPolicies.Options) - - // Read availability options (i.e. multi-az). - availabilityResult, err := conn.DescribeAvailabilityOptions(&cloudsearch.DescribeAvailabilityOptionsInput{ - DomainName: aws.String(d.Get("name").(string)), - }) - if err != nil { - return err - } - d.Set("multi_az", availabilityResult.AvailabilityOptions.Options) - - // Read scaling parameters. - scalingResult, err := conn.DescribeScalingParameters(&cloudsearch.DescribeScalingParametersInput{ - DomainName: aws.String(d.Get("name").(string)), - }) - if err != nil { - return err - } - d.Set("instance_type", scalingResult.ScalingParameters.Options.DesiredInstanceType) - d.Set("partition_count", scalingResult.ScalingParameters.Options.DesiredPartitionCount) - d.Set("replication_count", scalingResult.ScalingParameters.Options.DesiredReplicationCount) + // policyResult, err := conn.DescribeServiceAccessPolicies(&cloudsearch.DescribeServiceAccessPoliciesInput{ + // DomainName: aws.String(d.Get("name").(string)), + // }) + // if err != nil { + // return err + // } + // d.Set("service_access_policies", policyResult.AccessPolicies.Options) return err } @@ -373,18 +433,6 @@ func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) e } } - if d.Get("wait_for_endpoints").(bool) { - domainsList := cloudsearch.DescribeDomainsInput{ - DomainNames: []*string{ - aws.String(d.Get("name").(string)), - }, - } - - err = waitForSearchDomainToBeAvailable(d, conn, domainsList) - if err != nil { - return fmt.Errorf("%s %q", err, d.Get("name").(string)) - } - } return nil } @@ -401,6 +449,12 @@ func resourceCloudSearchDomainDelete(d *schema.ResourceData, meta interface{}) e 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 } @@ -419,58 +473,6 @@ func validateIndexName(v interface{}, k string) (ws []string, es []error) { return } -// Waiters -func waitForSearchDomainToBeAvailable(d *schema.ResourceData, conn *cloudsearch.CloudSearch, domainlist cloudsearch.DescribeDomainsInput) error { - log.Printf("[INFO] cloudsearch (%#v) waiting for domain endpoint. This usually takes 10 minutes.", domainlist.DomainNames[0]) - stateConf := &resource.StateChangeConf{ - Pending: []string{"Waiting"}, - Target: []string{"OK"}, - Timeout: 30 * time.Minute, - MinTimeout: 5 * time.Second, - Refresh: func() (interface{}, string, error) { - resp, err := conn.DescribeDomains(&domainlist) - log.Printf("[DEBUG] Checking %v", domainlist.DomainNames[0]) - if err != nil { - log.Printf("[ERROR] Could not find domain (%v). %s", domainlist.DomainNames[0], err) - return nil, "", err - } - // Not good enough to wait for processing, have to check for search endpoint. - domain := resp.DomainStatusList[0] - log.Printf("[DEBUG] GLEN: Domain = %s", domain) - processing := strconv.FormatBool(*domain.Processing) - log.Printf("[DEBUG] GLEN: Processing = %s", processing) - if domain.SearchService.Endpoint != nil { - log.Printf("[DEBUG] GLEN: type: %T", domain.SearchService.Endpoint) - log.Printf("[DEBUG] GLEN: SearchServiceEndpoint = %s", *domain.SearchService.Endpoint) - } - if aws.StringValue(domain.SearchService.Endpoint) == "" { - return resp, "Waiting", nil - } - return resp, "OK", nil - - }, - } - - log.Printf("[DEBUG] Waiting for CloudSearch domain to finish processing: %v", domainlist.DomainNames[0]) - _, err := stateConf.WaitForState() - - // Search service was blank. - resp, err1 := conn.DescribeDomains(&domainlist) - if err1 != nil { - return err1 - } - - domain := resp.DomainStatusList[0] - d.Set("id", domain.DomainId) - d.Set("document_endpoint", domain.DocService.Endpoint) - d.Set("search_endpoint", domain.SearchService.Endpoint) - - if err != nil { - return fmt.Errorf("Error waiting for CloudSearch domain (%#v) to finish processing: %s", domainlist.DomainNames[0], err) - } - return err -} - // Miscellaneous helper functions func defineIndexFields(d *schema.ResourceData, conn *cloudsearch.CloudSearch) (bool, error) { // Early return if we don't have a change. @@ -869,7 +871,7 @@ func readIndexField(raw *cloudsearch.IndexField) map[string]interface{} { return index } -func FindDomainByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.DomainStatus, error) { +func FindDomainStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.DomainStatus, error) { input := &cloudsearch.DescribeDomainsInput{ DomainNames: aws.StringSlice([]string{name}), } @@ -888,16 +890,177 @@ func FindDomainByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch. return nil, tfresource.NewTooManyResultsError(count, input) } - domainStatus := output.DomainStatusList[0] + return output.DomainStatusList[0], nil +} + +func findAvailabilityOptionsStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.AvailabilityOptionsStatus, error) { + input := &cloudsearch.DescribeAvailabilityOptionsInput{ + Deployed: aws.Bool(true), + DomainName: aws.String(name), + } + + output, err := conn.DescribeAvailabilityOptions(input) - if aws.BoolValue(domainStatus.Deleted) { + if tfawserr.ErrCodeEquals(err, cloudsearch.ErrCodeResourceNotFoundException) { return nil, &resource.NotFoundError{ - Message: "deleted", + LastError: err, LastRequest: input, } } - return domainStatus, nil + 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) { + 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 { diff --git a/internal/service/cloudsearch/domain_test.go b/internal/service/cloudsearch/domain_test.go index c38167aa1cd1..c7de3fecee62 100644 --- a/internal/service/cloudsearch/domain_test.go +++ b/internal/service/cloudsearch/domain_test.go @@ -2,21 +2,20 @@ package cloudsearch_test import ( "fmt" - "strconv" "testing" - "time" - "github.com/aws/aws-sdk-go/aws" "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 TestAccCloudSearchDomain_basic(t *testing.T) { - var domains cloudsearch.DescribeDomainsOutput + var v cloudsearch.DomainStatus resourceName := "aws_cloudsearch_domain.test" rName := acctest.ResourcePrefix + "-" + sdkacctest.RandString(28-(len(acctest.ResourcePrefix)+1)) @@ -27,9 +26,72 @@ func TestAccCloudSearchDomain_basic(t *testing.T) { CheckDestroy: testAccCloudSearchDomainDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudSearchDomainConfig_basic(rName), + Config: testAccCloudSearchDomainConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCloudSearchDomainExists(resourceName, &v), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "cloudsearch", fmt.Sprintf("domain/%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "document_service_endpoint"), + resource.TestCheckResourceAttrSet(resourceName, "domain_id"), + resource.TestCheckResourceAttr(resourceName, "endpoint_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "endpoint_options.0.enforce_https", "false"), + resource.TestCheckResourceAttrSet(resourceName, "endpoint_options.0.tls_security_policy"), + resource.TestCheckResourceAttr(resourceName, "index.#", "0"), + resource.TestCheckResourceAttr(resourceName, "multi_az", "false"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.#", "1"), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_instance_type", ""), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_partition_count", "0"), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_replication_count", "0"), + resource.TestCheckResourceAttrSet(resourceName, "search_service_endpoint"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCloudSearchDomain_disappears(t *testing.T) { + var v cloudsearch.DomainStatus + resourceName := "aws_cloudsearch_domain.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: testAccCloudSearchDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudSearchDomainConfig(rName), Check: resource.ComposeTestCheckFunc( - testAccCloudSearchDomainExists(resourceName, &domains), + testAccCloudSearchDomainExists(resourceName, &v), + acctest.CheckResourceDisappears(acctest.Provider, tfcloudsearch.ResourceDomain(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccCloudSearchDomain_simple(t *testing.T) { + var v cloudsearch.DomainStatus + resourceName := "aws_cloudsearch_domain.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: testAccCloudSearchDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudSearchDomainConfig_simple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCloudSearchDomainExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, @@ -41,7 +103,7 @@ func TestAccCloudSearchDomain_basic(t *testing.T) { { Config: testAccCloudSearchDomainConfig_basicIndexMix(rName), Check: resource.ComposeTestCheckFunc( - testAccCloudSearchDomainExists(resourceName, &domains), + testAccCloudSearchDomainExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, @@ -55,7 +117,7 @@ func TestAccCloudSearchDomain_basic(t *testing.T) { } func TestAccCloudSearchDomain_textAnalysisScheme(t *testing.T) { - var domains cloudsearch.DescribeDomainsOutput + var v cloudsearch.DomainStatus resourceName := "aws_cloudsearch_domain.test" rName := acctest.ResourcePrefix + "-" + sdkacctest.RandString(28-(len(acctest.ResourcePrefix)+1)) @@ -68,14 +130,14 @@ func TestAccCloudSearchDomain_textAnalysisScheme(t *testing.T) { { Config: testAccCloudSearchDomainConfig_textAnalysisScheme(rName, "_en_default_"), Check: resource.ComposeTestCheckFunc( - testAccCloudSearchDomainExists(resourceName, &domains), + testAccCloudSearchDomainExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", acctest.RandomFQDomainName()), ), }, { Config: testAccCloudSearchDomainConfig_textAnalysisScheme(rName, "_fr_default_"), Check: resource.ComposeTestCheckFunc( - testAccCloudSearchDomainExists(resourceName, &domains), + testAccCloudSearchDomainExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, @@ -88,80 +150,64 @@ func TestAccCloudSearchDomain_textAnalysisScheme(t *testing.T) { }) } -func testAccCloudSearchDomainExists(n string, domains *cloudsearch.DescribeDomainsOutput) resource.TestCheckFunc { +func testAccCloudSearchDomainExists(n string, v *cloudsearch.DomainStatus) 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 ID is set") + } + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudSearchConn - domainList := cloudsearch.DescribeDomainsInput{ - DomainNames: []*string{ - aws.String(rs.Primary.Attributes["name"]), - }, - } + output, err := tfcloudsearch.FindDomainStatusByName(conn, rs.Primary.ID) - resp, err := conn.DescribeDomains(&domainList) if err != nil { return err } - *domains = *resp + *v = *output return nil } } func testAccCloudSearchDomainDestroy(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudSearchConn - for _, rs := range s.RootModule().Resources { if rs.Type != "aws_cloudsearch_domain" { continue } - // Wait for the resource to start being deleted, which is marked as "Deleted" from the API. - stateConf := &resource.StateChangeConf{ - Pending: []string{"false"}, - Target: []string{"true"}, - Timeout: 20 * time.Minute, - NotFoundChecks: 100, - Refresh: func() (interface{}, string, error) { - domainlist := cloudsearch.DescribeDomainsInput{ - DomainNames: []*string{ - aws.String(rs.Primary.Attributes["name"]), - }, - } - - resp, err := conn.DescribeDomains(&domainlist) - if err != nil { - return nil, "false", err - } - - domain := resp.DomainStatusList[0] - - // If we see that the domain has been deleted, go ahead and return true. - if *domain.Deleted { - return domain, "true", nil - } - - if domain.Deleted != nil { - return nil, strconv.FormatBool(*domain.Deleted), nil - } - - return nil, "false", nil - }, + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudSearchConn + + _, err := tfcloudsearch.FindDomainStatusByName(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err } - _, err := stateConf.WaitForState() - return err + + return fmt.Errorf("CloudSearch Domain %s still exists", rs.Primary.ID) } return nil } -func testAccCloudSearchDomainConfig_basic(name string) string { +func testAccCloudSearchDomainConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_cloudsearch_domain" "test" { + name = %[1]q +} +`, rName) +} + +func testAccCloudSearchDomainConfig_simple(name string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { name = "%s" From 9c3227b6ae63520a72e80e450574555d0bc70f95 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 Dec 2021 14:59:47 -0500 Subject: [PATCH 32/50] r/aws_cloudsearch_domain_service_access_policy: New resource. --- .changelog/17723.txt | 4 + internal/provider/provider.go | 1 + internal/service/cloudsearch/domain.go | 44 +--- .../domain_service_access_policy.go | 217 ++++++++++++++++++ .../domain_service_access_policy_test.go | 1 + ...domain_service_access_policy.html.markdown | 55 +++++ 6 files changed, 279 insertions(+), 43 deletions(-) create mode 100644 internal/service/cloudsearch/domain_service_access_policy.go create mode 100644 internal/service/cloudsearch/domain_service_access_policy_test.go create mode 100644 website/docs/r/cloudsearch_domain_service_access_policy.html.markdown diff --git a/.changelog/17723.txt b/.changelog/17723.txt index bea8120c0cb7..6f0e5f6d2373 100644 --- a/.changelog/17723.txt +++ b/.changelog/17723.txt @@ -1,3 +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 e831e9cf690f..b676894af526 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -883,6 +883,7 @@ func Provider() *schema.Provider { "aws_cloudhsm_v2_hsm": cloudhsmv2.ResourceHSM(), "aws_cloudsearch_domain": cloudsearch.ResourceDomain(), + "aws_cloudsearch_domain_service_access_policy": cloudsearch.ResourceDomainServiceAccessPolicy(), "aws_cloudtrail": cloudtrail.ResourceCloudTrail(), diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index 3131f9e15cf9..d36819c4fa49 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -13,11 +13,9 @@ import ( "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 ResourceDomain() *schema.Resource { @@ -37,18 +35,6 @@ func ResourceDomain() *schema.Resource { }, Schema: map[string]*schema.Schema{ - // TODO: Separate access policy resource? - // TODO: Is it Required? - "access_policies": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, - StateFunc: func(v interface{}) string { - json, _ := structure.NormalizeJsonString(v) - return json - }, - }, "arn": { Type: schema.TypeString, Computed: true, @@ -204,17 +190,6 @@ func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) e d.SetId(name) - // TODO: Separate domain access policy resource? - // log.Printf("[DEBUG] Updating CloudSearch Domain (%s) access policies", name) - // _, err = conn.UpdateServiceAccessPolicies(&cloudsearch.UpdateServiceAccessPoliciesInput{ - // DomainName: aws.String(d.Id()), - // AccessPolicies: aws.String(d.Get("access_policies").(string)), - // }) - - // if err != nil { - // return fmt.Errorf("error updating CloudSearch Domain (%s) service access policies: %w", d.Id(), err) - // } - if v, ok := d.GetOk("scaling_parameters"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { input := &cloudsearch.UpdateScalingParametersInput{ DomainName: aws.String(d.Id()), @@ -373,30 +348,13 @@ func resourceCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) err } d.Set("index", result) - // Read service access policies. - // policyResult, err := conn.DescribeServiceAccessPolicies(&cloudsearch.DescribeServiceAccessPoliciesInput{ - // DomainName: aws.String(d.Get("name").(string)), - // }) - // if err != nil { - // return err - // } - // d.Set("service_access_policies", policyResult.AccessPolicies.Options) - return err } func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudSearchConn - _, err := conn.UpdateServiceAccessPolicies(&cloudsearch.UpdateServiceAccessPoliciesInput{ - DomainName: aws.String(d.Get("name").(string)), - AccessPolicies: aws.String(d.Get("service_access_policies").(string)), - }) - if err != nil { - return err - } - - _, err = conn.UpdateScalingParameters(&cloudsearch.UpdateScalingParametersInput{ + _, err := conn.UpdateScalingParameters(&cloudsearch.UpdateScalingParametersInput{ DomainName: aws.String(d.Get("name").(string)), ScalingParameters: &cloudsearch.ScalingParameters{ DesiredInstanceType: aws.String(d.Get("instance_type").(string)), 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..19ba4e868de1 --- /dev/null +++ b/internal/service/cloudsearch/domain_service_access_policy.go @@ -0,0 +1,217 @@ +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, + }, + + 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 creating CloudSearch Domain Service Access Policy (%s): %w", domainName, err) + } + + d.SetId(domainName) + + _, err = waitAccessPolicyActive(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error waiting for CloudSearch Domain Service Access Policy (%s) to become active: %w", d.Id(), err) + } + + return nil +} + +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 + + domainName := d.Get("domain_name").(string) + 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()) + + 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{ + Deployed: aws.Bool(true), + 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 + } +} + +const ( + accessPolicyTimeout = 2 * time.Minute +) + +func waitAccessPolicyActive(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.AccessPoliciesStatus, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{cloudsearch.OptionStateProcessing}, + Target: []string{cloudsearch.OptionStateActive}, + Refresh: statusAccessPolicyState(conn, name), + Timeout: accessPolicyTimeout, + } + + 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..35eb54b18733 --- /dev/null +++ b/internal/service/cloudsearch/domain_service_access_policy_test.go @@ -0,0 +1 @@ +package cloudsearch_test diff --git a/website/docs/r/cloudsearch_domain_service_access_policy.html.markdown b/website/docs/r/cloudsearch_domain_service_access_policy.html.markdown new file mode 100644 index 000000000000..6568648f4fa6 --- /dev/null +++ b/website/docs/r/cloudsearch_domain_service_access_policy.html.markdown @@ -0,0 +1,55 @@ +--- +subcategory: "CloudSearch" +layout: "aws" +page_title: "AWS: aws_cloudsearch_domain_service_access_policy" +description: |- + Provides an CloudSearch domain service access policy resource. +--- + +# Resource: aws_cloudsearch_domain_service_access_policy + +Provides an CloudSearch domain service access policy resource. + +## Example Usage + +```terraform +resource "aws_cloudsearch_domain" "example" { + name = "example-domain" +} + +resource "aws_cloudsearch_domain_service_access_policy" "example" { + domain_name = aws_cloudsearch_domain.example.id + + policy = < Date: Mon, 13 Dec 2021 15:00:52 -0500 Subject: [PATCH 33/50] go fmt. --- internal/provider/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b676894af526..f74edaf2c507 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -882,7 +882,7 @@ func Provider() *schema.Provider { "aws_cloudhsm_v2_cluster": cloudhsmv2.ResourceCluster(), "aws_cloudhsm_v2_hsm": cloudhsmv2.ResourceHSM(), - "aws_cloudsearch_domain": cloudsearch.ResourceDomain(), + "aws_cloudsearch_domain": cloudsearch.ResourceDomain(), "aws_cloudsearch_domain_service_access_policy": cloudsearch.ResourceDomainServiceAccessPolicy(), "aws_cloudtrail": cloudtrail.ResourceCloudTrail(), From 9512c35b1c365a6131fd8d90f50480498ef904c9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 Dec 2021 15:02:44 -0500 Subject: [PATCH 34/50] Fix 'domainName declared but not used'. --- internal/service/cloudsearch/domain_service_access_policy.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/cloudsearch/domain_service_access_policy.go b/internal/service/cloudsearch/domain_service_access_policy.go index 19ba4e868de1..f935d5bb3ac1 100644 --- a/internal/service/cloudsearch/domain_service_access_policy.go +++ b/internal/service/cloudsearch/domain_service_access_policy.go @@ -111,7 +111,6 @@ func resourceDomainServiceAccessPolicyRead(d *schema.ResourceData, meta interfac func resourceDomainServiceAccessPolicyDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudSearchConn - domainName := d.Get("domain_name").(string) input := &cloudsearch.UpdateServiceAccessPoliciesInput{ AccessPolicies: aws.String(""), DomainName: aws.String(d.Id()), From 317eb1669a7dbcd76aeeddb26fb0b65e7032c76c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 Dec 2021 15:06:38 -0500 Subject: [PATCH 35/50] Remove 'access_policies' from CloudSearch domain resource documentation. --- .../docs/r/cloudsearch_domain.html.markdown | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index 4350d83ec453..05d5a29fbc5a 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -14,7 +14,7 @@ Provides an CloudSearch domain resource. ```terraform resource "aws_cloudsearch_domain" "example" { - name = "test-domain" + name = "example-domain" instance_type = "search.medium" index { @@ -35,20 +35,6 @@ resource "aws_cloudsearch_domain" "example" { return = true sort = true } - access_policies = data.aws_iam_policy_document.cloudsearch_access_policy.json -} - -data "aws_iam_policy_document" "cloudsearch_access_policy" { - statement { - principals { - type = "AWS" - identifiers = ["*"] - } - actions = [ - "cloudsearch:search", - "cloudsearch:suggest" - ] - } } ``` @@ -56,7 +42,6 @@ data "aws_iam_policy_document" "cloudsearch_access_policy" { The following arguments are supported: -* `access_policies` - (Required) The AWS IAM access policy for the domain. See the [AWS documentation](https://docs.aws.amazon.com/cloudsearch/latest/developerguide/configuring-access.html#cloudsearch-access-policies) for more details. * `endpoint_options` - (Optional) Domain endpoint options. Documented below. * `multi_az` - (Optional) Whether or not to maintain extra instances for the domain in a second Availability Zone to ensure high availability. * `name` - (Required) The name of the CloudSearch domain. @@ -66,7 +51,6 @@ The following arguments are supported: * `replication_count` - (Optional) The amount of replicas. * `partition_count` - (Optional) The amount of partitions on each instance. Currently only supported by `search.2xlarge`. * `index` - (Required) See [Indices](#indices) below for details. -* `wait_for_endpoints` - (Optional) - Default true, wait for the search service end point. If you set this to false, the search and document endpoints won't be available to use as an attribute during the first run. ### endpoint_options @@ -120,5 +104,5 @@ In addition to all arguments above, the following attributes are exported: CloudSearch Domains can be imported using the `name`, e.g., ``` -$ terraform import aws_cloudsearch_domain.example test-domain +$ terraform import aws_cloudsearch_domain.example example-domain ``` \ No newline at end of file From ac76fbe5566b686de2316e27f05a8caad121c976 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 Dec 2021 15:16:43 -0500 Subject: [PATCH 36/50] Add 'TestAccCloudSearchDomainServiceAccessPolicy_basic'. --- .../domain_service_access_policy_test.go | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/internal/service/cloudsearch/domain_service_access_policy_test.go b/internal/service/cloudsearch/domain_service_access_policy_test.go index 35eb54b18733..3774e21ba8af 100644 --- a/internal/service/cloudsearch/domain_service_access_policy_test.go +++ b/internal/service/cloudsearch/domain_service_access_policy_test.go @@ -1 +1,111 @@ 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.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: testAccCloudSearchDomainConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCloudSearchDomainServiceAccessPolicyExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "access_policy"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +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 + + policy = < Date: Tue, 14 Dec 2021 07:51:02 -0500 Subject: [PATCH 37/50] r/aws_cloudsearch_domain_service_access_policy: Acceptance test access policy corrections. --- .../domain_service_access_policy.go | 2 +- .../domain_service_access_policy_test.go | 18 ++++++++++-------- ..._domain_service_access_policy.html.markdown | 18 +++++++++--------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/internal/service/cloudsearch/domain_service_access_policy.go b/internal/service/cloudsearch/domain_service_access_policy.go index f935d5bb3ac1..0abe5a72ee78 100644 --- a/internal/service/cloudsearch/domain_service_access_policy.go +++ b/internal/service/cloudsearch/domain_service_access_policy.go @@ -78,7 +78,7 @@ func resourceDomainServiceAccessPolicyPut(d *schema.ResourceData, meta interface return fmt.Errorf("error waiting for CloudSearch Domain Service Access Policy (%s) to become active: %w", d.Id(), err) } - return nil + return resourceDomainServiceAccessPolicyRead(d, meta) } func resourceDomainServiceAccessPolicyRead(d *schema.ResourceData, meta interface{}) error { diff --git a/internal/service/cloudsearch/domain_service_access_policy_test.go b/internal/service/cloudsearch/domain_service_access_policy_test.go index 3774e21ba8af..6cf5fe0bfdbb 100644 --- a/internal/service/cloudsearch/domain_service_access_policy_test.go +++ b/internal/service/cloudsearch/domain_service_access_policy_test.go @@ -15,7 +15,7 @@ import ( ) func TestAccCloudSearchDomainServiceAccessPolicy_basic(t *testing.T) { - resourceName := "aws_cloudsearch_domain.test" + resourceName := "aws_cloudsearch_domain_service_access_policy.test" rName := acctest.ResourcePrefix + "-" + sdkacctest.RandString(28-(len(acctest.ResourcePrefix)+1)) resource.ParallelTest(t, resource.TestCase{ @@ -25,7 +25,7 @@ func TestAccCloudSearchDomainServiceAccessPolicy_basic(t *testing.T) { CheckDestroy: testAccCloudSearchDomainServiceAccessPolicyDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudSearchDomainConfig(rName), + Config: testAccDomainServiceAccessPolicyConfig(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCloudSearchDomainServiceAccessPolicyExists(resourceName), resource.TestCheckResourceAttrSet(resourceName, "access_policy"), @@ -96,13 +96,15 @@ resource "aws_cloudsearch_domain" "test" { resource "aws_cloudsearch_domain_service_access_policy" "test" { domain_name = aws_cloudsearch_domain.test.id - policy = < Date: Tue, 14 Dec 2021 18:38:58 -0500 Subject: [PATCH 38/50] r/aws_cloudsearch_domain_service_access_policy: Increase timeouts (and make configurable) to reach Active state. Acceptance test output: % make testacc TESTARGS='-run=TestAccCloudSearchDomainServiceAccessPolicy_' PKG=cloudsearch ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/cloudsearch/... -v -count 1 -parallel 20 -run=TestAccCloudSearchDomainServiceAccessPolicy_ -timeout 180m === RUN TestAccCloudSearchDomainServiceAccessPolicy_basic === PAUSE TestAccCloudSearchDomainServiceAccessPolicy_basic === RUN TestAccCloudSearchDomainServiceAccessPolicy_update === PAUSE TestAccCloudSearchDomainServiceAccessPolicy_update === CONT TestAccCloudSearchDomainServiceAccessPolicy_basic === CONT TestAccCloudSearchDomainServiceAccessPolicy_update --- PASS: TestAccCloudSearchDomainServiceAccessPolicy_basic (1613.17s) --- PASS: TestAccCloudSearchDomainServiceAccessPolicy_update (2419.09s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/cloudsearch 2422.751s --- internal/service/cloudsearch/domain.go | 3 +- .../domain_service_access_policy.go | 20 +++--- .../domain_service_access_policy_test.go | 64 ++++++++++++++++++- .../docs/r/cloudsearch_domain.html.markdown | 8 ++- ...domain_service_access_policy.html.markdown | 15 ++++- 5 files changed, 92 insertions(+), 18 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index d36819c4fa49..ddcfb20c87ec 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -31,7 +31,7 @@ func ResourceDomain() *schema.Resource { Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(20 * time.Minute), Update: schema.DefaultTimeout(20 * time.Minute), - Delete: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), }, Schema: map[string]*schema.Schema{ @@ -853,7 +853,6 @@ func FindDomainStatusByName(conn *cloudsearch.CloudSearch, name string) (*clouds func findAvailabilityOptionsStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.AvailabilityOptionsStatus, error) { input := &cloudsearch.DescribeAvailabilityOptionsInput{ - Deployed: aws.Bool(true), DomainName: aws.String(name), } diff --git a/internal/service/cloudsearch/domain_service_access_policy.go b/internal/service/cloudsearch/domain_service_access_policy.go index 0abe5a72ee78..9e6e9aec1784 100644 --- a/internal/service/cloudsearch/domain_service_access_policy.go +++ b/internal/service/cloudsearch/domain_service_access_policy.go @@ -27,6 +27,11 @@ func ResourceDomainServiceAccessPolicy() *schema.Resource { 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, @@ -67,12 +72,12 @@ func resourceDomainServiceAccessPolicyPut(d *schema.ResourceData, meta interface _, err = conn.UpdateServiceAccessPolicies(input) if err != nil { - return fmt.Errorf("error creating CloudSearch Domain Service Access Policy (%s): %w", domainName, err) + return fmt.Errorf("error updating CloudSearch Domain Service Access Policy (%s): %w", domainName, err) } d.SetId(domainName) - _, err = waitAccessPolicyActive(conn, d.Id()) + _, 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) @@ -127,7 +132,7 @@ func resourceDomainServiceAccessPolicyDelete(d *schema.ResourceData, meta interf return fmt.Errorf("error deleting CloudSearch Domain Service Access Policy (%s): %w", d.Id(), err) } - _, err = waitAccessPolicyActive(conn, d.Id()) + _, 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) @@ -154,7 +159,6 @@ func FindAccessPolicyByName(conn *cloudsearch.CloudSearch, name string) (string, func findAccessPoliciesStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.AccessPoliciesStatus, error) { input := &cloudsearch.DescribeServiceAccessPoliciesInput{ - Deployed: aws.Bool(true), DomainName: aws.String(name), } @@ -194,16 +198,12 @@ func statusAccessPolicyState(conn *cloudsearch.CloudSearch, name string) resourc } } -const ( - accessPolicyTimeout = 2 * time.Minute -) - -func waitAccessPolicyActive(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.AccessPoliciesStatus, error) { +func waitAccessPolicyActive(conn *cloudsearch.CloudSearch, name string, timeout time.Duration) (*cloudsearch.AccessPoliciesStatus, error) { stateConf := &resource.StateChangeConf{ Pending: []string{cloudsearch.OptionStateProcessing}, Target: []string{cloudsearch.OptionStateActive}, Refresh: statusAccessPolicyState(conn, name), - Timeout: accessPolicyTimeout, + Timeout: timeout, } outputRaw, err := stateConf.WaitForState() diff --git a/internal/service/cloudsearch/domain_service_access_policy_test.go b/internal/service/cloudsearch/domain_service_access_policy_test.go index 6cf5fe0bfdbb..2f5994cba6a8 100644 --- a/internal/service/cloudsearch/domain_service_access_policy_test.go +++ b/internal/service/cloudsearch/domain_service_access_policy_test.go @@ -40,6 +40,39 @@ func TestAccCloudSearchDomainServiceAccessPolicy_basic(t *testing.T) { }) } +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] @@ -100,10 +133,13 @@ resource "aws_cloudsearch_domain_service_access_policy" "test" { { "Version":"2012-10-17", "Statement":[{ - "Sid":"search_only", + "Sid":"search_and_document", "Effect":"Allow", "Principal":"*", - "Action":["cloudsearch:search"], + "Action":[ + "cloudsearch:search", + "cloudsearch:document" + ], "Condition":{"IpAddress":{"aws:SourceIp":"192.0.2.0/32"}} }] } @@ -111,3 +147,27 @@ POLICY } `, rName) } + +func testAccDomainServiceAccessPolicyConfigUpdated(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 = < Date: Thu, 16 Dec 2021 16:52:31 -0500 Subject: [PATCH 39/50] Tidy up 'resourceCloudSearchDomainUpdate'. --- internal/service/cloudsearch/domain.go | 97 +++++++++++++++++++------- 1 file changed, 73 insertions(+), 24 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index ddcfb20c87ec..6869482e2c45 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -353,45 +353,94 @@ func resourceCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) err func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudSearchConn + requiresIndexDocuments := false - _, err := conn.UpdateScalingParameters(&cloudsearch.UpdateScalingParametersInput{ - DomainName: aws.String(d.Get("name").(string)), - ScalingParameters: &cloudsearch.ScalingParameters{ - DesiredInstanceType: aws.String(d.Get("instance_type").(string)), - DesiredReplicationCount: aws.Int64(int64(d.Get("replication_count").(int))), - DesiredPartitionCount: aws.Int64(int64(d.Get("partition_count").(int))), - }, - }) - if err != nil { - return err + 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 + } } - _, err = conn.UpdateAvailabilityOptions(&cloudsearch.UpdateAvailabilityOptionsInput{ - DomainName: aws.String(d.Get("name").(string)), - MultiAZ: aws.Bool(d.Get("multi_az").(bool)), - }) - if err != nil { - return err + 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 + } } - updated, err := defineIndexFields(d, conn) + 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 + } + } + + _, err := defineIndexFields(d, conn) if err != nil { return err } - // When you add fields or modify existing fields, you must explicitly issue a request to re-index your data - // when you are done making configuration changes. - // https://docs.aws.amazon.com/cloudsearch/latest/developerguide/configuring-index-fields.html - if updated { + if requiresIndexDocuments { _, err := conn.IndexDocuments(&cloudsearch.IndexDocumentsInput{ - DomainName: aws.String(d.Get("name").(string)), + DomainName: aws.String(d.Id()), }) + if err != nil { - return err + return fmt.Errorf("error indexing CloudSearch Domain (%s) documents: %w", d.Id(), err) } } - return nil + _, 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 { From b96d83b62e975d82a1da4137e1f8c16dff7330e4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 17 Dec 2021 08:50:11 -0500 Subject: [PATCH 40/50] DocService and SearchService are often not set immediately after the domain has been created. --- internal/service/cloudsearch/domain_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/service/cloudsearch/domain_test.go b/internal/service/cloudsearch/domain_test.go index c7de3fecee62..ec25b04262f7 100644 --- a/internal/service/cloudsearch/domain_test.go +++ b/internal/service/cloudsearch/domain_test.go @@ -30,7 +30,6 @@ func TestAccCloudSearchDomain_basic(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCloudSearchDomainExists(resourceName, &v), acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "cloudsearch", fmt.Sprintf("domain/%s", rName)), - resource.TestCheckResourceAttrSet(resourceName, "document_service_endpoint"), resource.TestCheckResourceAttrSet(resourceName, "domain_id"), resource.TestCheckResourceAttr(resourceName, "endpoint_options.#", "1"), resource.TestCheckResourceAttr(resourceName, "endpoint_options.0.enforce_https", "false"), @@ -42,7 +41,6 @@ func TestAccCloudSearchDomain_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_instance_type", ""), resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_partition_count", "0"), resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_replication_count", "0"), - resource.TestCheckResourceAttrSet(resourceName, "search_service_endpoint"), ), }, { From 930d392512988d562603bb38c87f400d8de097d1 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 17 Dec 2021 09:29:56 -0500 Subject: [PATCH 41/50] Increase default Create and Update timeouts to account for re-indexing. --- internal/service/cloudsearch/domain.go | 6 ++++-- website/docs/r/cloudsearch_domain.html.markdown | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index 6869482e2c45..dda425351a17 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -29,8 +29,8 @@ func ResourceDomain() *schema.Resource { }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(20 * time.Minute), - Update: schema.DefaultTimeout(20 * time.Minute), + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), Delete: schema.DefaultTimeout(20 * time.Minute), }, @@ -259,6 +259,8 @@ func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) e } } + // TODO: Status.RequiresIndexDocuments = true? + _, err = waitDomainActive(conn, d.Id(), d.Timeout(schema.TimeoutCreate)) if err != nil { diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index fed92f5674ff..0e8d0908c60e 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -97,8 +97,8 @@ In addition to all arguments above, the following attributes are exported: `aws_cloudsearch_domain` provides the following [Timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) configuration options: -* `create` - (Default `20 minutes`) How long to wait for the CloudSearch domain to be created. -* `update` - (Default `20 minutes`) How long to wait for the CloudSearch domain to be updated. +* `create` - (Default `30 minutes`) How long to wait for the CloudSearch domain to be created. +* `update` - (Default `30 minutes`) How long to wait for the CloudSearch domain to be updated. * `delete` - (Default `20 minutes`) How long to wait for the CloudSearch domain to be deleted. ## Import From 2d03068cabfbbf1904df1adf5f81349de7887919 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 18 Jan 2022 16:40:48 -0500 Subject: [PATCH 42/50] r/aws_cloudsearch_domain: Add 'expandIndexField'. --- internal/service/cloudsearch/domain.go | 337 ++++++++++++++++++++----- 1 file changed, 272 insertions(+), 65 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index dda425351a17..888ca92457ba 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -68,6 +68,53 @@ func ResourceDomain() *schema.Resource { }, }, }, + // 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, @@ -109,66 +156,6 @@ func ResourceDomain() *schema.Resource { Type: schema.TypeString, Computed: true, }, - - "index": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validateIndexName, - }, - - "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(cloudsearch.IndexFieldType_Values(), false), - }, - - "search": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "facet": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "return": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "sort": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "highlight": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "analysis_scheme": { - Type: schema.TypeString, - Optional: true, - }, - - "default_value": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, }, } } @@ -232,21 +219,31 @@ func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) e } } - if v, ok := d.GetOk("index"); ok && v.(*schema.Set).Len() > 0 { - for _, v := range v.(*schema.Set).List() { - field, err := generateIndexFieldInput(v.(map[string]interface{})) + if v, ok := d.GetOk("index_field"); ok && v.(*schema.Set).Len() > 0 { + for _, tfMapRaw := range v.(*schema.Set).List() { + 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(d.Id()), - IndexField: field, + IndexField: apiObject, }) if err != nil { - return fmt.Errorf("error defining CloudSearch Domain (%s) index field (%s): %w", d.Id(), aws.StringValue(field.IndexFieldName), err) + return fmt.Errorf("error defining CloudSearch Domain (%s) index field (%s): %w", d.Id(), aws.StringValue(apiObject.IndexFieldName), err) } } @@ -1107,6 +1104,216 @@ func flattenDomainEndpointOptions(apiObject *cloudsearch.DomainEndpointOptions) 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 expandScalingParameters(tfMap map[string]interface{}) *cloudsearch.ScalingParameters { if tfMap == nil { return nil From 25beea6b3e065531a174314e1667b0a9e03e502d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 18 Jan 2022 17:26:33 -0500 Subject: [PATCH 43/50] r/aws_cloudsearch_domain: Add 'flattenIndexFieldStatuses'. --- internal/service/cloudsearch/domain.go | 421 +++++++++++++++++-------- 1 file changed, 294 insertions(+), 127 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index 888ca92457ba..48dbfc906a9b 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -325,29 +325,22 @@ func resourceCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("error setting scaling_parameters: %w", err) } - // TODO... - // Read index fields. indexResults, err := conn.DescribeIndexFields(&cloudsearch.DescribeIndexFieldsInput{ DomainName: aws.String(d.Get("name").(string)), }) + if err != nil { - return err + return fmt.Errorf("error reading CloudSearch Domain (%s) index fields: %w", d.Id(), err) } - result := make([]map[string]interface{}, 0, len(indexResults.IndexFields)) - - for _, raw := range indexResults.IndexFields { - // Don't read in any fields that are pending deletion. - if *raw.Status.PendingDeletion { - continue - } - - result = append(result, readIndexField(raw.Options)) + 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) } - d.Set("index", result) - return err + return nil } func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) error { @@ -764,119 +757,6 @@ func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexFi return input, nil } -func readIndexField(raw *cloudsearch.IndexField) map[string]interface{} { - index := map[string]interface{}{ - "name": raw.IndexFieldName, - "type": raw.IndexFieldType, - } - - switch *raw.IndexFieldType { - case "date": - index["default_value"] = raw.DateOptions.DefaultValue - index["facet"] = raw.DateOptions.FacetEnabled - index["return"] = raw.DateOptions.ReturnEnabled - index["search"] = raw.DateOptions.SearchEnabled - index["sort"] = raw.DateOptions.SortEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - case "date-array": - index["default_value"] = raw.DateArrayOptions.DefaultValue - index["facet"] = raw.DateArrayOptions.FacetEnabled - index["return"] = raw.DateArrayOptions.ReturnEnabled - index["search"] = raw.DateArrayOptions.SearchEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - index["sort"] = false - case "double": - index["default_value"] = raw.DoubleOptions.DefaultValue - index["facet"] = raw.DoubleOptions.FacetEnabled - index["return"] = raw.DoubleOptions.ReturnEnabled - index["search"] = raw.DoubleOptions.SearchEnabled - index["sort"] = raw.DoubleOptions.SortEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - case "double-array": - index["default_value"] = raw.DoubleArrayOptions.DefaultValue - index["facet"] = raw.DoubleArrayOptions.FacetEnabled - index["return"] = raw.DoubleArrayOptions.ReturnEnabled - index["search"] = raw.DoubleArrayOptions.SearchEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - index["sort"] = false - case "int": - index["default_value"] = raw.IntOptions.DefaultValue - index["facet"] = raw.IntOptions.FacetEnabled - index["return"] = raw.IntOptions.ReturnEnabled - index["search"] = raw.IntOptions.SearchEnabled - index["sort"] = raw.IntOptions.SortEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - case "int-array": - index["default_value"] = raw.IntArrayOptions.DefaultValue - index["facet"] = raw.IntArrayOptions.FacetEnabled - index["return"] = raw.IntArrayOptions.ReturnEnabled - index["search"] = raw.IntArrayOptions.SearchEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - index["sort"] = false - case "latlon": - index["default_value"] = raw.LatLonOptions.DefaultValue - index["facet"] = raw.LatLonOptions.FacetEnabled - index["return"] = raw.LatLonOptions.ReturnEnabled - index["search"] = raw.LatLonOptions.SearchEnabled - index["sort"] = raw.LatLonOptions.SortEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - case "literal": - index["default_value"] = raw.LiteralOptions.DefaultValue - index["facet"] = raw.LiteralOptions.FacetEnabled - index["return"] = raw.LiteralOptions.ReturnEnabled - index["search"] = raw.LiteralOptions.SearchEnabled - index["sort"] = raw.LiteralOptions.SortEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - case "literal-array": - index["default_value"] = raw.LiteralArrayOptions.DefaultValue - index["facet"] = raw.LiteralArrayOptions.FacetEnabled - index["return"] = raw.LiteralArrayOptions.ReturnEnabled - index["search"] = raw.LiteralArrayOptions.SearchEnabled - - // Options that aren't valid for this type. - index["highlight"] = false - index["sort"] = false - case "text": - index["default_value"] = raw.TextOptions.DefaultValue - index["analysis_scheme"] = raw.TextOptions.AnalysisScheme - index["highlight"] = raw.TextOptions.HighlightEnabled - index["return"] = raw.TextOptions.ReturnEnabled - index["sort"] = raw.TextOptions.SortEnabled - - // Options that aren't valid for this type. - index["facet"] = false - index["search"] = false - case "text-array": - index["default_value"] = raw.TextArrayOptions.DefaultValue - index["analysis_scheme"] = raw.TextArrayOptions.AnalysisScheme - index["highlight"] = raw.TextArrayOptions.HighlightEnabled - index["return"] = raw.TextArrayOptions.ReturnEnabled - - // Options that aren't valid for this type. - index["facet"] = false - index["search"] = false - index["sort"] = false - } - - return index -} - func FindDomainStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.DomainStatus, error) { input := &cloudsearch.DescribeDomainsInput{ DomainNames: aws.StringSlice([]string{name}), @@ -1314,6 +1194,293 @@ func expandIndexField(tfMap map[string]interface{}) (*cloudsearch.IndexField, er 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["name"] = 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 From 318d10dadaf995a253b3399c3939c3eb09839df8 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 Jan 2022 11:17:35 -0500 Subject: [PATCH 44/50] r/aws_cloudsearch_domain: Index field functionality. % make testacc TESTARGS='-run=TestAccCloudSearchDomain_indexFields' PKG=cloudsearch ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/cloudsearch/... -v -count 1 -parallel 20 -run=TestAccCloudSearchDomain_indexFields -timeout 180m === RUN TestAccCloudSearchDomain_indexFields === PAUSE TestAccCloudSearchDomain_indexFields === CONT TestAccCloudSearchDomain_indexFields --- PASS: TestAccCloudSearchDomain_indexFields (1083.01s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/cloudsearch 1086.866s --- internal/service/cloudsearch/domain.go | 357 ++++---------------- internal/service/cloudsearch/domain_test.go | 63 +++- 2 files changed, 119 insertions(+), 301 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index 48dbfc906a9b..b6957698311e 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -3,7 +3,6 @@ package cloudsearch import ( "fmt" "log" - "reflect" "regexp" "strconv" "time" @@ -220,34 +219,13 @@ func resourceCloudSearchDomainCreate(d *schema.ResourceData, meta interface{}) e } if v, ok := d.GetOk("index_field"); ok && v.(*schema.Set).Len() > 0 { - for _, tfMapRaw := range v.(*schema.Set).List() { - tfMap, ok := tfMapRaw.(map[string]interface{}) - - if !ok { - continue - } - - apiObject, err := expandIndexField(tfMap) - - if err != nil { - return err - } + err := defineIndexFields(conn, d.Id(), v.(*schema.Set).List()) - if apiObject == nil { - continue - } - - _, err = conn.DefineIndexField(&cloudsearch.DefineIndexFieldInput{ - DomainName: aws.String(d.Id()), - IndexField: apiObject, - }) - - if err != nil { - return fmt.Errorf("error defining CloudSearch Domain (%s) index field (%s): %w", d.Id(), aws.StringValue(apiObject.IndexFieldName), err) - } + if err != nil { + return err } - _, err := conn.IndexDocuments(&cloudsearch.IndexDocumentsInput{ + _, err = conn.IndexDocuments(&cloudsearch.IndexDocumentsInput{ DomainName: aws.String(d.Id()), }) @@ -325,7 +303,6 @@ func resourceCloudSearchDomainRead(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("error setting scaling_parameters: %w", err) } - // Read index fields. indexResults, err := conn.DescribeIndexFields(&cloudsearch.DescribeIndexFieldsInput{ DomainName: aws.String(d.Get("name").(string)), }) @@ -411,9 +388,43 @@ func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) e } } - _, err := defineIndexFields(d, conn) - if err != nil { - return err + 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 { @@ -426,7 +437,7 @@ func resourceCloudSearchDomainUpdate(d *schema.ResourceData, meta interface{}) e } } - _, err = waitDomainActive(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) + _, 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) @@ -472,291 +483,37 @@ func validateIndexName(v interface{}, k string) (ws []string, es []error) { return } -// Miscellaneous helper functions -func defineIndexFields(d *schema.ResourceData, conn *cloudsearch.CloudSearch) (bool, error) { - // Early return if we don't have a change. - if !d.HasChange("index") { - return false, nil - } - - o, n := d.GetChange("index") +func defineIndexFields(conn *cloudsearch.CloudSearch, domainName string, tfList []interface{}) error { + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) - old := o.(*schema.Set) - new := n.(*schema.Set) + if !ok { + continue + } - // Returns a set of only old fields, to be deleted. - toDelete := old.Difference(new) - for _, index := range toDelete.List() { - v, _ := index.(map[string]interface{}) + apiObject, err := expandIndexField(tfMap) - _, err := conn.DeleteIndexField(&cloudsearch.DeleteIndexFieldInput{ - DomainName: aws.String(d.Get("name").(string)), - IndexFieldName: aws.String(v["name"].(string)), - }) if err != nil { - return true, err + return err } - } - - // Returns a set of only fields that needs to be added or updated (upserted). - toUpsert := new.Difference(old) - for _, index := range toUpsert.List() { - v, _ := index.(map[string]interface{}) - field, err := generateIndexFieldInput(v) - if err != nil { - return true, err + if apiObject == nil { + continue } _, err = conn.DefineIndexField(&cloudsearch.DefineIndexFieldInput{ - DomainName: aws.String(d.Get("name").(string)), - IndexField: field, + DomainName: aws.String(domainName), + IndexField: apiObject, }) - if err != nil { - return true, err - } - } - return true, nil -} - -/* -extractFromMapToType extracts a specific value from map[string]interface{} into an interface of type -expects: map[string]interface{}, string, interface{} -returns: error -*/ -func extractFromMapToType(index map[string]interface{}, property string, t interface{}) error { - v, ok := index[property] - if !ok { - return fmt.Errorf("%s is not a valid property of an index", property) - } - - if "default_value" == property { - switch t.(type) { - case *int: - d, err := strconv.Atoi(v.(string)) - if err != nil { - return parseError(v.(string), "int") - } - - reflect.ValueOf(t).Elem().Set(reflect.ValueOf(d)) - case *float64: - f, err := strconv.ParseFloat(v.(string), 64) - if err != nil { - return parseError(v.(string), "double") - } - - reflect.ValueOf(t).Elem().Set(reflect.ValueOf(f)) - default: - if v.(string) != "" { - reflect.ValueOf(t).Elem().Set(reflect.ValueOf(v)) - } + if err != nil { + return fmt.Errorf("error defining CloudSearch Domain (%s) index field (%s): %w", domainName, aws.StringValue(apiObject.IndexFieldName), err) } - return nil } - reflect.ValueOf(t).Elem().Set(reflect.ValueOf(v)) return nil } -var parseError = func(d string, t string) error { - return fmt.Errorf("can't convert default_value '%s' of type '%s' to int", d, t) -} - -func generateIndexFieldInput(index map[string]interface{}) (*cloudsearch.IndexField, error) { - input := &cloudsearch.IndexField{ - IndexFieldName: aws.String(index["name"].(string)), - IndexFieldType: aws.String(index["type"].(string)), - } - - // TODO: clean this up, this very likely could be written in a much cleaner way than this. - var facet bool - var returnV bool - var search bool - var sort bool - var highlight bool - var analysisScheme string - - err := extractFromMapToType(index, "facet", &facet) - if err != nil { - return nil, err - } - - err = extractFromMapToType(index, "return", &returnV) - if err != nil { - return nil, err - } - - err = extractFromMapToType(index, "search", &search) - if err != nil { - return nil, err - } - - err = extractFromMapToType(index, "sort", &sort) - if err != nil { - return nil, err - } - - err = extractFromMapToType(index, "highlight", &highlight) - if err != nil { - return nil, err - } - - err = extractFromMapToType(index, "analysis_scheme", &analysisScheme) - if err != nil { - return nil, err - } - - switch index["type"] { - case "date": - input.DateOptions = &cloudsearch.DateOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } - - if index["default_value"].(string) != "" { - input.DateOptions.DefaultValue = aws.String(index["default_value"].(string)) - } - case "date-array": - input.DateArrayOptions = &cloudsearch.DateArrayOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - } - - if index["default_value"].(string) != "" { - input.DateArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) - } - case "double": - input.DoubleOptions = &cloudsearch.DoubleOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } - - if index["default_value"].(string) != "" { - var defaultValue float64 - err = extractFromMapToType(index, "default_value", &defaultValue) - if err != nil { - return nil, err - } - input.DoubleOptions.DefaultValue = aws.Float64(defaultValue) - } - case "double-array": - input.DoubleArrayOptions = &cloudsearch.DoubleArrayOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - } - - if index["default_value"].(string) != "" { - var defaultValue float64 - err = extractFromMapToType(index, "default_value", &defaultValue) - if err != nil { - return nil, err - } - input.DoubleArrayOptions.DefaultValue = aws.Float64(defaultValue) - } - case "int": - input.IntOptions = &cloudsearch.IntOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } - - if index["default_value"].(string) != "" { - var defaultValue int - err = extractFromMapToType(index, "default_value", &defaultValue) - if err != nil { - return nil, err - } - input.IntOptions.DefaultValue = aws.Int64(int64(defaultValue)) - } - case "int-array": - input.IntArrayOptions = &cloudsearch.IntArrayOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - } - - if index["default_value"].(string) != "" { - var defaultValue int - err = extractFromMapToType(index, "default_value", &defaultValue) - if err != nil { - return nil, err - } - input.IntArrayOptions.DefaultValue = aws.Int64(int64(defaultValue)) - } - case "latlon": - input.LatLonOptions = &cloudsearch.LatLonOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } - - if index["default_value"].(string) != "" { - input.LatLonOptions.DefaultValue = aws.String(index["default_value"].(string)) - } - case "literal": - input.LiteralOptions = &cloudsearch.LiteralOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - SortEnabled: aws.Bool(sort), - } - - if index["default_value"].(string) != "" { - input.LiteralOptions.DefaultValue = aws.String(index["default_value"].(string)) - } - case "literal-array": - input.LiteralArrayOptions = &cloudsearch.LiteralArrayOptions{ - FacetEnabled: aws.Bool(facet), - ReturnEnabled: aws.Bool(returnV), - SearchEnabled: aws.Bool(search), - } - - if index["default_value"].(string) != "" { - input.LiteralArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) - } - case "text": - input.TextOptions = &cloudsearch.TextOptions{ - SortEnabled: aws.Bool(sort), - ReturnEnabled: aws.Bool(returnV), - HighlightEnabled: aws.Bool(highlight), - } - - if analysisScheme != "" { - input.TextOptions.AnalysisScheme = aws.String(analysisScheme) - } - - if index["default_value"].(string) != "" { - input.TextOptions.DefaultValue = aws.String(index["default_value"].(string)) - } - case "text-array": - input.TextArrayOptions = &cloudsearch.TextArrayOptions{ - ReturnEnabled: aws.Bool(returnV), - HighlightEnabled: aws.Bool(highlight), - } - - if analysisScheme != "" { - input.TextArrayOptions.AnalysisScheme = aws.String(analysisScheme) - } - - if index["default_value"].(string) != "" { - input.TextArrayOptions.DefaultValue = aws.String(index["default_value"].(string)) - } - default: - return input, fmt.Errorf("invalid index field type %s", index["type"]) - } - - return input, nil -} - func FindDomainStatusByName(conn *cloudsearch.CloudSearch, name string) (*cloudsearch.DomainStatus, error) { input := &cloudsearch.DescribeDomainsInput{ DomainNames: aws.StringSlice([]string{name}), @@ -1213,7 +970,7 @@ func flattenIndexFieldStatus(apiObject *cloudsearch.IndexFieldStatus) (map[strin fieldType := field.IndexFieldType if fieldType != nil { - tfMap["name"] = aws.StringValue(fieldType) + tfMap["type"] = aws.StringValue(fieldType) } switch fieldType := aws.StringValue(fieldType); fieldType { diff --git a/internal/service/cloudsearch/domain_test.go b/internal/service/cloudsearch/domain_test.go index ec25b04262f7..6a1aca8409bd 100644 --- a/internal/service/cloudsearch/domain_test.go +++ b/internal/service/cloudsearch/domain_test.go @@ -34,7 +34,7 @@ func TestAccCloudSearchDomain_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "endpoint_options.#", "1"), resource.TestCheckResourceAttr(resourceName, "endpoint_options.0.enforce_https", "false"), resource.TestCheckResourceAttrSet(resourceName, "endpoint_options.0.tls_security_policy"), - resource.TestCheckResourceAttr(resourceName, "index.#", "0"), + resource.TestCheckResourceAttr(resourceName, "index_field.#", "0"), resource.TestCheckResourceAttr(resourceName, "multi_az", "false"), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "scaling_parameters.#", "1"), @@ -75,6 +75,44 @@ func TestAccCloudSearchDomain_disappears(t *testing.T) { }) } +func TestAccCloudSearchDomain_indexFields(t *testing.T) { + var v cloudsearch.DomainStatus + resourceName := "aws_cloudsearch_domain.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: testAccCloudSearchDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudSearchDomainIndexFieldsConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCloudSearchDomainExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "index_field.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "index_field.*", map[string]string{ + "name": "int_test", + "type": "int", + "default_value": "2", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "index_field.*", map[string]string{ + "name": "literal_test", + "type": "literal", + "facet": "true", + "search": "true", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccCloudSearchDomain_simple(t *testing.T) { var v cloudsearch.DomainStatus resourceName := "aws_cloudsearch_domain.test" @@ -205,6 +243,29 @@ resource "aws_cloudsearch_domain" "test" { `, rName) } +func testAccCloudSearchDomainIndexFieldsConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_cloudsearch_domain" "test" { + name = %[1]q + + index_field { + name = "int_test" + type = "int" + default_value = "2" + } + + index_field { + name = "literal_test" + type = "literal" + facet = true + return = false + search = true + sort = false + } +} +`, rName) +} + func testAccCloudSearchDomainConfig_simple(name string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { From 85e6cf0504899558933a50bedc53c830f8161b4b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 Jan 2022 11:53:24 -0500 Subject: [PATCH 45/50] r/aws_cloudsearch_domain: Test index field updates. % make testacc TESTARGS='-run=TestAccCloudSearchDomain_indexFields' PKG=cloudsearch ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/cloudsearch/... -v -count 1 -parallel 20 -run=TestAccCloudSearchDomain_indexFields -timeout 180m === RUN TestAccCloudSearchDomain_indexFields === PAUSE TestAccCloudSearchDomain_indexFields === CONT TestAccCloudSearchDomain_indexFields --- PASS: TestAccCloudSearchDomain_indexFields (1565.71s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/cloudsearch 1570.444s --- internal/service/cloudsearch/domain_test.go | 361 ++------------------ 1 file changed, 32 insertions(+), 329 deletions(-) diff --git a/internal/service/cloudsearch/domain_test.go b/internal/service/cloudsearch/domain_test.go index 6a1aca8409bd..8e6e7c0a45bf 100644 --- a/internal/service/cloudsearch/domain_test.go +++ b/internal/service/cloudsearch/domain_test.go @@ -109,79 +109,32 @@ func TestAccCloudSearchDomain_indexFields(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - }, - }) -} - -func TestAccCloudSearchDomain_simple(t *testing.T) { - var v cloudsearch.DomainStatus - resourceName := "aws_cloudsearch_domain.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: testAccCloudSearchDomainDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCloudSearchDomainConfig_simple(rName), - Check: resource.ComposeTestCheckFunc( - testAccCloudSearchDomainExists(resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "name", rName), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudSearchDomainConfig_basicIndexMix(rName), - Check: resource.ComposeTestCheckFunc( - testAccCloudSearchDomainExists(resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "name", rName), - ), - }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccCloudSearchDomain_textAnalysisScheme(t *testing.T) { - var v cloudsearch.DomainStatus - resourceName := "aws_cloudsearch_domain.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: testAccCloudSearchDomainDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCloudSearchDomainConfig_textAnalysisScheme(rName, "_en_default_"), - Check: resource.ComposeTestCheckFunc( - testAccCloudSearchDomainExists(resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "name", acctest.RandomFQDomainName()), - ), - }, - { - Config: testAccCloudSearchDomainConfig_textAnalysisScheme(rName, "_fr_default_"), - Check: resource.ComposeTestCheckFunc( + Config: testAccCloudSearchDomainIndexFieldsUpdatedConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCloudSearchDomainExists(resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "index_field.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "index_field.*", map[string]string{ + "name": "literal_test", + "type": "literal", + "default_value": "literally testing", + "return": "true", + "sort": "true", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "index_field.*", map[string]string{ + "name": "double_array_test", + "type": "double-array", + "default_value": "-12.34", + "search": "true", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "index_field.*", map[string]string{ + "name": "text_test", + "type": "text", + "analysis_scheme": "_en_default_", + "highlight": "true", + }), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } @@ -266,286 +219,36 @@ resource "aws_cloudsearch_domain" "test" { `, rName) } -func testAccCloudSearchDomainConfig_simple(name string) string { +func testAccCloudSearchDomainIndexFieldsUpdatedConfig(rName string) string { return fmt.Sprintf(` resource "aws_cloudsearch_domain" "test" { - name = "%s" - - index { - name = "date_test" - type = "date" - facet = true - search = true - return = true - sort = true - } - - index { - name = "date_array_test" - type = "date-array" - facet = true - search = true - return = true - } - - index { - name = "double_test" - type = "double" - facet = true - search = true - return = true - sort = true - } - - index { - name = "double_array_test" - type = "double-array" - facet = true - search = true - return = true - } - - index { - name = "int_test" - type = "int" - facet = true - search = true - return = true - sort = true - } - - index { - name = "int_array_test" - type = "int-array" - facet = true - search = true - return = true - } - - index { - name = "latlon_test" - type = "latlon" - facet = true - search = true - return = true - sort = true - } + name = %[1]q - index { + index_field { name = "literal_test" type = "literal" - facet = true - search = true + facet = false return = true + search = false sort = true - } - - index { - name = "literal_array_test" - type = "literal-array" - facet = true - search = true - return = true - } - - index { - name = "text_test" - type = "text" - analysis_scheme = "_en_default_" - highlight = true - return = true - sort = true - } - - index { - name = "text_array_test" - type = "text-array" - analysis_scheme = "_en_default_" - highlight = true - return = true - } - - wait_for_endpoints = false - service_access_policies = < Date: Wed, 19 Jan 2022 11:59:45 -0500 Subject: [PATCH 46/50] r/aws_cloudsearch_domain_service_access_policy: Fix terrafmt errors. --- .../service/cloudsearch/domain_service_access_policy_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/cloudsearch/domain_service_access_policy_test.go b/internal/service/cloudsearch/domain_service_access_policy_test.go index 2f5994cba6a8..0d52e9d82df3 100644 --- a/internal/service/cloudsearch/domain_service_access_policy_test.go +++ b/internal/service/cloudsearch/domain_service_access_policy_test.go @@ -128,7 +128,7 @@ resource "aws_cloudsearch_domain" "test" { resource "aws_cloudsearch_domain_service_access_policy" "test" { domain_name = aws_cloudsearch_domain.test.id - + access_policy = < Date: Wed, 19 Jan 2022 13:29:04 -0500 Subject: [PATCH 47/50] r/aws_cloudsearch_domain: Add 'TestAccCloudSearchDomain_update'. % make testacc TESTARGS='-run=TestAccCloudSearchDomain_update' PKG=cloudsearch ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/cloudsearch/... -v -count 1 -parallel 20 -run=TestAccCloudSearchDomain_update -timeout 180m === RUN TestAccCloudSearchDomain_update === PAUSE TestAccCloudSearchDomain_update === CONT TestAccCloudSearchDomain_update --- PASS: TestAccCloudSearchDomain_update (1264.26s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/cloudsearch 1267.577s --- internal/service/cloudsearch/domain_test.go | 68 +++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/internal/service/cloudsearch/domain_test.go b/internal/service/cloudsearch/domain_test.go index 8e6e7c0a45bf..bbe21d318597 100644 --- a/internal/service/cloudsearch/domain_test.go +++ b/internal/service/cloudsearch/domain_test.go @@ -139,6 +139,48 @@ func TestAccCloudSearchDomain_indexFields(t *testing.T) { }) } +func TestAccCloudSearchDomain_update(t *testing.T) { + var v cloudsearch.DomainStatus + resourceName := "aws_cloudsearch_domain.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: testAccCloudSearchDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudSearchDomainAllOptionsConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCloudSearchDomainExists(resourceName, &v), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "cloudsearch", fmt.Sprintf("domain/%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "domain_id"), + resource.TestCheckResourceAttr(resourceName, "endpoint_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "endpoint_options.0.enforce_https", "true"), + resource.TestCheckResourceAttr(resourceName, "endpoint_options.0.tls_security_policy", "Policy-Min-TLS-1-0-2019-07"), + resource.TestCheckResourceAttr(resourceName, "index_field.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "index_field.*", map[string]string{ + "name": "latlon_test", + "type": "latlon", + }), + resource.TestCheckResourceAttr(resourceName, "multi_az", "true"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.#", "1"), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_instance_type", "search.small"), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_partition_count", "1"), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_replication_count", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCloudSearchDomainExists(n string, v *cloudsearch.DomainStatus) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -252,3 +294,29 @@ resource "aws_cloudsearch_domain" "test" { } `, rName) } + +func testAccCloudSearchDomainAllOptionsConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_cloudsearch_domain" "test" { + name = %[1]q + + endpoint_options { + enforce_https = true + tls_security_policy = "Policy-Min-TLS-1-0-2019-07" + } + + multi_az = true + + scaling_parameters { + desired_instance_type = "search.small" + desired_partition_count = 1 + desired_replication_count = 1 + } + + index_field { + name = "latlon_test" + type = "latlon" + } +} +`, rName) +} From ad3a0c441c66b5b829746656c4d58ce48b3a9b58 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 Jan 2022 13:40:21 -0500 Subject: [PATCH 48/50] Fix golangci-lint 'unparam' errors. --- internal/service/cloudsearch/domain.go | 2 +- internal/service/cloudsearch/domain_service_access_policy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/cloudsearch/domain.go b/internal/service/cloudsearch/domain.go index b6957698311e..7c3534bc2397 100644 --- a/internal/service/cloudsearch/domain.go +++ b/internal/service/cloudsearch/domain.go @@ -671,7 +671,7 @@ func statusDomainProcessing(conn *cloudsearch.CloudSearch, name string) resource } } -func waitDomainActive(conn *cloudsearch.CloudSearch, name string, timeout time.Duration) (*cloudsearch.DomainStatus, error) { +func waitDomainActive(conn *cloudsearch.CloudSearch, name string, timeout time.Duration) (*cloudsearch.DomainStatus, error) { //nolint:unparam stateConf := &resource.StateChangeConf{ Pending: []string{"true"}, Target: []string{"false"}, diff --git a/internal/service/cloudsearch/domain_service_access_policy.go b/internal/service/cloudsearch/domain_service_access_policy.go index 9e6e9aec1784..1d49e6c7bc32 100644 --- a/internal/service/cloudsearch/domain_service_access_policy.go +++ b/internal/service/cloudsearch/domain_service_access_policy.go @@ -198,7 +198,7 @@ func statusAccessPolicyState(conn *cloudsearch.CloudSearch, name string) resourc } } -func waitAccessPolicyActive(conn *cloudsearch.CloudSearch, name string, timeout time.Duration) (*cloudsearch.AccessPoliciesStatus, error) { +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}, From e23870b5df65048b20cbb85a85d90f88c4bb8e8f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 Jan 2022 14:20:41 -0500 Subject: [PATCH 49/50] r/aws_cloudsearch_domain: Do updates in 'TestAccCloudSearchDomain_update'. % make testacc TESTARGS='-run=TestAccCloudSearchDomain_update' PKG=cloudsearch ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/cloudsearch/... -v -count 1 -parallel 20 -run=TestAccCloudSearchDomain_update -timeout 180m === RUN TestAccCloudSearchDomain_update === PAUSE TestAccCloudSearchDomain_update === CONT TestAccCloudSearchDomain_update --- PASS: TestAccCloudSearchDomain_update (1570.32s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/cloudsearch 1574.295s --- internal/service/cloudsearch/domain_test.go | 52 +++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/internal/service/cloudsearch/domain_test.go b/internal/service/cloudsearch/domain_test.go index bbe21d318597..36070c0cbddc 100644 --- a/internal/service/cloudsearch/domain_test.go +++ b/internal/service/cloudsearch/domain_test.go @@ -177,6 +177,30 @@ func TestAccCloudSearchDomain_update(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccCloudSearchDomainAllOptionsUpdatedConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCloudSearchDomainExists(resourceName, &v), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "cloudsearch", fmt.Sprintf("domain/%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "domain_id"), + resource.TestCheckResourceAttr(resourceName, "endpoint_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "endpoint_options.0.enforce_https", "true"), + resource.TestCheckResourceAttr(resourceName, "endpoint_options.0.tls_security_policy", "Policy-Min-TLS-1-2-2019-07"), + resource.TestCheckResourceAttr(resourceName, "index_field.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "index_field.*", map[string]string{ + "name": "text_array_test", + "type": "text-array", + "return": "true", + "analysis_scheme": "_fr_default_", + }), + resource.TestCheckResourceAttr(resourceName, "multi_az", "false"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.#", "1"), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_instance_type", "search.medium"), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_partition_count", "1"), + resource.TestCheckResourceAttr(resourceName, "scaling_parameters.0.desired_replication_count", "2"), + ), + }, }, }) } @@ -320,3 +344,31 @@ resource "aws_cloudsearch_domain" "test" { } `, rName) } + +func testAccCloudSearchDomainAllOptionsUpdatedConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_cloudsearch_domain" "test" { + name = %[1]q + + endpoint_options { + enforce_https = true + tls_security_policy = "Policy-Min-TLS-1-2-2019-07" + } + + multi_az = false + + scaling_parameters { + desired_instance_type = "search.medium" + desired_partition_count = 1 + desired_replication_count = 2 + } + + index_field { + name = "text_array_test" + type = "text-array" + return = true + analysis_scheme = "_fr_default_" + } +} +`, rName) +} From 4c1e261369d35db11687d083c5fae2d286a69bc9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 19 Jan 2022 15:15:17 -0500 Subject: [PATCH 50/50] Update documentation for 'index_field'. --- .../docs/r/cloudsearch_domain.html.markdown | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/website/docs/r/cloudsearch_domain.html.markdown b/website/docs/r/cloudsearch_domain.html.markdown index 0e8d0908c60e..6b635195507d 100644 --- a/website/docs/r/cloudsearch_domain.html.markdown +++ b/website/docs/r/cloudsearch_domain.html.markdown @@ -16,10 +16,13 @@ Terraform waits for the domain to become `Active` when applying a configuration. ```terraform resource "aws_cloudsearch_domain" "example" { - name = "example-domain" - instance_type = "search.medium" + name = "example-domain" - index { + scaling_parameters { + desired_instance_type = "search.medium" + } + + index_field { name = "headline" type = "text" search = true @@ -29,7 +32,7 @@ resource "aws_cloudsearch_domain" "example" { analysis_scheme = "_en_default_" } - index { + index_field { name = "price" type = "double" search = true @@ -45,15 +48,11 @@ resource "aws_cloudsearch_domain" "example" { The following arguments are supported: * `endpoint_options` - (Optional) Domain endpoint options. Documented below. +* `index_field` - (Optional) The index fields for documents added to the domain. Documented below. * `multi_az` - (Optional) Whether or not to maintain extra instances for the domain in a second Availability Zone to ensure high availability. * `name` - (Required) The name of the CloudSearch domain. * `scaling_parameters` - (Optional) Domain scaling parameters. Documented below. -* `instance_type` - (Optional) The type of instance to start. -* `replication_count` - (Optional) The amount of replicas. -* `partition_count` - (Optional) The amount of partitions on each instance. Currently only supported by `search.2xlarge`. -* `index` - (Required) See [Indices](#indices) below for details. - ### endpoint_options This configuration block supports the following attributes: @@ -69,19 +68,19 @@ This configuration block supports the following attributes: * `desired_partition_count` - (Optional) The number of partitions you want to preconfigure for your domain. Only valid when you select `search.2xlarge` as the instance type. * `desired_replication_count` - (Optional) The number of replicas you want to preconfigure for each index partition. -### Indices +### index_field -Each of the `index` entities represents an index field of the domain. +This configuration block supports the following attributes: -* `name` - (Required) Represents the property field name. -* `type` - (Required) Represents the property type. It can be one of `int,int-array,double,double-array,literal,literal-array,text,text-array,date,date-array,lation` [AWS's Docs](http://docs.aws.amazon.com/cloudsearch/latest/developerguide/configuring-index-fields.html) -* `search` - (Required) You can set whether this index should be searchable or not. (index of type text ist always searchable) +* `name` - (Required) A unique name for the field. Field names must begin with a letter and be at least 3 and no more than 64 characters long. The allowed characters are: `a`-`z` (lower-case letters), `0`-`9`, and `_` (underscore). The name `score` is reserved and cannot be used as a field name. +* `type` - (Required) The field type. Valid values: `date`, `date-array`, `double`, `double-array`, `int`, `int-array`, `literal`, `literal-array`, `text`, `text-array`. +* `analysis_scheme` - (Optional) The analysis scheme you want to use for a `text` field. The analysis scheme specifies the language-specific text processing options that are used during indexing. +* `default_value` - (Optional) The default value for the field. This value is used when no value is specified for the field in the document data. * `facet` - (Optional) You can get facet information by enabling this. -* `return` - (Required) You can enable returning the value of all searchable fields. -* `sort` - (Optional) You can enable the property to be sortable. * `highlight` - (Optional) You can highlight information. -* `analysis_scheme` - (Optional) Only needed with type `text`. [AWS's Docs - supported languages](http://docs.aws.amazon.com/cloudsearch/latest/developerguide/text-processing.html) -* `default_value` - (Optional) The default value. +* `return` - (Optional) You can enable returning the value of all searchable fields. +* `search` - (Optional) You can set whether this index should be searchable or not. +* `sort` - (Optional) You can enable the property to be sortable. ## Attributes Reference