diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 241ec80a435..5a13ff725dc 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -11,6 +11,7 @@ import ( "log" "net" "os" + "os/exec" "reflect" "regexp" "strconv" @@ -2418,6 +2419,34 @@ resource "aws_subnet" "test" { ) } +func ConfigVPCWithSubnetsEnableDNSHostnames(rName string, subnetCount int) string { + return ConfigCompose( + ConfigAvailableAZsNoOptInDefaultExclude(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + count = %[2]d + + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[count.index] + cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) + + tags = { + Name = %[1]q + } +} +`, rName, subnetCount), + ) +} + func ConfigVPCWithSubnetsIPv6(rName string, subnetCount int) string { return ConfigCompose( ConfigAvailableAZsNoOptInDefaultExclude(), @@ -2588,6 +2617,18 @@ func SkipIfEnvVarNotSet(t *testing.T, key string) string { return v } +// SkipIfExeNotOnPath skips the current test if the specified executable is not found in the directories named by the PATH environment variable. +// The absolute path to the executable is returned. +func SkipIfExeNotOnPath(t *testing.T, file string) string { + t.Helper() + + v, err := exec.LookPath(file) + if err != nil { + t.Skipf("File %s not found on PATH, skipping test: %s", v, err) + } + return v +} + // RunSerialTests1Level runs test cases in parallel, optionally sleeping between each. func RunSerialTests1Level(t *testing.T, testCases map[string]func(*testing.T), d time.Duration) { t.Helper() diff --git a/internal/framework/flex/list.go b/internal/framework/flex/list.go index a7eecfaeebd..602cf311c00 100644 --- a/internal/framework/flex/list.go +++ b/internal/framework/flex/list.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" ) func ExpandFrameworkStringList(ctx context.Context, v basetypes.ListValuable) []*string { @@ -72,6 +73,10 @@ func FlattenFrameworkStringValueList[T ~string](ctx context.Context, v []T) type return output } +func FlattenFrameworkStringValueListOfString(ctx context.Context, vs []string) fwtypes.ListValueOf[basetypes.StringValue] { + return fwtypes.ListValueOf[basetypes.StringValue]{ListValue: FlattenFrameworkStringValueList(ctx, vs)} +} + // FlattenFrameworkStringValueListLegacy is the Plugin Framework variant of FlattenStringValueList. // A nil slice is converted to an empty (non-null) List. func FlattenFrameworkStringValueListLegacy[T ~string](_ context.Context, vs []T) types.List { diff --git a/internal/service/bedrockagent/agent.go b/internal/service/bedrockagent/agent.go index 4d0e40931e8..b25cc440327 100644 --- a/internal/service/bedrockagent/agent.go +++ b/internal/service/bedrockagent/agent.go @@ -353,14 +353,14 @@ func (r *agentResource) Delete(ctx context.Context, request resource.DeleteReque return } - if _, err := waitAgentDeleted(ctx, conn, agentID, r.DeleteTimeout(ctx, data.Timeouts)); err != nil { - response.Diagnostics.AddError(fmt.Sprintf("waiting for Bedrock Agent (%s) delete", agentID), err.Error()) + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting Bedrock Agent (%s)", agentID), err.Error()) return } - if err != nil { - response.Diagnostics.AddError(fmt.Sprintf("deleting Bedrock Agent (%s)", data.ID.ValueString()), err.Error()) + if _, err := waitAgentDeleted(ctx, conn, agentID, r.DeleteTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Bedrock Agent (%s) delete", agentID), err.Error()) return } diff --git a/internal/service/bedrockagent/bedrockagent_test.go b/internal/service/bedrockagent/bedrockagent_test.go index 10d1e470ccc..c3938f7a64f 100644 --- a/internal/service/bedrockagent/bedrockagent_test.go +++ b/internal/service/bedrockagent/bedrockagent_test.go @@ -14,11 +14,11 @@ func TestAccBedrockAgent_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "KnowledgeBase": { - "basic": testAccKnowledgeBase_basic, - "disappears": testAccKnowledgeBase_disappears, - "rds": testAccKnowledgeBase_rds, - "update": testAccKnowledgeBase_update, - "tags": testAccKnowledgeBase_tags, + "basicRDS": testAccKnowledgeBase_basicRDS, + "disappears": testAccKnowledgeBase_disappears, + "tags": testAccKnowledgeBase_tags, + "basicOpenSearch": testAccKnowledgeBase_basicOpenSearch, + "updateOpenSearch": testAccKnowledgeBase_updateOpenSearch, }, } diff --git a/internal/service/bedrockagent/knowledge_base.go b/internal/service/bedrockagent/knowledge_base.go index 1ac723842f4..a2c0e6a0c92 100644 --- a/internal/service/bedrockagent/knowledge_base.go +++ b/internal/service/bedrockagent/knowledge_base.go @@ -5,6 +5,8 @@ package bedrockagent import ( "context" + "errors" + "fmt" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -13,21 +15,21 @@ import ( "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -45,10 +47,6 @@ func newKnowledgeBaseResource(context.Context) (resource.ResourceWithConfigure, return r, nil } -const ( - ResNameKnowledgeBase = "Knowledge Base" -) - type knowledgeBaseResource struct { framework.ResourceWithConfigure framework.WithImportByID @@ -77,9 +75,6 @@ func (r *knowledgeBaseResource) Schema(ctx context.Context, request resource.Sch CustomType: fwtypes.ListOfStringType, ElementType: types.StringType, Computed: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, }, names.AttrID: framework.IDAttribute(), "name": schema.StringAttribute{ @@ -325,14 +320,14 @@ func (r *knowledgeBaseResource) Schema(ctx context.Context, request resource.Sch } func (r *knowledgeBaseResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { - conn := r.Meta().BedrockAgentClient(ctx) - var data knowledgeBaseResourceModel response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) if response.Diagnostics.HasError() { return } + conn := r.Meta().BedrockAgentClient(ctx) + input := &bedrockagent.CreateKnowledgeBaseInput{} response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) if response.Diagnostics.HasError() { @@ -340,6 +335,7 @@ func (r *knowledgeBaseResource) Create(ctx context.Context, request resource.Cre } // Additional fields. + input.ClientToken = aws.String(id.UniqueId()) input.Tags = getTagsIn(ctx) outputRaw, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) { @@ -347,77 +343,64 @@ func (r *knowledgeBaseResource) Create(ctx context.Context, request resource.Cre }, errCodeValidationException, "cannot assume role") if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.BedrockAgent, create.ErrActionCreating, ResNameKnowledgeBase, data.Name.String(), err), - err.Error(), - ) + response.Diagnostics.AddError("creating Bedrock Agent Knowledge Base", err.Error()) + return } - knowledgebase := outputRaw.(*bedrockagent.CreateKnowledgeBaseOutput).KnowledgeBase - data.KnowledgeBaseARN = fwflex.StringToFramework(ctx, knowledgebase.KnowledgeBaseArn) - data.KnowledgeBaseID = fwflex.StringToFramework(ctx, knowledgebase.KnowledgeBaseId) + kb := outputRaw.(*bedrockagent.CreateKnowledgeBaseOutput).KnowledgeBase + data.KnowledgeBaseARN = fwflex.StringToFramework(ctx, kb.KnowledgeBaseArn) + data.KnowledgeBaseID = fwflex.StringToFramework(ctx, kb.KnowledgeBaseId) + + kb, err = waitKnowledgeBaseCreated(ctx, conn, data.KnowledgeBaseID.ValueString(), r.CreateTimeout(ctx, data.Timeouts)) - createTimeout := r.CreateTimeout(ctx, data.Timeouts) - knowledgebase, err = waitKnowledgeBaseCreated(ctx, conn, data.KnowledgeBaseID.ValueString(), createTimeout) if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.BedrockAgent, create.ErrActionWaitingForCreation, ResNameKnowledgeBase, data.Name.String(), err), - err.Error(), - ) - return - } + response.Diagnostics.AddError(fmt.Sprintf("waiting for Bedrock Agent Knowledge Base (%s) create", data.KnowledgeBaseID.ValueString()), err.Error()) - response.Diagnostics.Append(fwflex.Flatten(ctx, knowledgebase, &data)...) - if response.Diagnostics.HasError() { return } // Set values for unknowns after creation is complete. - data.CreatedAt = fwflex.TimeToFramework(ctx, knowledgebase.CreatedAt) - data.UpdatedAt = fwflex.TimeToFramework(ctx, knowledgebase.UpdatedAt) + data.CreatedAt = fwflex.TimeToFramework(ctx, kb.CreatedAt) + data.FailureReasons = fwflex.FlattenFrameworkStringValueListOfString(ctx, kb.FailureReasons) + data.UpdatedAt = fwflex.TimeToFramework(ctx, kb.UpdatedAt) response.Diagnostics.Append(response.State.Set(ctx, data)...) } func (r *knowledgeBaseResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { - conn := r.Meta().BedrockAgentClient(ctx) - var data knowledgeBaseResourceModel response.Diagnostics.Append(request.State.Get(ctx, &data)...) if response.Diagnostics.HasError() { return } - knowledgebase, err := findKnowledgeBaseByID(ctx, conn, data.KnowledgeBaseID.ValueString()) + conn := r.Meta().BedrockAgentClient(ctx) + + kb, err := findKnowledgeBaseByID(ctx, conn, data.KnowledgeBaseID.ValueString()) if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) response.State.RemoveResource(ctx) + return } + if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.BedrockAgent, create.ErrActionSetting, ResNameKnowledgeBase, data.KnowledgeBaseID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Bedrock Agent Knowledge Base (%s)", data.KnowledgeBaseID.ValueString()), err.Error()) + return } - response.Diagnostics.Append(fwflex.Flatten(ctx, knowledgebase, &data)...) + response.Diagnostics.Append(fwflex.Flatten(ctx, kb, &data)...) if response.Diagnostics.HasError() { return } - // Set values for unknowns after creation is complete. - data.CreatedAt = fwflex.TimeToFramework(ctx, knowledgebase.CreatedAt) - data.UpdatedAt = fwflex.TimeToFramework(ctx, knowledgebase.UpdatedAt) - response.Diagnostics.Append(response.State.Set(ctx, &data)...) } func (r *knowledgeBaseResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { - conn := r.Meta().BedrockAgentClient(ctx) - var old, new knowledgeBaseResourceModel response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) if response.Diagnostics.HasError() { @@ -428,9 +411,11 @@ func (r *knowledgeBaseResource) Update(ctx context.Context, request resource.Upd return } - if !new.Name.Equal(old.Name) || - !new.Description.Equal(old.Description) || + conn := r.Meta().BedrockAgentClient(ctx) + + if !new.Description.Equal(old.Description) || !new.KnowledgeBaseConfiguration.Equal(old.KnowledgeBaseConfiguration) || + !new.Name.Equal(old.Name) || !new.StorageConfiguration.Equal(old.StorageConfiguration) { input := &bedrockagent.UpdateKnowledgeBaseInput{} response.Diagnostics.Append(fwflex.Expand(ctx, new, input)...) @@ -443,94 +428,79 @@ func (r *knowledgeBaseResource) Update(ctx context.Context, request resource.Upd }, errCodeValidationException, "cannot assume role") if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.BedrockAgent, create.ErrActionUpdating, ResNameKnowledgeBase, new.KnowledgeBaseID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("updating Bedrock Agent Knowledge Base (%s)", new.KnowledgeBaseID.ValueString()), err.Error()) + return } - } - updateTimeout := r.UpdateTimeout(ctx, new.Timeouts) - knowledgebase, err := waitKnowledgeBaseUpdated(ctx, conn, new.KnowledgeBaseID.ValueString(), updateTimeout) - if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.BedrockAgent, create.ErrActionWaitingForUpdate, ResNameKnowledgeBase, new.KnowledgeBaseID.String(), err), - err.Error(), - ) - return - } + kb, err := waitKnowledgeBaseUpdated(ctx, conn, new.KnowledgeBaseID.ValueString(), r.UpdateTimeout(ctx, new.Timeouts)) - response.Diagnostics.Append(fwflex.Flatten(ctx, knowledgebase, &new)...) - if response.Diagnostics.HasError() { - return - } + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Bedrock Agent Knowledge Base (%s) create", new.KnowledgeBaseID.ValueString()), err.Error()) - // Set values for unknowns after update is complete. - new.CreatedAt = fwflex.TimeToFramework(ctx, knowledgebase.CreatedAt) - new.UpdatedAt = fwflex.TimeToFramework(ctx, knowledgebase.UpdatedAt) + return + } + + new.FailureReasons = fwflex.FlattenFrameworkStringValueListOfString(ctx, kb.FailureReasons) + new.UpdatedAt = fwflex.TimeToFramework(ctx, kb.UpdatedAt) + } else { + new.FailureReasons = old.FailureReasons + new.UpdatedAt = old.UpdatedAt + } response.Diagnostics.Append(response.State.Set(ctx, &new)...) } func (r *knowledgeBaseResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { - conn := r.Meta().BedrockAgentClient(ctx) - var data knowledgeBaseResourceModel response.Diagnostics.Append(request.State.Get(ctx, &data)...) if response.Diagnostics.HasError() { return } - in := &bedrockagent.DeleteKnowledgeBaseInput{ + conn := r.Meta().BedrockAgentClient(ctx) + + _, err := conn.DeleteKnowledgeBase(ctx, &bedrockagent.DeleteKnowledgeBaseInput{ KnowledgeBaseId: aws.String(data.KnowledgeBaseID.ValueString()), - } + }) - _, err := conn.DeleteKnowledgeBase(ctx, in) + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return - } - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.BedrockAgent, create.ErrActionDeleting, ResNameKnowledgeBase, data.KnowledgeBaseID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("deleting Bedrock Agent Knowledge Base (%s)", data.KnowledgeBaseID.ValueString()), err.Error()) + return } - deleteTimeout := r.DeleteTimeout(ctx, data.Timeouts) - _, err = waitKnowledgeBaseDeleted(ctx, conn, data.KnowledgeBaseID.ValueString(), deleteTimeout) + _, err = waitKnowledgeBaseDeleted(ctx, conn, data.KnowledgeBaseID.ValueString(), r.DeleteTimeout(ctx, data.Timeouts)) + if err != nil { - response.Diagnostics.AddError( - create.ProblemStandardMessage(names.BedrockAgent, create.ErrActionWaitingForDeletion, ResNameKnowledgeBase, data.KnowledgeBaseID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("waiting for Bedrock Agent Knowledge Base (%s) delete", data.KnowledgeBaseID.ValueString()), err.Error()) + return } } -func (r *knowledgeBaseResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) -} - func (r *knowledgeBaseResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { r.SetTagsAll(ctx, request, response) } func waitKnowledgeBaseCreated(ctx context.Context, conn *bedrockagent.Client, id string, timeout time.Duration) (*awstypes.KnowledgeBase, error) { stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(awstypes.KnowledgeBaseStatusCreating), - Target: enum.Slice(awstypes.KnowledgeBaseStatusActive), - Refresh: statusKnowledgeBase(ctx, conn, id), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, + Pending: enum.Slice(awstypes.KnowledgeBaseStatusCreating), + Target: enum.Slice(awstypes.KnowledgeBaseStatusActive), + Refresh: statusKnowledgeBase(ctx, conn, id), + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*awstypes.KnowledgeBase); ok { - return out, err + + if output, ok := outputRaw.(*awstypes.KnowledgeBase); ok { + tfresource.SetLastError(err, errors.Join(tfslices.ApplyToAll(output.FailureReasons, errors.New)...)) + + return output, err } return nil, err @@ -538,17 +508,18 @@ func waitKnowledgeBaseCreated(ctx context.Context, conn *bedrockagent.Client, id func waitKnowledgeBaseUpdated(ctx context.Context, conn *bedrockagent.Client, id string, timeout time.Duration) (*awstypes.KnowledgeBase, error) { stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(awstypes.KnowledgeBaseStatusUpdating), - Target: enum.Slice(awstypes.KnowledgeBaseStatusActive), - Refresh: statusKnowledgeBase(ctx, conn, id), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, + Pending: enum.Slice(awstypes.KnowledgeBaseStatusUpdating), + Target: enum.Slice(awstypes.KnowledgeBaseStatusActive), + Refresh: statusKnowledgeBase(ctx, conn, id), + Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*awstypes.KnowledgeBase); ok { - return out, err + + if output, ok := outputRaw.(*awstypes.KnowledgeBase); ok { + tfresource.SetLastError(err, errors.Join(tfslices.ApplyToAll(output.FailureReasons, errors.New)...)) + + return output, err } return nil, err @@ -563,8 +534,11 @@ func waitKnowledgeBaseDeleted(ctx context.Context, conn *bedrockagent.Client, id } outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*awstypes.KnowledgeBase); ok { - return out, err + + if output, ok := outputRaw.(*awstypes.KnowledgeBase); ok { + tfresource.SetLastError(err, errors.Join(tfslices.ApplyToAll(output.FailureReasons, errors.New)...)) + + return output, err } return nil, err @@ -573,6 +547,7 @@ func waitKnowledgeBaseDeleted(ctx context.Context, conn *bedrockagent.Client, id func statusKnowledgeBase(ctx context.Context, conn *bedrockagent.Client, id string) retry.StateRefreshFunc { return func() (interface{}, string, error) { output, err := findKnowledgeBaseByID(ctx, conn, id) + if tfresource.NotFound(err) { return nil, "", nil } @@ -586,27 +561,28 @@ func statusKnowledgeBase(ctx context.Context, conn *bedrockagent.Client, id stri } func findKnowledgeBaseByID(ctx context.Context, conn *bedrockagent.Client, id string) (*awstypes.KnowledgeBase, error) { - in := &bedrockagent.GetKnowledgeBaseInput{ + input := &bedrockagent.GetKnowledgeBaseInput{ KnowledgeBaseId: aws.String(id), } - out, err := conn.GetKnowledgeBase(ctx, in) - if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } + output, err := conn.GetKnowledgeBase(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } + } + if err != nil { return nil, err } - if out == nil || out.KnowledgeBase == nil { - return nil, tfresource.NewEmptyResultError(in) + if output == nil || output.KnowledgeBase == nil { + return nil, tfresource.NewEmptyResultError(input) } - return out.KnowledgeBase, nil + return output.KnowledgeBase, nil } type knowledgeBaseResourceModel struct { diff --git a/internal/service/bedrockagent/knowledge_base_test.go b/internal/service/bedrockagent/knowledge_base_test.go index 167f502d6f9..16b450a5101 100644 --- a/internal/service/bedrockagent/knowledge_base_test.go +++ b/internal/service/bedrockagent/knowledge_base_test.go @@ -19,8 +19,10 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func testAccKnowledgeBase_basic(t *testing.T) { - acctest.Skip(t, "Bedrock Agent Knowledge Base requires external configuration of a vector index") +// Prerequisites: +// * psql run via null_resource/provisioner "local-exec" +func testAccKnowledgeBase_basicRDS(t *testing.T) { + acctest.SkipIfExeNotOnPath(t, "psql") ctx := acctest.Context(t) var knowledgebase types.KnowledgeBase @@ -34,10 +36,16 @@ func testAccKnowledgeBase_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.BedrockAgentServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckKnowledgeBaseDestroy(ctx), + ExternalProviders: map[string]resource.ExternalProvider{ + "null": { + Source: "hashicorp/null", + VersionConstraint: "3.2.2", + }, + }, + CheckDestroy: testAccCheckKnowledgeBaseDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccKnowledgeBaseConfig_basic(rName, foundationModel), + Config: testAccKnowledgeBaseConfig_basicRDS(rName, foundationModel), Check: resource.ComposeTestCheckFunc( testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), @@ -45,27 +53,29 @@ func testAccKnowledgeBase_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "knowledge_base_configuration.0.vector_knowledge_base_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "knowledge_base_configuration.0.type", "VECTOR"), resource.TestCheckResourceAttr(resourceName, "storage_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.type", "OPENSEARCH_SERVERLESS"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.opensearch_serverless_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.opensearch_serverless_configuration.0.vector_index_name", "bedrock-knowledge-base-default-index"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.opensearch_serverless_configuration.0.field_mapping.#", "1"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.opensearch_serverless_configuration.0.field_mapping.0.vector_field", "bedrock-knowledge-base-default-vector"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.opensearch_serverless_configuration.0.field_mapping.0.text_field", "AMAZON_BEDROCK_TEXT_CHUNK"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.opensearch_serverless_configuration.0.field_mapping.0.metadata_field", "AMAZON_BEDROCK_METADATA"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.type", "RDS"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.table_name", "bedrock_integration.bedrock_kb"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.#", "1"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.0.vector_field", "embedding"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.0.text_field", "chunks"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.0.metadata_field", "metadata"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.0.primary_key_field", "id"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{}, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) } -func testAccKnowledgeBase_rds(t *testing.T) { - acctest.Skip(t, "Bedrock Agent Knowledge Base requires external configuration of a vector index") +// Prerequisites: +// * psql run via null_resource/provisioner "local-exec" +func testAccKnowledgeBase_disappears(t *testing.T) { + acctest.SkipIfExeNotOnPath(t, "psql") ctx := acctest.Context(t) var knowledgebase types.KnowledgeBase @@ -88,42 +98,84 @@ func testAccKnowledgeBase_rds(t *testing.T) { CheckDestroy: testAccCheckKnowledgeBaseDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccKnowledgeBaseConfig_rds(rName, foundationModel), + Config: testAccKnowledgeBaseConfig_basicRDS(rName, foundationModel), Check: resource.ComposeTestCheckFunc( testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), - resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), - resource.TestCheckResourceAttr(resourceName, "knowledge_base_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "knowledge_base_configuration.0.vector_knowledge_base_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "knowledge_base_configuration.0.type", "VECTOR"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.type", "RDS"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.table_name", "bedrock_integration.bedrock_kb"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.#", "1"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.0.vector_field", "embedding"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.0.text_field", "chunks"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.0.metadata_field", "metadata"), - resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.rds_configuration.0.field_mapping.0.primary_key_field", "id"), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbedrockagent.ResourceKnowledgeBase, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +// Prerequisites: +// * psql run via null_resource/provisioner "local-exec" +func testAccKnowledgeBase_tags(t *testing.T) { + acctest.SkipIfExeNotOnPath(t, "psql") + + ctx := acctest.Context(t) + var knowledgebase types.KnowledgeBase + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_bedrockagent_knowledge_base.test" + foundationModel := "amazon.titan-embed-text-v1" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.BedrockAgentServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "null": { + Source: "hashicorp/null", + VersionConstraint: "3.2.2", + }, + }, + CheckDestroy: testAccCheckKnowledgeBaseDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccKnowledgeBaseConfig_tags1(rName, foundationModel, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{}, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccKnowledgeBaseConfig_tags2(rName, foundationModel, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccKnowledgeBaseConfig_tags1(rName, foundationModel, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), }, }, }) } -func testAccKnowledgeBase_disappears(t *testing.T) { +func testAccKnowledgeBase_basicOpenSearch(t *testing.T) { acctest.Skip(t, "Bedrock Agent Knowledge Base requires external configuration of a vector index") ctx := acctest.Context(t) var knowledgebase types.KnowledgeBase rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_bedrockagent_knowledge_base.test" - foundationModel := "amazon.titan-embed-g1-text-02" + foundationModel := "amazon.titan-embed-text-v1" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -134,10 +186,9 @@ func testAccKnowledgeBase_disappears(t *testing.T) { CheckDestroy: testAccCheckKnowledgeBaseDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccKnowledgeBaseConfig_basic(rName, foundationModel), + Config: testAccKnowledgeBaseConfig_basicOpenSearch(rName, foundationModel), Check: resource.ComposeTestCheckFunc( testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfbedrockagent.ResourceKnowledgeBase, resourceName), resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), resource.TestCheckResourceAttr(resourceName, "knowledge_base_configuration.#", "1"), resource.TestCheckResourceAttr(resourceName, "knowledge_base_configuration.0.vector_knowledge_base_configuration.#", "1"), @@ -151,13 +202,18 @@ func testAccKnowledgeBase_disappears(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.opensearch_serverless_configuration.0.field_mapping.0.text_field", "AMAZON_BEDROCK_TEXT_CHUNK"), resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.opensearch_serverless_configuration.0.field_mapping.0.metadata_field", "AMAZON_BEDROCK_METADATA"), ), - ExpectNonEmptyPlan: true, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{}, }, }, }) } -func testAccKnowledgeBase_update(t *testing.T) { +func testAccKnowledgeBase_updateOpenSearch(t *testing.T) { acctest.Skip(t, "Bedrock Agent Knowledge Base requires external configuration of a vector index") ctx := acctest.Context(t) @@ -175,7 +231,7 @@ func testAccKnowledgeBase_update(t *testing.T) { CheckDestroy: testAccCheckKnowledgeBaseDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccKnowledgeBaseConfig_basic(rName, foundationModel), + Config: testAccKnowledgeBaseConfig_basicOpenSearch(rName, foundationModel), Check: resource.ComposeTestCheckFunc( testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), resource.TestCheckResourceAttr(resourceName, "name", rName), @@ -200,7 +256,7 @@ func testAccKnowledgeBase_update(t *testing.T) { ImportStateVerifyIgnore: []string{}, }, { - Config: testAccKnowledgeBaseConfig_update(rName, foundationModel), + Config: testAccKnowledgeBaseConfig_updateOpenSearch(rName, foundationModel), Check: resource.ComposeTestCheckFunc( testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), resource.TestCheckResourceAttr(resourceName, "name", rName+"-updated"), @@ -229,58 +285,6 @@ func testAccKnowledgeBase_update(t *testing.T) { }) } -func testAccKnowledgeBase_tags(t *testing.T) { - acctest.Skip(t, "Bedrock Agent Knowledge Base requires external configuration of a vector index") - - ctx := acctest.Context(t) - var knowledgebase types.KnowledgeBase - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_bedrockagent_knowledge_base.test" - foundationModel := "amazon.titan-embed-g1-text-02" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(ctx, t) - }, - ErrorCheck: acctest.ErrorCheck(t, names.BedrockAgentServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckKnowledgeBaseDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccKnowledgeBaseConfig_tags1(rName, foundationModel, "key1", "value1"), - Check: resource.ComposeTestCheckFunc( - testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{}, - }, - { - Config: testAccKnowledgeBaseConfig_tags2(rName, foundationModel, "key1", "value1updated", "key2", "value2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), - ), - }, - { - Config: testAccKnowledgeBaseConfig_tags1(rName, foundationModel, "key2", "value2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), - ), - }, - }, - }) -} - func testAccCheckKnowledgeBaseDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).BedrockAgentClient(ctx) @@ -328,34 +332,11 @@ func testAccCheckKnowledgeBaseExists(ctx context.Context, n string, v *types.Kno } } -func testAccKnowledgeBaseConfig_baseOpenSearch(rName, model string) string { - return fmt.Sprintf(` +func testAccKnowledgeBase_baseRDS(rName, model string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnetsEnableDNSHostnames(rName, 2), fmt.Sprintf(` data "aws_partition" "current" {} data "aws_region" "current" {} -resource "aws_opensearchserverless_security_policy" "test" { - name = %[1]q - type = "encryption" - - policy = jsonencode({ - "Rules" = [ - { - "Resource" = [ - "collection/%[1]s" - ], - "ResourceType" = "collection" - } - ], - "AWSOwnedKey" = true - }) -} - -resource "aws_opensearchserverless_collection" "test" { - name = %[1]q - - depends_on = [aws_opensearchserverless_security_policy.test] -} - resource "aws_iam_role" "test" { name = %[1]q path = "/service-role/" @@ -397,61 +378,129 @@ resource "aws_iam_role_policy" "test" { "Resource": [ "arn:${data.aws_partition.current.partition}:bedrock:${data.aws_region.current.name}::foundation-model/%[2]s" ] - }, - { - "Action": [ - "aoss:APIAccessAll" - ], - "Effect": "Allow", - "Resource": [ - "${aws_opensearchserverless_collection.test.arn}" - ] } ] } POLICY } -`, rName, model) +resource "aws_iam_role_policy_attachment" "rds_data_full_access" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_partition.current.partition}:policy/AmazonRDSDataFullAccess" } -func testAccKnowledgeBaseConfig_basic(rName, model string) string { - return acctest.ConfigCompose(testAccKnowledgeBaseConfig_baseOpenSearch(rName, model), fmt.Sprintf(` -resource "aws_bedrockagent_knowledge_base" "test" { - name = %[1]q - role_arn = aws_iam_role.test.arn +resource "aws_iam_role_policy_attachment" "secrets_manager_read_write" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_partition.current.partition}:policy/SecretsManagerReadWrite" +} - knowledge_base_configuration { - vector_knowledge_base_configuration { - embedding_model_arn = "arn:${data.aws_partition.current.partition}:bedrock:${data.aws_region.current.name}::foundation-model/%[2]s" - } - type = "VECTOR" +resource "aws_rds_cluster" "test" { + cluster_identifier = %[1]q + engine = "aurora-postgresql" + engine_mode = "provisioned" + engine_version = "15.4" + database_name = "test" + master_username = "test" + manage_master_user_password = true + enable_http_endpoint = true + vpc_security_group_ids = [aws_security_group.test.id] + skip_final_snapshot = true + db_subnet_group_name = aws_db_subnet_group.test.name + + serverlessv2_scaling_configuration { + max_capacity = 1.0 + min_capacity = 0.5 } +} - storage_configuration { - type = "OPENSEARCH_SERVERLESS" - opensearch_serverless_configuration { - collection_arn = aws_opensearchserverless_collection.test.arn - vector_index_name = "bedrock-knowledge-base-default-index" - field_mapping { - vector_field = "bedrock-knowledge-base-default-vector" - text_field = "AMAZON_BEDROCK_TEXT_CHUNK" - metadata_field = "AMAZON_BEDROCK_METADATA" - } - } +resource "aws_rds_cluster_instance" "test" { + cluster_identifier = aws_rds_cluster.test.id + instance_class = "db.serverless" + engine = aws_rds_cluster.test.engine + engine_version = aws_rds_cluster.test.engine_version + publicly_accessible = true +} + +resource "aws_db_subnet_group" "test" { + name = %[1]q + subnet_ids = aws_subnet.test[*].id +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q } +} - depends_on = [aws_iam_role_policy.test] +resource "aws_default_route_table" "test" { + default_route_table_id = aws_vpc.test.default_route_table_id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.test.id + } + + tags = { + Name = %[1]q + } +} + +resource "aws_security_group" "test" { + name = %[1]q + vpc_id = aws_vpc.test.id + + ingress { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = %[1]q + } +} + +data "aws_secretsmanager_secret_version" "test" { + secret_id = aws_rds_cluster.test.master_user_secret[0].secret_arn + version_stage = "AWSCURRENT" + depends_on = [aws_rds_cluster.test] +} + +resource "null_resource" "db_setup" { + depends_on = [aws_rds_cluster_instance.test, aws_rds_cluster.test, data.aws_secretsmanager_secret_version.test] + + provisioner "local-exec" { + command = <