Skip to content
Open
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
3 changes: 3 additions & 0 deletions .changelog/44089.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_dynamodb_table_item: Add import support
```
100 changes: 100 additions & 0 deletions internal/service/dynamodb/table_item.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ func resourceTableItem() *schema.Resource {
UpdateWithoutTimeout: resourceTableItemUpdate,
DeleteWithoutTimeout: resourceTableItemDelete,

Importer: &schema.ResourceImporter{
StateContext: resourceTableItemImportState,
},

Schema: map[string]*schema.Schema{
"hash_key": {
Type: schema.TypeString,
Expand Down Expand Up @@ -309,3 +313,99 @@ func expandTableItemQueryKey(attrs map[string]awstypes.AttributeValue, hashKey,

return queryKey
}

func createTableItemKeyAttr(attrTypes map[string]awstypes.ScalarAttributeType, name, value string) (awstypes.AttributeValue, error) {
attrType, ok := attrTypes[name]
if !ok {
return nil, fmt.Errorf("key %s not found in attribute definitions", name)
}
switch attrType {
case awstypes.ScalarAttributeTypeS:
return &awstypes.AttributeValueMemberS{Value: value}, nil
case awstypes.ScalarAttributeTypeN:
return &awstypes.AttributeValueMemberN{Value: value}, nil
case awstypes.ScalarAttributeTypeB:
data, err := itypes.Base64Decode(value)
if err != nil {
return nil, fmt.Errorf("invalid base64 value for binary attribute %s: %s", name, err)
}
return &awstypes.AttributeValueMemberB{Value: data}, nil
default:
return nil, fmt.Errorf("unsupported attribute type: %s", attrType)
}
}

func resourceTableItemImportState(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
conn := meta.(*conns.AWSClient).DynamoDBClient(ctx)

idParts := strings.Split(d.Id(), "|")
if len(idParts) < 3 || len(idParts) > 4 {
return nil, fmt.Errorf("unexpected format for import ID (%s), expected tableName|hashKeyName|hashKeyValue[|rangeKeyValue]", d.Id())
}

tableName := idParts[0]
hashKey := idParts[1]
hashValue := idParts[2]
var rangeValue string
if len(idParts) == 4 {
rangeValue = idParts[3]
}

output, err := conn.DescribeTable(ctx, &dynamodb.DescribeTableInput{
TableName: aws.String(tableName),
})
if err != nil {
return nil, fmt.Errorf("describing table %s: %s", tableName, err)
}

var rangeKey string
if rangeValue != "" {
var found bool
for _, elem := range output.Table.KeySchema {
if aws.ToString(elem.AttributeName) != hashKey && elem.KeyType == awstypes.KeyTypeRange {
rangeKey = aws.ToString(elem.AttributeName)
found = true
break
}
}
if !found {
return nil, fmt.Errorf("import ID contains range key value but table %s does not have a range key", tableName)
}
}

attrTypes := map[string]awstypes.ScalarAttributeType{}
for _, v := range output.Table.AttributeDefinitions {
attrTypes[aws.ToString(v.AttributeName)] = v.AttributeType
}

key := map[string]awstypes.AttributeValue{}
key[hashKey], err = createTableItemKeyAttr(attrTypes, hashKey, hashValue)
if err != nil {
return nil, err
}
if rangeValue != "" {
key[rangeKey], err = createTableItemKeyAttr(attrTypes, rangeKey, rangeValue)
if err != nil {
return nil, err
}
}

item, err := findTableItemByTwoPartKey(ctx, conn, tableName, key)
if err != nil {
return nil, fmt.Errorf("reading DynamoDB Table Item: %s: %s", d.Id(), err)
}
itemAttrs, err := flattenTableItemAttributes(item)
if err != nil {
return nil, fmt.Errorf("flattening item attributes: %s", err)
}

d.Set(names.AttrTableName, tableName)
d.Set("hash_key", hashKey)
if rangeKey != "" {
d.Set("range_key", rangeKey)
}
d.Set("item", itemAttrs)
d.SetId(tableItemCreateResourceID(tableName, hashKey, rangeKey, item))

return []*schema.ResourceData{d}, nil
}
32 changes: 28 additions & 4 deletions internal/service/dynamodb/table_item_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package dynamodb_test

import (
"context"
"encoding/json"
"fmt"
"testing"

Expand All @@ -28,13 +29,13 @@ func TestAccDynamoDBTableItem_basic(t *testing.T) {

tableName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
hashKey := "hashKey"
itemContent := `{
itemContent := testAccNormalizeItemJSON(t, `{
"hashKey": {"S": "something"},
"one": {"N": "11111"},
"two": {"N": "22222"},
"three": {"N": "33333"},
"four": {"N": "44444"}
}`
}`)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
Expand All @@ -52,6 +53,11 @@ func TestAccDynamoDBTableItem_basic(t *testing.T) {
acctest.CheckResourceAttrEquivalentJSON("aws_dynamodb_table_item.test", "item", itemContent),
),
},
{
ResourceName: "aws_dynamodb_table_item.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand All @@ -63,14 +69,14 @@ func TestAccDynamoDBTableItem_rangeKey(t *testing.T) {
tableName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
hashKey := "hashKey"
rangeKey := "rangeKey"
itemContent := `{
itemContent := testAccNormalizeItemJSON(t, `{
"hashKey": {"S": "something"},
"rangeKey": {"S": "something-else"},
"one": {"N": "11111"},
"two": {"N": "22222"},
"three": {"N": "33333"},
"four": {"N": "44444"}
}`
}`)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
Expand All @@ -89,6 +95,11 @@ func TestAccDynamoDBTableItem_rangeKey(t *testing.T) {
acctest.CheckResourceAttrEquivalentJSON("aws_dynamodb_table_item.test", "item", itemContent),
),
},
{
ResourceName: "aws_dynamodb_table_item.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
Expand Down Expand Up @@ -717,3 +728,16 @@ ITEM
}
`, tableName, hashKey, content)
}

func testAccNormalizeItemJSON(t *testing.T, item string) string {
t.Helper()
var data any
if err := json.Unmarshal([]byte(item), &data); err != nil {
t.Fatalf("failed to unmarshal JSON: %s", err)
}
normalized, err := json.Marshal(data)
if err != nil {
t.Fatalf("failed to marshal JSON: %s", err)
}
return string(normalized)
}
15 changes: 14 additions & 1 deletion website/docs/r/dynamodb_table_item.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,17 @@ This resource exports the following attributes in addition to the arguments abov

## Import

You cannot import DynamoDB table items.
In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import DynamoDB table items using the `table_name|hash_key_name|hash_key_value[|range_key_value]`. For example:

```terraform
import {
to = aws_dynamodb_table_item.example
id = "example-table|exampleHashKey|exampleHashValue"
}
```

Using `terraform import`, import DynamoDB table items using the `table_name|hash_key_name|hash_key_value[|range_key_value]`. For example:

```console
% terraform import aws_dynamodb_table_item.example 'example-table|exampleHashKey|exampleHashValue'
```
Loading