diff --git a/.changelog/25199.txt b/.changelog/25199.txt new file mode 100644 index 000000000000..edc5494ad564 --- /dev/null +++ b/.changelog/25199.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_kendra_thesaurus +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7e6200d9acbd..240ba56e2df6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1586,7 +1586,8 @@ func Provider() *schema.Provider { "aws_mskconnect_custom_plugin": kafkaconnect.ResourceCustomPlugin(), "aws_mskconnect_worker_configuration": kafkaconnect.ResourceWorkerConfiguration(), - "aws_kendra_index": kendra.ResourceIndex(), + "aws_kendra_index": kendra.ResourceIndex(), + "aws_kendra_thesaurus": kendra.ResourceThesaurus(), "aws_keyspaces_keyspace": keyspaces.ResourceKeyspace(), "aws_keyspaces_table": keyspaces.ResourceTable(), diff --git a/internal/service/kendra/find.go b/internal/service/kendra/find.go new file mode 100644 index 000000000000..db1bf147d2d7 --- /dev/null +++ b/internal/service/kendra/find.go @@ -0,0 +1,39 @@ +package kendra + +import ( + "context" + "errors" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/kendra" + "github.com/aws/aws-sdk-go-v2/service/kendra/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func FindThesaurusByID(ctx context.Context, conn *kendra.Client, id, indexId string) (*kendra.DescribeThesaurusOutput, error) { + in := &kendra.DescribeThesaurusInput{ + Id: aws.String(id), + IndexId: aws.String(indexId), + } + + out, err := conn.DescribeThesaurus(ctx, in) + + var resourceNotFoundException *types.ResourceNotFoundException + if errors.As(err, &resourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + if err != nil { + return nil, err + } + + if out == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} diff --git a/internal/service/kendra/id.go b/internal/service/kendra/id.go new file mode 100644 index 000000000000..8c7198355467 --- /dev/null +++ b/internal/service/kendra/id.go @@ -0,0 +1,16 @@ +package kendra + +import ( + "fmt" + "strings" +) + +func ThesaurusParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, "/") + + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("please make sure ID is in format THESAURUS_ID/INDEX_ID") + } + + return parts[0], parts[1], nil +} diff --git a/internal/service/kendra/id_test.go b/internal/service/kendra/id_test.go new file mode 100644 index 000000000000..51ffc69e635f --- /dev/null +++ b/internal/service/kendra/id_test.go @@ -0,0 +1,75 @@ +package kendra_test + +import ( + "testing" + + tfkendra "github.com/hashicorp/terraform-provider-aws/internal/service/kendra" +) + +func TestThesaurusParseResourceID(t *testing.T) { + testCases := []struct { + TestName string + Input string + ExpectedId string + ExpectedIndexId string + Error bool + }{ + { + TestName: "empty", + Input: "", + ExpectedId: "", + ExpectedIndexId: "", + Error: true, + }, + { + TestName: "Invalid ID", + Input: "abcdefg12345678/", + ExpectedId: "", + ExpectedIndexId: "", + Error: true, + }, + { + TestName: "Invalid ID separator", + Input: "abcdefg12345678:qwerty09876", + ExpectedId: "", + ExpectedIndexId: "", + Error: true, + }, + { + TestName: "Invalid ID with more than 1 separator", + Input: "abcdefg12345678/qwerty09876/zxcvbnm123456", + ExpectedId: "", + ExpectedIndexId: "", + Error: true, + }, + { + TestName: "Valid ID", + Input: "abcdefg12345678/qwerty09876", + ExpectedId: "abcdefg12345678", + ExpectedIndexId: "qwerty09876", + Error: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + gotId, gotIndexId, err := tfkendra.ThesaurusParseResourceID(testCase.Input) + + if err != nil && !testCase.Error { + t.Errorf("got error (%s), expected no error", err) + } + + if err == nil && testCase.Error { + t.Errorf("got (Id: %s, IndexId: %s) and no error, expected error", gotId, gotIndexId) + } + + if gotId != testCase.ExpectedId { + t.Errorf("got %s, expected %s", gotId, testCase.ExpectedIndexId) + } + + if gotIndexId != testCase.ExpectedIndexId { + t.Errorf("got %s, expected %s", gotIndexId, testCase.ExpectedIndexId) + } + }) + } +} diff --git a/internal/service/kendra/index.go b/internal/service/kendra/index.go index 1321330c3725..c3ab1ebdb151 100644 --- a/internal/service/kendra/index.go +++ b/internal/service/kendra/index.go @@ -26,6 +26,9 @@ import ( const ( // Allow IAM role to become visible to the index propagationTimeout = 2 * time.Minute + + // validationExceptionMessage describes the error returned when the IAM role has not yet propagated + validationExceptionMessage = "Please make sure your role exists and has `kendra.amazonaws.com` as trusted entity" ) func ResourceIndex() *schema.Resource { @@ -390,7 +393,7 @@ func resourceIndexCreate(ctx context.Context, d *schema.ResourceData, meta inter func(err error) (bool, error) { var validationException *types.ValidationException - if errors.As(err, &validationException) && strings.Contains(validationException.ErrorMessage(), "Please make sure your role exists and has `kendra.amazonaws.com` as trusted entity") { + if errors.As(err, &validationException) && strings.Contains(validationException.ErrorMessage(), validationExceptionMessage) { return true, err } @@ -440,10 +443,6 @@ func resourceIndexRead(ctx context.Context, d *schema.ResourceData, meta interfa return diag.Errorf("error getting Kendra Index (%s): %s", d.Id(), err) } - if resp == nil { - return diag.Errorf("error getting Kendra Index (%s): empty response", d.Id()) - } - arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Service: "kendra", @@ -536,10 +535,24 @@ func resourceIndexUpdate(ctx context.Context, d *schema.ResourceData, meta inter input.UserTokenConfigurations = expandUserTokenConfigurations(d.Get("user_token_configurations").([]interface{})) } - _, err := conn.UpdateIndex(ctx, input) + _, err := tfresource.RetryWhen( + propagationTimeout, + func() (interface{}, error) { + return conn.UpdateIndex(ctx, input) + }, + func(err error) (bool, error) { + var validationException *types.ValidationException + + if errors.As(err, &validationException) && strings.Contains(validationException.ErrorMessage(), validationExceptionMessage) { + return true, err + } + + return false, err + }, + ) if err != nil { - return diag.Errorf("[ERROR] Error updating Index (%s): %s", d.Id(), err) + return diag.Errorf("error updating Index (%s): %s", d.Id(), err) } // waiter since the status changes from UPDATING to either ACTIVE or FAILED diff --git a/internal/service/kendra/index_test.go b/internal/service/kendra/index_test.go index 48dc1955aae8..34be7c31a302 100644 --- a/internal/service/kendra/index_test.go +++ b/internal/service/kendra/index_test.go @@ -36,7 +36,7 @@ func testAccPreCheck(t *testing.T) { } } -func TestAccKendraIndex_basic(t *testing.T) { +func testAccIndex_basic(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -45,7 +45,7 @@ func TestAccKendraIndex_basic(t *testing.T) { description := "basic" resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, @@ -88,7 +88,7 @@ func TestAccKendraIndex_basic(t *testing.T) { }) } -func TestAccKendraIndex_serverSideEncryption(t *testing.T) { +func testAccIndex_serverSideEncryption(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -96,7 +96,7 @@ func TestAccKendraIndex_serverSideEncryption(t *testing.T) { rName3 := sdkacctest.RandomWithPrefix("resource-test-terraform") resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, @@ -119,7 +119,7 @@ func TestAccKendraIndex_serverSideEncryption(t *testing.T) { }) } -func TestAccKendraIndex_updateCapacityUnits(t *testing.T) { +func testAccIndex_updateCapacityUnits(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -131,7 +131,7 @@ func TestAccKendraIndex_updateCapacityUnits(t *testing.T) { updatedStorageCapacityUnits := 2 resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, @@ -163,7 +163,7 @@ func TestAccKendraIndex_updateCapacityUnits(t *testing.T) { }, }) } -func TestAccKendraIndex_updateDescription(t *testing.T) { +func testAccIndex_updateDescription(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -173,7 +173,7 @@ func TestAccKendraIndex_updateDescription(t *testing.T) { updatedDescription := "updated description" resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, @@ -202,7 +202,7 @@ func TestAccKendraIndex_updateDescription(t *testing.T) { }) } -func TestAccKendraIndex_updateName(t *testing.T) { +func testAccIndex_updateName(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -212,7 +212,7 @@ func TestAccKendraIndex_updateName(t *testing.T) { description := "description" resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, @@ -241,7 +241,7 @@ func TestAccKendraIndex_updateName(t *testing.T) { }) } -func TestAccKendraIndex_updateUserTokenJSON(t *testing.T) { +func testAccIndex_updateUserTokenJSON(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -253,7 +253,7 @@ func TestAccKendraIndex_updateUserTokenJSON(t *testing.T) { updatedUserNameAttributeField := "usernames" resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, @@ -298,7 +298,7 @@ func TestAccKendraIndex_updateUserTokenJSON(t *testing.T) { }) } -func TestAccKendraIndex_updateTags(t *testing.T) { +func testAccIndex_updateTags(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -307,7 +307,7 @@ func TestAccKendraIndex_updateTags(t *testing.T) { description := "description" resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, @@ -349,7 +349,7 @@ func TestAccKendraIndex_updateTags(t *testing.T) { }) } -func TestAccKendraIndex_updateRoleARN(t *testing.T) { +func testAccIndex_updateRoleARN(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -358,7 +358,7 @@ func TestAccKendraIndex_updateRoleARN(t *testing.T) { description := "description" resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, @@ -387,7 +387,7 @@ func TestAccKendraIndex_updateRoleARN(t *testing.T) { }) } -func TestAccKendraIndex_disappears(t *testing.T) { +func testAccIndex_disappears(t *testing.T) { var index kendra.DescribeIndexOutput rName := sdkacctest.RandomWithPrefix("resource-test-terraform") @@ -396,7 +396,7 @@ func TestAccKendraIndex_disappears(t *testing.T) { description := "disappears" resourceName := "aws_kendra_index.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), ProviderFactories: acctest.ProviderFactories, diff --git a/internal/service/kendra/kendra_test.go b/internal/service/kendra/kendra_test.go new file mode 100644 index 000000000000..be0421f417d8 --- /dev/null +++ b/internal/service/kendra/kendra_test.go @@ -0,0 +1,41 @@ +package kendra_test + +import "testing" + +// Serialize to limit service quota exceeded errors. +func TestAccKendra_serial(t *testing.T) { + testCases := map[string]map[string]func(t *testing.T){ + "Index": { + "basic": testAccIndex_basic, + "disappears": testAccIndex_disappears, + "tags": testAccIndex_updateTags, + "CapacityUnits": testAccIndex_updateCapacityUnits, + "Description": testAccIndex_updateDescription, + "Name": testAccIndex_updateName, + "RoleARN": testAccIndex_updateRoleARN, + "ServerSideEncryption": testAccIndex_serverSideEncryption, + "UserTokenJSON": testAccIndex_updateUserTokenJSON, + }, + "Thesaurus": { + "basic": testAccThesaurus_basic, + "disappears": testAccThesaurus_disappears, + "tags": testAccThesaurus_tags, + "Description": testAccThesaurus_description, + "Name": testAccThesaurus_name, + "RoleARN": testAccThesaurus_roleARN, + "SourceS3Path": testAccThesaurus_sourceS3Path, + }, + } + + for group, m := range testCases { + m := m + t.Run(group, func(t *testing.T) { + for name, tc := range m { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } + }) + } +} diff --git a/internal/service/kendra/thesaurus.go b/internal/service/kendra/thesaurus.go new file mode 100644 index 000000000000..c8f7479faaa6 --- /dev/null +++ b/internal/service/kendra/thesaurus.go @@ -0,0 +1,426 @@ +package kendra + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/kendra" + "github.com/aws/aws-sdk-go-v2/service/kendra/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceThesaurus() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceThesaurusCreate, + ReadWithoutTimeout: resourceThesaurusRead, + UpdateWithoutTimeout: resourceThesaurusUpdate, + DeleteWithoutTimeout: resourceThesaurusDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "index_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "source_s3_path": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + }, + "key": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceThesaurusCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).KendraConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + input := &kendra.CreateThesaurusInput{ + ClientToken: aws.String(resource.UniqueId()), + IndexId: aws.String(d.Get("index_id").(string)), + Name: aws.String(d.Get("name").(string)), + RoleArn: aws.String(d.Get("role_arn").(string)), + SourceS3Path: expandSourceS3Path(d.Get("source_s3_path").([]interface{})), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + outputRaw, err := tfresource.RetryWhen( + propagationTimeout, + func() (interface{}, error) { + return conn.CreateThesaurus(ctx, input) + }, + func(err error) (bool, error) { + var validationException *types.ValidationException + + if errors.As(err, &validationException) && strings.Contains(validationException.ErrorMessage(), validationExceptionMessage) { + return true, err + } + + return false, err + }, + ) + + if err != nil { + return diag.Errorf("creating Amazon Kendra Thesaurus (%s): %s", d.Get("name").(string), err) + } + + if outputRaw == nil { + return diag.Errorf("creating Amazon Kendra Thesaurus (%s): empty output", d.Get("name").(string)) + } + + output := outputRaw.(*kendra.CreateThesaurusOutput) + + id := aws.ToString(output.Id) + indexId := d.Get("index_id").(string) + + d.SetId(fmt.Sprintf("%s/%s", id, indexId)) + + if _, err := waitThesaurusCreated(ctx, conn, id, indexId, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for Amazon Kendra Thesaurus (%s) create: %s", d.Id(), err) + } + + return resourceThesaurusRead(ctx, d, meta) +} + +func resourceThesaurusRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).KendraConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + id, indexId, err := ThesaurusParseResourceID(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + out, err := FindThesaurusByID(ctx, conn, id, indexId) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Kendra Thesaurus (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("reading Kendra Thesaurus (%s): %s", d.Id(), err) + } + + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Region: meta.(*conns.AWSClient).Region, + Service: "kendra", + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("index/%s/thesaurus/%s", indexId, id), + }.String() + + d.Set("arn", arn) + d.Set("description", out.Description) + d.Set("index_id", out.IndexId) + d.Set("name", out.Name) + d.Set("role_arn", out.RoleArn) + d.Set("status", out.Status) + + if err := d.Set("source_s3_path", flattenSourceS3Path(out.SourceS3Path)); err != nil { + return diag.Errorf("setting complex argument: %s", err) + } + + tags, err := ListTags(ctx, conn, arn) + if err != nil { + return diag.Errorf("listing tags for Kendra Thesaurus (%s): %s", d.Id(), err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("setting tags_all: %s", err) + } + + return nil +} + +func resourceThesaurusUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).KendraConn + + if d.HasChangesExcept("tags", "tags_all") { + id, indexId, err := ThesaurusParseResourceID(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + input := &kendra.UpdateThesaurusInput{ + Id: aws.String(id), + IndexId: aws.String(indexId), + } + + if d.HasChange("description") { + input.Description = aws.String(d.Get("description").(string)) + } + + if d.HasChange("name") { + input.Name = aws.String(d.Get("name").(string)) + } + + if d.HasChange("role_arn") { + input.RoleArn = aws.String(d.Get("role_arn").(string)) + } + + if d.HasChange("source_s3_path") { + input.SourceS3Path = expandSourceS3Path(d.Get("source_s3_path").([]interface{})) + } + + log.Printf("[DEBUG] Updating Kendra Thesaurus (%s): %#v", d.Id(), input) + + _, err = tfresource.RetryWhen( + propagationTimeout, + func() (interface{}, error) { + return conn.UpdateThesaurus(ctx, input) + }, + func(err error) (bool, error) { + var validationException *types.ValidationException + + if errors.As(err, &validationException) && strings.Contains(validationException.ErrorMessage(), validationExceptionMessage) { + return true, err + } + + return false, err + }, + ) + + if err != nil { + return diag.Errorf("updating Kendra Thesaurus(%s): %s", d.Id(), err) + } + + if _, err := waitThesaurusUpdated(ctx, conn, id, indexId, d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("waiting for Kendra Thesaurus (%s) update: %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return diag.FromErr(fmt.Errorf("error updating Kendra Thesaurus (%s) tags: %s", d.Id(), err)) + } + } + + return resourceThesaurusRead(ctx, d, meta) +} + +func resourceThesaurusDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).KendraConn + + log.Printf("[INFO] Deleting Kendra Thesaurus %s", d.Id()) + + id, indexId, err := ThesaurusParseResourceID(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + _, err = conn.DeleteThesaurus(ctx, &kendra.DeleteThesaurusInput{ + Id: aws.String(id), + IndexId: aws.String(indexId), + }) + + var resourceNotFoundException *types.ResourceNotFoundException + if errors.As(err, &resourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("deleting Kendra Thesaurus (%s): %s", d.Id(), err) + } + + if _, err := waitThesaurusDeleted(ctx, conn, id, indexId, d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("waiting for Kendra Thesaurus (%s) to be deleted: %s", d.Id(), err) + } + + return nil +} + +func flattenSourceS3Path(apiObject *types.S3Path) []interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.Bucket; v != nil { + m["bucket"] = aws.ToString(v) + } + + if v := apiObject.Key; v != nil { + m["key"] = aws.ToString(v) + } + + return []interface{}{m} +} + +func expandSourceS3Path(tfList []interface{}) *types.S3Path { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + result := &types.S3Path{} + + if v, ok := tfMap["bucket"].(string); ok && v != "" { + result.Bucket = aws.String(v) + } + + if v, ok := tfMap["key"].(string); ok && v != "" { + result.Key = aws.String(v) + } + + return result +} + +func statusThesaurus(ctx context.Context, conn *kendra.Client, id, indexId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := FindThesaurusByID(ctx, conn, id, indexId) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.Status), nil + } +} + +func waitThesaurusCreated(ctx context.Context, conn *kendra.Client, id, indexId string, timeout time.Duration) (*kendra.DescribeThesaurusOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{string(types.ThesaurusStatusCreating)}, + Target: []string{string(types.ThesaurusStatusActive)}, + Refresh: statusThesaurus(ctx, conn, id, indexId), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*kendra.DescribeThesaurusOutput); ok { + if out.Status == types.ThesaurusStatusFailed { + tfresource.SetLastError(err, errors.New(aws.ToString(out.ErrorMessage))) + } + return out, err + } + + return nil, err +} + +func waitThesaurusUpdated(ctx context.Context, conn *kendra.Client, id, indexId string, timeout time.Duration) (*kendra.DescribeThesaurusOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{string(types.QuerySuggestionsBlockListStatusUpdating)}, + Target: []string{string(types.QuerySuggestionsBlockListStatusActive)}, + Refresh: statusThesaurus(ctx, conn, id, indexId), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*kendra.DescribeThesaurusOutput); ok { + if out.Status == types.ThesaurusStatusActiveButUpdateFailed || out.Status == types.ThesaurusStatusFailed { + tfresource.SetLastError(err, errors.New(aws.ToString(out.ErrorMessage))) + } + return out, err + } + + return nil, err +} + +func waitThesaurusDeleted(ctx context.Context, conn *kendra.Client, id, indexId string, timeout time.Duration) (*kendra.DescribeThesaurusOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{string(types.QuerySuggestionsBlockListStatusDeleting)}, + Target: []string{}, + Refresh: statusThesaurus(ctx, conn, id, indexId), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*kendra.DescribeThesaurusOutput); ok { + if out.Status == types.ThesaurusStatusFailed { + tfresource.SetLastError(err, errors.New(aws.ToString(out.ErrorMessage))) + } + return out, err + } + + return nil, err +} diff --git a/internal/service/kendra/thesaurus_test.go b/internal/service/kendra/thesaurus_test.go new file mode 100644 index 000000000000..db95c36f25c2 --- /dev/null +++ b/internal/service/kendra/thesaurus_test.go @@ -0,0 +1,589 @@ +package kendra_test + +import ( + "context" + "fmt" + "regexp" + "testing" + + 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" + tfkendra "github.com/hashicorp/terraform-provider-aws/internal/service/kendra" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccThesaurus_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kendra_thesaurus.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.KendraEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckThesaurusDestroy, + Steps: []resource.TestStep{ + { + Config: testAccThesaurusConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "index_id", "aws_kendra_index.test", "id"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "source_s3_path.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "source_s3_path.0.bucket", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "source_s3_path.0.key", "aws_s3_object.test", "key"), + resource.TestCheckResourceAttrSet(resourceName, "status"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "kendra", regexp.MustCompile(`index/.+/thesaurus/.+$`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccThesaurus_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kendra_thesaurus.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.KendraEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckThesaurusDestroy, + Steps: []resource.TestStep{ + { + Config: testAccThesaurusConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfkendra.ResourceThesaurus(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccThesaurus_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kendra_thesaurus.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.KendraEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckThesaurusDestroy, + Steps: []resource.TestStep{ + { + Config: testAccThesaurusConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccThesaurusConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccThesaurusConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccThesaurus_description(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kendra_thesaurus.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.KendraEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckThesaurusDestroy, + Steps: []resource.TestStep{ + { + Config: testAccThesaurusConfig_description(rName, "description1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + ), + }, + { + Config: testAccThesaurusConfig_description(rName, "description2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccThesaurusConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", ""), + ), + }, + }, + }) +} + +func testAccThesaurus_name(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kendra_thesaurus.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.KendraEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckThesaurusDestroy, + Steps: []resource.TestStep{ + { + Config: testAccThesaurusConfig_basic(rName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName1), + ), + }, + { + Config: testAccThesaurusConfig_name(rName1, rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccThesaurus_roleARN(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kendra_thesaurus.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.KendraEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckThesaurusDestroy, + Steps: []resource.TestStep{ + { + Config: testAccThesaurusConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), + ), + }, + { + Config: testAccThesaurusConfig_roleARN(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test2", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccThesaurus_sourceS3Path(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kendra_thesaurus.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.KendraEndpointID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.KendraEndpointID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckThesaurusDestroy, + Steps: []resource.TestStep{ + { + Config: testAccThesaurusConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "source_s3_path.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "source_s3_path.0.bucket", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "source_s3_path.0.key", "aws_s3_object.test", "key")), + }, + { + Config: testAccThesaurusConfig_sourceS3Path(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckThesaurusExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "source_s3_path.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "source_s3_path.0.bucket", "aws_s3_bucket.test", "id"), + resource.TestCheckResourceAttrPair(resourceName, "source_s3_path.0.key", "aws_s3_object.test2", "key")), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckThesaurusDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).KendraConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_kendra_thesaurus" { + continue + } + + id, indexId, err := tfkendra.ThesaurusParseResourceID(rs.Primary.ID) + if err != nil { + return err + } + + _, err = tfkendra.FindThesaurusByID(context.TODO(), conn, id, indexId) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + } + + return nil +} + +func testAccCheckThesaurusExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Kendra Thesaurus is set") + } + + id, indexId, err := tfkendra.ThesaurusParseResourceID(rs.Primary.ID) + if err != nil { + return err + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).KendraConn + + _, err = tfkendra.FindThesaurusByID(context.TODO(), conn, id, indexId) + + if err != nil { + return fmt.Errorf("Error describing Kendra Thesaurus: %s", err.Error()) + } + + return nil + } +} + +func testAccThesaurusBaseConfig(rName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +data "aws_iam_policy_document" "assume_role" { + statement { + actions = ["sts:AssumeRole"] + effect = "Allow" + principals { + type = "Service" + identifiers = ["kendra.${data.aws_partition.current.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "test" { + name = %[1]q + path = "/" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +data "aws_iam_policy_document" "test" { + statement { + effect = "Allow" + actions = [ + "kendra:*", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket" + ] + resources = [ + aws_s3_bucket.test.arn, + "${aws_s3_bucket.test.arn}/*" + ] + } +} + +resource "aws_iam_policy" "test" { + name = %[1]q + description = "Allow Kendra to access S3" + policy = data.aws_iam_policy_document.test.json +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = aws_iam_policy.test.arn +} + +resource "aws_kendra_index" "test" { + depends_on = [aws_iam_role_policy_attachment.test] + + name = %[1]q + role_arn = aws_iam_role.test.arn +} + +resource "aws_s3_bucket" "test" { + bucket = %[1]q + force_destroy = true +} + +resource "aws_s3_object" "test" { + bucket = aws_s3_bucket.test.bucket + content = "test" + key = "test/thesaurus.txt" +} +`, rName) +} + +func testAccThesaurusConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccThesaurusBaseConfig(rName), + fmt.Sprintf(` +resource "aws_kendra_thesaurus" "test" { + index_id = aws_kendra_index.test.id + name = %[1]q + role_arn = aws_iam_role.test.arn + + source_s3_path { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + } +} +`, rName)) +} + +func testAccThesaurusConfig_tags1(rName, tag, value string) string { + return acctest.ConfigCompose( + testAccThesaurusBaseConfig(rName), + fmt.Sprintf(` +resource "aws_kendra_thesaurus" "test" { + index_id = aws_kendra_index.test.id + name = %[1]q + role_arn = aws_iam_role.test.arn + + source_s3_path { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + } + + tags = { + %[2]q = %[3]q + } +} +`, rName, tag, value)) +} + +func testAccThesaurusConfig_tags2(rName, tag1, value1, tag2, value2 string) string { + return acctest.ConfigCompose( + testAccThesaurusBaseConfig(rName), + fmt.Sprintf(` +resource "aws_kendra_thesaurus" "test" { + index_id = aws_kendra_index.test.id + name = %[1]q + role_arn = aws_iam_role.test.arn + + source_s3_path { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tag1, value1, tag2, value2)) +} + +func testAccThesaurusConfig_description(rName, description string) string { + return acctest.ConfigCompose( + testAccThesaurusBaseConfig(rName), + fmt.Sprintf(` +resource "aws_kendra_thesaurus" "test" { + description = %[1]q + index_id = aws_kendra_index.test.id + name = %[2]q + role_arn = aws_iam_role.test.arn + + source_s3_path { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + } +} +`, description, rName)) +} + +func testAccThesaurusConfig_name(rName, name string) string { + return acctest.ConfigCompose( + testAccThesaurusBaseConfig(rName), + fmt.Sprintf(` +resource "aws_kendra_thesaurus" "test" { + index_id = aws_kendra_index.test.id + name = %[1]q + role_arn = aws_iam_role.test.arn + + source_s3_path { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + } +} +`, name)) +} + +func testAccThesaurusConfig_roleARN(rName string) string { + return acctest.ConfigCompose( + testAccThesaurusBaseConfig(rName), + fmt.Sprintf(` +resource "aws_iam_role" "test2" { + name = "%[1]s-2" + path = "/" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +resource "aws_iam_policy" "test2" { + name = "%[1]s-2" + description = "Allow Kendra to access S3" + policy = data.aws_iam_policy_document.test.json +} + +resource "aws_iam_role_policy_attachment" "test2" { + role = aws_iam_role.test2.name + policy_arn = aws_iam_policy.test2.arn +} + +resource "aws_kendra_thesaurus" "test" { + index_id = aws_kendra_index.test.id + name = %[1]q + role_arn = aws_iam_role.test2.arn + + source_s3_path { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + } +} +`, rName)) +} + +func testAccThesaurusConfig_sourceS3Path(rName string) string { + return acctest.ConfigCompose( + testAccThesaurusBaseConfig(rName), + fmt.Sprintf(` +resource "aws_s3_object" "test2" { + bucket = aws_s3_bucket.test.bucket + content = "test2" + key = "test/new_suggestions.txt" +} + +resource "aws_kendra_thesaurus" "test" { + index_id = aws_kendra_index.test.id + name = %[1]q + role_arn = aws_iam_role.test.arn + + source_s3_path { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test2.key + } +} +`, rName)) +} diff --git a/website/docs/r/kendra_thesaurus.html.markdown b/website/docs/r/kendra_thesaurus.html.markdown new file mode 100644 index 000000000000..f37c7399ee91 --- /dev/null +++ b/website/docs/r/kendra_thesaurus.html.markdown @@ -0,0 +1,72 @@ +--- +subcategory: "Kendra" +layout: "aws" +page_title: "AWS: aws_kendra_thesaurus" +description: |- + Terraform resource for managing an AWS Kendra Thesaurus. +--- + +# Resource: aws_kendra_thesaurus + +Terraform resource for managing an AWS Kendra Thesaurus. + +## Example Usage + +```terraform +resource "aws_kendra_thesaurus" "example" { + index_id = aws_kendra_index.example.id + name = "Example" + role_arn = aws_iam_role.example.arn + + source_s3_path { + bucket = aws_s3_bucket.example.id + key = aws_s3_object.example.key + } + + tags = { + Name = "Example Kendra Thesaurus" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `index_id`- (Required, Forces new resource) The identifier of the index for a thesaurus. +* `name` - (Required) The identifier of the index for a thesaurus. +* `role_arn` - (Required) The IAM (Identity and Access Management) role used to access the thesaurus file in S3. +* `source_s3_path` - (Required) The S3 path where your thesaurus file sits in S3. Detailed below. + +The `source_s3_path` configuration block supports the following arguments: + +* `bucket` - (Required) The name of the S3 bucket that contains the file. +* `key` - (Required) The name of the file. + +The following arguments are optional: + +* `description` - (Optional) The description for a thesaurus. +* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the thesaurus. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). + +## Timeouts + +`aws_kendra_thesaurus` provides the following [Timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) configuration options: + +* `create` - (Optional, Default: `30m`) +* `update` - (Optional, Default: `30m`) +* `delete` - (Optional, Default: `30m`) + +## Import + +`aws_kendra_thesaurus` can be imported using the unique identifiers of the thesaurus and index separated by a slash (`/`), e.g., + +``` +$ terraform import aws_kendra_thesaurus.example thesaurus-123456780/idx-8012925589 +```