diff --git a/.changelog/27504.txt b/.changelog/27504.txt new file mode 100644 index 00000000000..b33991cccff --- /dev/null +++ b/.changelog/27504.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_dynamodb_table_item +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 22b2674e274..abe6856cb80 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -530,7 +530,8 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_directory_service_directory": ds.DataSourceDirectory(), - "aws_dynamodb_table": dynamodb.DataSourceTable(), + "aws_dynamodb_table": dynamodb.DataSourceTable(), + "aws_dynamodb_table_item": dynamodb.DataSourceTableItem(), "aws_ami": ec2.DataSourceAMI(), "aws_ami_ids": ec2.DataSourceAMIIDs(), diff --git a/internal/service/dynamodb/table_item_data_source.go b/internal/service/dynamodb/table_item_data_source.go new file mode 100644 index 00000000000..856870251f0 --- /dev/null +++ b/internal/service/dynamodb/table_item_data_source.go @@ -0,0 +1,115 @@ +package dynamodb + +import ( + "context" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func DataSourceTableItem() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceTableItemRead, + + Schema: map[string]*schema.Schema{ + "expression_attribute_names": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "item": { + Type: schema.TypeString, + Computed: true, + }, + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateTableItem, + }, + "projection_expression": { + Type: schema.TypeString, + Optional: true, + }, + "table_name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +const ( + DSNameTableItem = "Table Item Data Source" +) + +func dataSourceTableItemRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).DynamoDBConn + + tableName := d.Get("table_name").(string) + key, err := ExpandTableItemAttributes(d.Get("key").(string)) + + if err != nil { + return diag.FromErr(err) + } + + id := buildTableItemDataSourceID(tableName, key) + in := &dynamodb.GetItemInput{ + ConsistentRead: aws.Bool(true), + Key: key, + TableName: aws.String(tableName), + } + + if v, ok := d.GetOk("expression_attribute_names"); ok && len(v.(map[string]interface{})) > 0 { + in.ExpressionAttributeNames = flex.ExpandStringMap(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("projection_expression"); ok { + in.ProjectionExpression = aws.String(v.(string)) + } + + out, err := conn.GetItemWithContext(ctx, in) + + if err != nil { + return create.DiagError(names.DynamoDB, create.ErrActionReading, DSNameTableItem, id, err) + } + + if out.Item == nil { + return create.DiagError(names.DynamoDB, create.ErrActionReading, DSNameTableItem, id, err) + } + + d.SetId(id) + + d.Set("expression_attribute_names", aws.StringValueMap(in.ExpressionAttributeNames)) + d.Set("projection_expression", in.ProjectionExpression) + d.Set("table_name", tableName) + + itemAttrs, err := flattenTableItemAttributes(out.Item) + + if err != nil { + return create.DiagError(names.DynamoDB, create.ErrActionReading, DSNameTableItem, id, err) + } + + d.Set("item", itemAttrs) + + return nil +} + +func buildTableItemDataSourceID(tableName string, attrs map[string]*dynamodb.AttributeValue) string { + id := []string{tableName} + + for key, element := range attrs { + id = append(id, key, verify.Base64Encode(element.B)) + id = append(id, aws.StringValue(element.S)) + id = append(id, aws.StringValue(element.N)) + } + + return strings.Join(id, "|") +} diff --git a/internal/service/dynamodb/table_item_data_source_test.go b/internal/service/dynamodb/table_item_data_source_test.go new file mode 100644 index 00000000000..90f3b2f4b66 --- /dev/null +++ b/internal/service/dynamodb/table_item_data_source_test.go @@ -0,0 +1,227 @@ +package dynamodb_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/dynamodb" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccDynamoDBTableItemDataSource_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_dynamodb_table_item.test" + hashKey := "hashKey" + itemContent := `{ + "hashKey": {"S": "something"}, + "one": {"N": "11111"}, + "two": {"N": "22222"}, + "three": {"N": "33333"}, + "four": {"N": "44444"} +}` + key := `{ + "hashKey": {"S": "something"} +}` + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(dynamodb.EndpointsID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccTableItemDataSourceConfig_basic(rName, hashKey, itemContent, key), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrEquivalentJSON(dataSourceName, "item", itemContent), + resource.TestCheckResourceAttr(dataSourceName, "table_name", rName), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableItemDataSource_projectionExpression(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_dynamodb_table_item.test" + hashKey := "hashKey" + projectionExpression := "one,two" + itemContent := `{ + "hashKey": {"S": "something"}, + "one": {"N": "11111"}, + "two": {"N": "22222"}, + "three": {"N": "33333"}, + "four": {"N": "44444"} +}` + key := `{ + "hashKey": {"S": "something"} +}` + + expected := `{ + "one": {"N": "11111"}, + "two": {"N": "22222"} +}` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(dynamodb.EndpointsID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccTableItemDataSourceConfig_projectionExpression(rName, hashKey, itemContent, projectionExpression, key), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrEquivalentJSON(dataSourceName, "item", expected), + resource.TestCheckResourceAttr(dataSourceName, "table_name", rName), + resource.TestCheckResourceAttr(dataSourceName, "projection_expression", projectionExpression), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableItemDataSource_expressionAttributeNames(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_dynamodb_table_item.test" + hashKey := "hashKey" + itemContent := `{ + "hashKey": {"S": "something"}, + "one": {"N": "11111"}, + "Percentile": {"N": "22222"} +}` + key := `{ + "hashKey": {"S": "something"} +}` + + expected := `{ + "Percentile": {"N": "22222"} +}` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(dynamodb.EndpointsID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccTableItemDataSourceConfig_expressionAttributeNames(rName, hashKey, itemContent, key), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrEquivalentJSON(dataSourceName, "item", expected), + resource.TestCheckResourceAttr(dataSourceName, "table_name", rName), + resource.TestCheckResourceAttr(dataSourceName, "projection_expression", "#P"), + ), + }, + }, + }) +} + +func testAccTableItemDataSourceConfig_basic(tableName, hashKey, item string, key string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 10 + write_capacity = 10 + hash_key = %[2]q + + attribute { + name = %[3]q + type = "S" + } +} + +resource "aws_dynamodb_table_item" "test" { + table_name = aws_dynamodb_table.test.name + hash_key = aws_dynamodb_table.test.hash_key + + item = <