Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changelog/45470.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_s3vectors_index: Add `metadata_configuration` block
```

```release-note:enhancement
resource/aws_s3vectors_index: Add `encryption_configuration` block
```
63 changes: 54 additions & 9 deletions internal/service/s3vectors/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3vectors"
awstypes "github.com/aws/aws-sdk-go-v2/service/s3vectors/types"
"github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier"
"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/setplanmodifier"
"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/retry"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
Expand Down Expand Up @@ -74,6 +79,10 @@ func (r *indexResource) Schema(ctx context.Context, request resource.SchemaReque
stringplanmodifier.RequiresReplace(),
},
},
names.AttrEncryptionConfiguration: framework.ResourceOptionalComputedListOfObjectsAttribute[indexEncryptionConfigurationModel](ctx, 1, nil,
listplanmodifier.UseStateForUnknown(),
listplanmodifier.RequiresReplace(),
),
"index_arn": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
Expand All @@ -95,6 +104,31 @@ func (r *indexResource) Schema(ctx context.Context, request resource.SchemaReque
},
},
},
Blocks: map[string]schema.Block{
"metadata_configuration": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[indexMetadataConfigurationModel](ctx),
PlanModifiers: []planmodifier.List{
listplanmodifier.RequiresReplace(),
},
Validators: []validator.List{
listvalidator.SizeAtMost(1),
},
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"non_filterable_metadata_keys": schema.SetAttribute{
Required: true,
ElementType: types.StringType,
PlanModifiers: []planmodifier.Set{
setplanmodifier.RequiresReplace(),
},
Validators: []validator.Set{
setvalidator.SizeBetween(1, 10),
},
},
},
},
},
},
}
}

Expand Down Expand Up @@ -248,13 +282,24 @@ func findIndex(ctx context.Context, conn *s3vectors.Client, input *s3vectors.Get

type indexResourceModel struct {
framework.WithRegionModel
CreationTime timetypes.RFC3339 `tfsdk:"creation_time"`
DataType fwtypes.StringEnum[awstypes.DataType] `tfsdk:"data_type"`
Dimension types.Int32 `tfsdk:"dimension"`
DistanceMetric fwtypes.StringEnum[awstypes.DistanceMetric] `tfsdk:"distance_metric"`
IndexARN types.String `tfsdk:"index_arn"`
IndexName types.String `tfsdk:"index_name"`
Tags tftags.Map `tfsdk:"tags"`
TagsAll tftags.Map `tfsdk:"tags_all"`
VectorBucketName types.String `tfsdk:"vector_bucket_name"`
CreationTime timetypes.RFC3339 `tfsdk:"creation_time"`
DataType fwtypes.StringEnum[awstypes.DataType] `tfsdk:"data_type"`
Dimension types.Int32 `tfsdk:"dimension"`
DistanceMetric fwtypes.StringEnum[awstypes.DistanceMetric] `tfsdk:"distance_metric"`
EncryptionConfiguration fwtypes.ListNestedObjectValueOf[indexEncryptionConfigurationModel] `tfsdk:"encryption_configuration"`
IndexARN types.String `tfsdk:"index_arn"`
IndexName types.String `tfsdk:"index_name"`
MetadataConfiguration fwtypes.ListNestedObjectValueOf[indexMetadataConfigurationModel] `tfsdk:"metadata_configuration"`
Tags tftags.Map `tfsdk:"tags"`
TagsAll tftags.Map `tfsdk:"tags_all"`
VectorBucketName types.String `tfsdk:"vector_bucket_name"`
}

type indexEncryptionConfigurationModel struct {
KMSKeyARN fwtypes.ARN `tfsdk:"kms_key_arn"`
SseType fwtypes.StringEnum[awstypes.SseType] `tfsdk:"sse_type"`
}

type indexMetadataConfigurationModel struct {
NonFilterableMetadataKeys fwtypes.SetOfString `tfsdk:"non_filterable_metadata_keys"`
}
223 changes: 223 additions & 0 deletions internal/service/s3vectors/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package s3vectors_test
import (
"context"
"fmt"
"strings"
"testing"

"github.com/YakDriver/regexache"
Expand Down Expand Up @@ -79,6 +80,132 @@ func TestAccS3VectorsIndex_basic(t *testing.T) {
})
}

func TestAccS3VectorsIndex_encryptionConfigurationAES256(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.Index
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_s3vectors_index.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
testAccPreCheck(ctx, t)
},
ErrorCheck: acctest.ErrorCheck(t, names.S3VectorsServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckIndexDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccIndexConfig_encryptionConfigurationAES256(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIndexExists(ctx, resourceName, &v),
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
},
},
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrEncryptionConfiguration).AtSliceIndex(0).AtMapKey("sse_type"), tfknownvalue.StringExact(awstypes.SseTypeAes256)),
},
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "index_arn"),
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "index_arn",
},
},
})
}

func TestAccS3VectorsIndex_encryptionConfigurationCMK(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.Index
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_s3vectors_index.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
testAccPreCheck(ctx, t)
},
ErrorCheck: acctest.ErrorCheck(t, names.S3VectorsServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckIndexDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccIndexConfig_encryptionConfigurationCMK(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIndexExists(ctx, resourceName, &v),
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
},
},
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrEncryptionConfiguration).AtSliceIndex(0).AtMapKey("sse_type"), tfknownvalue.StringExact(awstypes.SseTypeAwsKms)),
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrEncryptionConfiguration).AtSliceIndex(0).AtMapKey(names.AttrKMSKeyARN), knownvalue.NotNull()),
},
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "index_arn"),
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "index_arn",
},
},
})
}

func TestAccS3VectorsIndex_metadataConfiguration(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.Index
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_s3vectors_index.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
testAccPreCheck(ctx, t)
},
ErrorCheck: acctest.ErrorCheck(t, names.S3VectorsServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckIndexDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccIndexConfig_metadataConfiguration(rName, []string{acctest.CtKey1, acctest.CtKey2}),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckIndexExists(ctx, resourceName, &v),
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
},
},
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("metadata_configuration").AtSliceIndex(0).AtMapKey("non_filterable_metadata_keys"), knownvalue.SetExact(
[]knownvalue.Check{
knownvalue.StringExact(acctest.CtKey1),
knownvalue.StringExact(acctest.CtKey2),
},
)),
},
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "index_arn"),
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "index_arn",
},
},
})
}

func TestAccS3VectorsIndex_disappears(t *testing.T) {
ctx := acctest.Context(t)
var v awstypes.Index
Expand Down Expand Up @@ -229,3 +356,99 @@ resource "aws_s3vectors_index" "test" {
}
`, rName)
}

func testAccIndexConfig_encryptionConfigurationAES256(rName string) string {
return fmt.Sprintf(`
resource "aws_s3vectors_vector_bucket" "test" {
vector_bucket_name = "%[1]s-bucket"
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 = 2
distance_metric = "euclidean"

encryption_configuration {
sse_type = "AES256"
}
}
`, rName)
}

func testAccIndexConfig_encryptionConfigurationCMK(rName string) string {
return fmt.Sprintf(`
data "aws_caller_identity" "current" {}
data "aws_partition" "current" {}

data "aws_iam_policy_document" "kms_key_policy" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["indexing.s3vectors.amazonaws.com"]
}
actions = ["kms:Decrypt"]
resources = ["*"]
}
statement {
effect = "Allow"
principals {
type = "AWS"
identifiers = ["arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"]
}
actions = ["kms:*"]
resources = ["*"]
}
}

resource "aws_kms_key" "test" {
deletion_window_in_days = 7
policy = data.aws_iam_policy_document.kms_key_policy.json
}

resource "aws_s3vectors_vector_bucket" "test" {
vector_bucket_name = "%[1]s-bucket"
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 = 2
distance_metric = "euclidean"

encryption_configuration {
kms_key_arn = aws_kms_key.test.arn
sse_type = "aws:kms"
}
}
`, rName)
}

func testAccIndexConfig_metadataConfiguration(rName string, keys []string) string {
return fmt.Sprintf(`
resource "aws_s3vectors_vector_bucket" "test" {
vector_bucket_name = "%[1]s-bucket"
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 = 2
distance_metric = "euclidean"

metadata_configuration {
non_filterable_metadata_keys = ["%[2]s"]
}
}
`, rName, strings.Join(keys, `", "`))
}
15 changes: 15 additions & 0 deletions website/docs/r/s3vectors_index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,23 @@ The following arguments are required:
The following arguments are optional:

* `region` - (Optional) Region where this resource will be [managed](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference).
* `encryption_configuration` - (Optional, Forces new resource) Block for encryption configuration for the vector index. See [`encyption_configuration` block](#encyption_configuration-block) below.
* `metadata_configuration` - (Optional, Forces new resource) Block for metadata configuration for the vector index. See [`metadata_configuration` block](#metadata_configuration-block) below.
* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.

### `encyption_configuration` block

The `encryption_configuration` block supports the following attributes:

* `kms_key_id` - (Optional, Forces new resource) AWS Key Management Service (KMS) customer managed key ID to use for the encryption configuration. This parameter is allowed if and only if `sse_type` is set to `aws:kms`.
* `sse_type` - (Optional, Forces new resource) Type of encryption to use. Valid values: `AES256`, `aws:kms`. Defaults to `AES256`.

### `metadata_configuration` block

The `metadata_configuration` block supports the following attributes:

* `non_filterable_metadata_keys` - (Required, Forces new resource) List of non-filterable metadata keys.

## Attribute Reference

This resource exports the following attributes in addition to the arguments above:
Expand Down
Loading