diff --git a/.changelog/45468.txt b/.changelog/45468.txt new file mode 100644 index 000000000000..e6df71453053 --- /dev/null +++ b/.changelog/45468.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_bedrockagent_knowledge_base: Add storage_configuration.s3_vectors_configuration block +``` diff --git a/internal/service/bedrockagent/bedrockagent_test.go b/internal/service/bedrockagent/bedrockagent_test.go index ae39ed34b5c1..c0d81029d5d7 100644 --- a/internal/service/bedrockagent/bedrockagent_test.go +++ b/internal/service/bedrockagent/bedrockagent_test.go @@ -20,6 +20,7 @@ func TestAccBedrockAgent_serial(t *testing.T) { "OpenSearchBasic": testAccKnowledgeBase_OpenSearch_basic, "OpenSearchUpdate": testAccKnowledgeBase_OpenSearch_update, "OpenSearchSupplementalDataStorage": testAccKnowledgeBase_OpenSearch_supplementalDataStorage, + "S3Vectors": testAccKnowledgeBase_S3Vectors, }, "DataSource": { acctest.CtBasic: testAccDataSource_basic, diff --git a/internal/service/bedrockagent/knowledge_base.go b/internal/service/bedrockagent/knowledge_base.go index dfabab25bf3f..d22b0b8596ab 100644 --- a/internal/service/bedrockagent/knowledge_base.go +++ b/internal/service/bedrockagent/knowledge_base.go @@ -17,7 +17,9 @@ 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-validators/objectvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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" @@ -496,6 +498,53 @@ func (r *knowledgeBaseResource) Schema(ctx context.Context, request resource.Sch }, }, }, + "s3_vectors_configuration": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[s3VectorsConfigurationModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "index_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("index_name"), path.MatchRelative().AtParent().AtName("vector_bucket_arn")), + }, + }, + "index_name": schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("vector_bucket_arn")), + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("index_arn")), + }, + }, + "vector_bucket_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("index_name")), + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("index_arn")), + }, + }, + }, + Validators: []validator.Object{ + objectvalidator.AtLeastOneOf(path.MatchRelative().AtName("index_arn"), path.MatchRelative().AtName("index_name")), + }, + }, + }, }, }, }, @@ -840,6 +889,7 @@ type storageConfigurationModel struct { PineconeConfiguration fwtypes.ListNestedObjectValueOf[pineconeConfigurationModel] `tfsdk:"pinecone_configuration"` RDSConfiguration fwtypes.ListNestedObjectValueOf[rdsConfigurationModel] `tfsdk:"rds_configuration"` RedisEnterpriseCloudConfiguration fwtypes.ListNestedObjectValueOf[redisEnterpriseCloudConfigurationModel] `tfsdk:"redis_enterprise_cloud_configuration"` + S3VectorsConfiguration fwtypes.ListNestedObjectValueOf[s3VectorsConfigurationModel] `tfsdk:"s3_vectors_configuration"` Type types.String `tfsdk:"type"` } @@ -895,3 +945,9 @@ type redisEnterpriseCloudFieldMappingModel struct { TextField types.String `tfsdk:"text_field"` VectorField types.String `tfsdk:"vector_field"` } + +type s3VectorsConfigurationModel struct { + IndexARN fwtypes.ARN `tfsdk:"index_arn"` + IndexName types.String `tfsdk:"index_name"` + VectorBucketARN fwtypes.ARN `tfsdk:"vector_bucket_arn"` +} diff --git a/internal/service/bedrockagent/knowledge_base_test.go b/internal/service/bedrockagent/knowledge_base_test.go index b12b4f9ecae4..2ca610e70f54 100644 --- a/internal/service/bedrockagent/knowledge_base_test.go +++ b/internal/service/bedrockagent/knowledge_base_test.go @@ -404,6 +404,69 @@ func testAccKnowledgeBase_OpenSearch_supplementalDataStorage(t *testing.T) { }) } +func testAccKnowledgeBase_S3Vectors(t *testing.T) { + ctx := acctest.Context(t) + + var knowledgebase types.KnowledgeBase + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_bedrockagent_knowledge_base.test" + foundationModel := "amazon.titan-embed-text-v2:0" + + 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_S3VectorsByIndexARN(rName, foundationModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + Check: resource.ComposeTestCheckFunc( + testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), + resource.TestCheckResourceAttrPair(resourceName, names.AttrRoleARN, "aws_iam_role.test", names.AttrARN), + 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", "S3_VECTORS"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.s3_vectors_configuration.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_configuration.0.s3_vectors_configuration.0.index_arn", "aws_s3vectors_index.test", "index_arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccKnowledgeBaseConfig_S3VectorsByIndexName(rName, foundationModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + Check: resource.ComposeTestCheckFunc( + testAccCheckKnowledgeBaseExists(ctx, resourceName, &knowledgebase), + resource.TestCheckResourceAttrPair(resourceName, names.AttrRoleARN, "aws_iam_role.test", names.AttrARN), + 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", "S3_VECTORS"), + resource.TestCheckResourceAttr(resourceName, "storage_configuration.0.s3_vectors_configuration.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "storage_configuration.0.s3_vectors_configuration.0.index_name", "aws_s3vectors_index.test", "index_name"), + ), + }, + }, + }) +} + func testAccCheckKnowledgeBaseDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).BedrockAgentClient(ctx) @@ -946,3 +1009,138 @@ resource "aws_bedrockagent_knowledge_base" "test" { } `, rName, model)) } + +func testAccKnowledgeBaseConfig_S3VectorsBase(rName string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} +data "aws_partition" "current" {} + +data "aws_iam_policy_document" "assume_role_bedrock" { + statement { + effect = "Allow" + principals { + type = "Service" + identifiers = ["bedrock.amazonaws.com"] + } + actions = ["sts:AssumeRole"] + } +} + +data "aws_iam_policy_document" "bedrock" { + statement { + effect = "Allow" + actions = ["bedrock:InvokeModel"] + resources = ["*"] + } + statement { + effect = "Allow" + actions = ["s3:ListBucket", "s3:GetObject"] + resources = ["*"] + } + statement { + effect = "Allow" + actions = [ + "s3vectors:GetIndex", + "s3vectors:QueryVectors", + "s3vectors:PutVectors", + "s3vectors:GetVectors", + "s3vectors:DeleteVectors" + ] + resources = ["*"] + } +} + +resource "aws_iam_role" "test" { + assume_role_policy = data.aws_iam_policy_document.assume_role_bedrock.json + name = %[1]q +} + +resource "aws_iam_role_policy" "test" { + role = aws_iam_role.test.name + policy = data.aws_iam_policy_document.bedrock.json +} + +resource "aws_s3vectors_vector_bucket" "test" { + vector_bucket_name = %[1]q + force_destroy = true +} + +resource "aws_s3vectors_index" "test" { + index_name = %[1]q + vector_bucket_name = aws_s3vectors_vector_bucket.test.vector_bucket_name + + data_type = "float32" + dimension = 256 + distance_metric = "euclidean" +} +`, rName) +} + +func testAccKnowledgeBaseConfig_S3VectorsByIndexARN(rName, model string) string { + return acctest.ConfigCompose( + testAccKnowledgeBaseConfig_S3VectorsBase(rName), + fmt.Sprintf(` +resource "aws_bedrockagent_knowledge_base" "test" { + depends_on = [ + aws_iam_role_policy.test, + ] + name = %[1]q + role_arn = aws_iam_role.test.arn + + knowledge_base_configuration { + vector_knowledge_base_configuration { + embedding_model_arn = "arn:${data.aws_partition.current.partition}:bedrock:${data.aws_region.current.region}::foundation-model/%[2]s" + embedding_model_configuration { + bedrock_embedding_model_configuration { + dimensions = 256 + embedding_data_type = "FLOAT32" + } + } + } + type = "VECTOR" + } + + storage_configuration { + type = "S3_VECTORS" + s3_vectors_configuration { + index_arn = aws_s3vectors_index.test.index_arn + } + } +} +`, rName, model)) +} + +func testAccKnowledgeBaseConfig_S3VectorsByIndexName(rName, model string) string { + return acctest.ConfigCompose( + testAccKnowledgeBaseConfig_S3VectorsBase(rName), + fmt.Sprintf(` +resource "aws_bedrockagent_knowledge_base" "test" { + depends_on = [ + aws_iam_role_policy.test, + ] + name = %[1]q + role_arn = aws_iam_role.test.arn + + knowledge_base_configuration { + vector_knowledge_base_configuration { + embedding_model_arn = "arn:${data.aws_partition.current.partition}:bedrock:${data.aws_region.current.region}::foundation-model/%[2]s" + embedding_model_configuration { + bedrock_embedding_model_configuration { + dimensions = 256 + embedding_data_type = "FLOAT32" + } + } + } + type = "VECTOR" + } + + storage_configuration { + type = "S3_VECTORS" + s3_vectors_configuration { + index_name = aws_s3vectors_index.test.index_name + vector_bucket_arn = aws_s3vectors_vector_bucket.test.vector_bucket_arn + } + } +} +`, rName, model)) +} diff --git a/website/docs/r/bedrockagent_knowledge_base.html.markdown b/website/docs/r/bedrockagent_knowledge_base.html.markdown index d25ab2183c34..daee77e14b7c 100644 --- a/website/docs/r/bedrockagent_knowledge_base.html.markdown +++ b/website/docs/r/bedrockagent_knowledge_base.html.markdown @@ -83,6 +83,48 @@ resource "aws_bedrockagent_knowledge_base" "example" { } ``` +### S3 Vectors Configuration + +```terraform +resource "aws_s3vectors_vector_bucket" "example" { + vector_bucket_name = "example-bucket" +} + +resource "aws_s3vectors_index" "example" { + index_name = "example-index" + vector_bucket_name = aws_s3vectors_vector_bucket.example.vector_bucket_name + + data_type = "float32" + dimension = 256 + distance_metric = "euclidean" +} + +resource "aws_bedrockagent_knowledge_base" "example" { + name = "example-s3vectors-kb" + role_arn = aws_iam_role.example.arn + + knowledge_base_configuration { + vector_knowledge_base_configuration { + embedding_model_arn = "arn:aws:bedrock:us-west-2::foundation-model/amazon.titan-embed-text-v2:0" + embedding_model_configuration { + bedrock_embedding_model_configuration { + dimensions = 256 + embedding_data_type = "FLOAT32" + } + } + } + type = "VECTOR" + } + + storage_configuration { + type = "S3_VECTORS" + s3_vectors_configuration { + index_arn = aws_s3vectors_index.example.index_arn + } + } +} +``` + ## Argument Reference The following arguments are required: @@ -149,11 +191,12 @@ The `s3_location` configuration block supports the following arguments: The `storage_configuration` configuration block supports the following arguments: -* `type` - (Required) Vector store service in which the knowledge base is stored. Valid Values: `OPENSEARCH_SERVERLESS`, `PINECONE`, `REDIS_ENTERPRISE_CLOUD`, `RDS`. +* `type` - (Required) Vector store service in which the knowledge base is stored. Valid Values: `OPENSEARCH_SERVERLESS`, `PINECONE`, `REDIS_ENTERPRISE_CLOUD`, `RDS`, `S3_VECTORS`. * `opensearch_serverless_configuration` - (Optional) The storage configuration of the knowledge base in Amazon OpenSearch Service. See [`opensearch_serverless_configuration` block](#opensearch_serverless_configuration-block) for details. * `pinecone_configuration` - (Optional) The storage configuration of the knowledge base in Pinecone. See [`pinecone_configuration` block](#pinecone_configuration-block) for details. * `rds_configuration` - (Optional) Details about the storage configuration of the knowledge base in Amazon RDS. For more information, see [Create a vector index in Amazon RDS](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-setup.html). See [`rds_configuration` block](#rds_configuration-block) for details. * `redis_enterprise_cloud_configuration` - (Optional) The storage configuration of the knowledge base in Redis Enterprise Cloud. See [`redis_enterprise_cloud_configuration` block](#redis_enterprise_cloud_configuration-block) for details. +* `s3_vectors_configuration` - (Optional) The storage configuration of the knowledge base in Amazon S3 Vectors. See [`s3_vectors_configuration` block](#s3_vectors_configuration-block) for details. ### `opensearch_serverless_configuration` block @@ -204,6 +247,15 @@ The `redis_enterprise_cloud_configuration` configuration block supports the foll * `vector_field` - (Required) Name of the field in which Amazon Bedrock stores the vector embeddings for your data sources. * `vector_index_name` - (Required) Name of the vector index. +### `s3_vectors_configuration` block + +The `s3_vectors_configuration` configuration block supports the following arguments. +Either `index_arn`, or both `index_name` and `vector_bucket_arn` must be specified. + +* `index_arn` - (Optional) ARN of the S3 Vectors index. Conflicts with `index_name` and `vector_bucket_arn`. +* `index_name` - (Optional) Name of the S3 Vectors index. Must be specified with `vector_bucket_arn`. Conflicts with `index_arn`. +* `vector_bucket_arn` - (Optional) ARN of the S3 Vectors vector bucket. Must be specified with `index_name`. Conflicts with `index_arn`. + ## Attribute Reference This resource exports the following attributes in addition to the arguments above: