Skip to content

Instantly share code, notes, and snippets.

@mutovkin
Created June 29, 2022 18:31
Show Gist options
  • Save mutovkin/4912e83b2f16dd4ac8cf80ce2c51673e to your computer and use it in GitHub Desktop.
Save mutovkin/4912e83b2f16dd4ac8cf80ce2c51673e to your computer and use it in GitHub Desktop.
DynamoDB Time Formatting Example
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
var awsConfig aws.Config
var dynamoDbClient *dynamodb.Client
func initAwsConfig(profile, region string) error {
var err error
var optFns [](func(*config.LoadOptions) error)
optFns = append(optFns, config.WithRegion(region))
optFns = append(optFns, config.WithSharedConfigProfile(profile))
// Using the SDK's default configuration, loading additional config
// and credentials values from the environment variables, shared
// credentials, and shared configuration files
customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
if service == dynamodb.ServiceID && region == "localhost" {
return aws.Endpoint{
URL: "http://localhost:8000",
}, nil
}
// returning EndpointNotFoundError will allow the service to fallback to it's default resolution
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
})
optFns = append(optFns, config.WithEndpointResolver(customResolver))
awsConfig, err = config.LoadDefaultConfig(context.TODO(), optFns...)
if err != nil {
log.Error().Err(err).Msg("failed to load SDK config")
return err
}
return nil
}
func configureDynamoDbClient(profile, region string) error {
if err := initAwsConfig(profile, region); err != nil {
return err
}
log.Debug().
Interface("AWSConfig", awsConfig.Region).
Str("Region", region).
Msg("ConfigureDynamoDbClient")
dynamoDbClient = dynamodb.NewFromConfig(awsConfig)
return nil
}
func CreateTable(client *dynamodb.Client, tableName string) error {
tableInput := dynamodb.CreateTableInput{
TableName: aws.String(tableName),
KeySchema: []types.KeySchemaElement{
{
AttributeName: aws.String("PK"),
KeyType: "HASH",
},
{
AttributeName: aws.String("SK"),
KeyType: "RANGE",
},
},
GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{
{
IndexName: aws.String("GlobalIndex"),
KeySchema: []types.KeySchemaElement{
{
AttributeName: aws.String("GSI1PK"),
KeyType: "HASH",
},
{
AttributeName: aws.String("GSI1SK"),
KeyType: "RANGE",
},
},
Projection: &types.Projection{
ProjectionType: "ALL",
},
ProvisionedThroughput: &types.ProvisionedThroughput{
ReadCapacityUnits: aws.Int64(10),
WriteCapacityUnits: aws.Int64(10),
},
},
},
AttributeDefinitions: []types.AttributeDefinition{
{
AttributeName: aws.String("PK"),
AttributeType: "S",
},
{
AttributeName: aws.String("SK"),
AttributeType: "S",
},
{
AttributeName: aws.String("GSI1PK"),
AttributeType: "S",
},
{
AttributeName: aws.String("GSI1SK"),
AttributeType: "S",
},
},
ProvisionedThroughput: &types.ProvisionedThroughput{
ReadCapacityUnits: aws.Int64(10),
WriteCapacityUnits: aws.Int64(10),
},
}
_, err := client.CreateTable(context.TODO(), &tableInput)
if err != nil {
return err
}
return nil
}
func DeleteTable(client *dynamodb.Client, tableName string) error {
deleteTableInput := dynamodb.DeleteTableInput{
TableName: aws.String(tableName),
}
_, err := client.DeleteTable(context.TODO(), &deleteTableInput)
if err != nil {
return err
}
return nil
}
func createTableIfNotFound(client *dynamodb.Client, tableName string) error {
isFound := false
resp, err := client.ListTables(context.TODO(), &dynamodb.ListTablesInput{
Limit: aws.Int32(5),
})
if err != nil {
log.Error().Err(err).Msg("failed to list tables")
return err
}
for _, name := range resp.TableNames {
log.Debug().Str("table", name).Msg("dynamodb table found")
if tableName == name {
isFound = true
break
}
}
if !isFound {
err := CreateTable(client, tableName)
if err != nil {
return err
}
}
return nil
}
func configureLogger() {
zerolog.TimeFieldFormat = "2006-01-02T15:04:05.000Z"
// set time format UTC
zerolog.TimestampFunc = func() time.Time {
return time.Now() //.UTC()
}
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
consoleLogger := log.Output(zerolog.ConsoleWriter{
Out: os.Stderr,
TimeFormat: "15:04:05.000",
})
log.Logger = consoleLogger
}
type DemoRecord struct {
PK string
SK string
Time time.Time
Text string
}
func main() {
tableName := "time-demo-table"
configureLogger()
if err := configureDynamoDbClient("dev", "us-west-2"); err != nil {
log.Panic().Err(err).Msg("failed configureDynamoDbClient")
}
if err := createTableIfNotFound(dynamoDbClient, tableName); err != nil {
log.Panic().Err(err).Msg("failed createTableIfNotFound")
}
timeFormat := time.RFC3339Nano
timeFormat = "2006-01-02T15:04:05.000Z"
modDefaultOptionsFunc := func(options *attributevalue.EncoderOptions) {
options.EncodeTime = func(t time.Time) (types.AttributeValue, error) {
return &types.AttributeValueMemberS{
Value: t.Format(timeFormat),
}, nil
}
}
demoRecord := DemoRecord{
PK: uuid.NewString(),
SK: "time-demo",
Time: time.Now().UTC(),
Text: fmt.Sprintf("Time Format Used: %s", timeFormat),
}
av, err := attributevalue.MarshalMapWithOptions(demoRecord, modDefaultOptionsFunc)
if err != nil {
log.Panic().Err(err).Msg("failed MarshalMap")
}
input := &dynamodb.PutItemInput{
Item: av,
TableName: aws.String(tableName),
ReturnValues: types.ReturnValueAllOld,
}
output, err := dynamoDbClient.PutItem(context.TODO(), input)
if err != nil {
log.Panic().Err(err).Msg("failed PutItem operation")
}
log.Debug().Interface("attributes", output.Attributes).Msg("result")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment