Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Data Source: aws_dynamodb_table_item #27504

Merged
merged 17 commits into from
Nov 7, 2022
3 changes: 3 additions & 0 deletions .changelog/27504.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-data-source
aws_dynamodb_table_item
```
3 changes: 2 additions & 1 deletion internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
115 changes: 115 additions & 0 deletions internal/service/dynamodb/table_item_data_source.go
Original file line number Diff line number Diff line change
@@ -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, "|")
}
227 changes: 227 additions & 0 deletions internal/service/dynamodb/table_item_data_source_test.go
Original file line number Diff line number Diff line change
@@ -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 = <<ITEM
%[4]s
ITEM
}

data "aws_dynamodb_table_item" "test" {
table_name = aws_dynamodb_table.test.name

key = <<KEY
%[5]s
KEY
depends_on = [aws_dynamodb_table_item.test]
}
`, tableName, hashKey, hashKey, item, key)
}

func testAccTableItemDataSourceConfig_projectionExpression(tableName, hashKey, item, projectionExpression, 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 = <<ITEM
%[4]s
ITEM
}

data "aws_dynamodb_table_item" "test" {
table_name = aws_dynamodb_table.test.name
projection_expression = %[5]q
key = <<KEY
%[6]s
KEY
depends_on = [aws_dynamodb_table_item.test]
}
`, tableName, hashKey, hashKey, item, projectionExpression, key)
}

func testAccTableItemDataSourceConfig_expressionAttributeNames(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 = <<ITEM
%[4]s
ITEM
}

data "aws_dynamodb_table_item" "test" {
table_name = aws_dynamodb_table.test.name
expression_attribute_names = {
"#P" = "Percentile"
}
projection_expression = "#P"
key = <<KEY
%[5]s
KEY
depends_on = [aws_dynamodb_table_item.test]
}
`, tableName, hashKey, hashKey, item, key)
}
Loading