Skip to main content
How To Create a DynamoDB Table In AWS Using Pulumi And Golang
  1. Blog/

How To Create a DynamoDB Table In AWS Using Pulumi And Golang

·5 mins·
Infrastructure as Code with Pulumi and Go - This article is part of a series.
Part 5: This Article

In previous posts, I used Pulumi for VPCs, subnets, and EKS clusters. Most apps also need a datastore, so this post covers creating a DynamoDB table.

The complete project is available on GitHub.

Static types
#

One of Go’s strengths is static typing — you know what goes in and what comes out of every function. At the time of writing, the Pulumi Go SDK didn’t offer static types for AWS resources, so the code below defines DynamoAttribute and GlobalSecondaryIndex types to fill that gap:

// DynamoAttribute represents an attribute for describing the key schema for the table and indexes.
type DynamoAttribute struct {
	Name string
	Type string
}

// DynamoAttributes is an array of DynamoAttribute
type DynamoAttributes []DynamoAttribute

// GlobalSecondaryIndex represents the properties of a global secondary index
type GlobalSecondaryIndex struct {
	Name           string
	HashKey        string
	ProjectionType string
	WriteCapacity  int
	ReadCapacity   int
}

// GlobalSecondaryIndexes is an array of GlobalSecondaryIndex
type GlobalSecondaryIndexes []GlobalSecondaryIndex

The tableArgs expects interface{} for these fields, and the underlying infrastructure needs them as a list. These ToList() methods handle that conversion:

// ToList takes a DynamoAttributes object and turns that into a slice of map[string]interface{} so it can be correctly passed to the Pulumi runtime
func (d DynamoAttributes) ToList() []map[string]interface{} {
	array := make([]map[string]interface{}, len(d))
	for idx, attr := range d {
		m := make(map[string]interface{})
		m["name"] = attr.Name
		m["type"] = attr.Type
		array[idx] = m
	}
	return array
}

// ToList takes a GlobalSecondaryIndexes object and turns that into a slice of map[string]interface{} so it can be correctly passed to the Pulumi runtime
func (g GlobalSecondaryIndexes) ToList() []map[string]interface{} {
	array := make([]map[string]interface{}, len(g))
	for idx, attr := range g {
		m := make(map[string]interface{})
		m["name"] = attr.Name
		m["hash_key"] = attr.HashKey
		m["projection_type"] = attr.ProjectionType
		m["write_capacity"] = attr.WriteCapacity
		m["read_capacity"] = attr.ReadCapacity
		array[idx] = m
	}
	return array
}

This gives you typed objects in your code while still satisfying the Pulumi runtime.

Building a table
#

Here’s where it all comes together. This example creates a table with usernames and unique IDs (used in one of my apps for order data). The actual order data isn’t modeled here, so it won’t be queryable.

// Create the attributes for ID and User
dynamoAttributes := DynamoAttributes{
    DynamoAttribute{
        Name: "ID",
        Type: "S",
    },
    DynamoAttribute{
        Name: "User",
        Type: "S",
    },
}

// Create a Global Secondary Index for the user field
gsi := GlobalSecondaryIndexes{
    GlobalSecondaryIndex{
        Name: "User",
        HashKey: "User",
        ProjectionType: "ALL",
        WriteCapacity: 10,
        ReadCapacity: 10,
    },
}

// Create a TableArgs struct that contains all the data
tableArgs := &dynamodb.TableArgs{
    Attributes:    dynamoAttributes.ToList(),
    HashKey:       "ID",
    WriteCapacity: 10,
    ReadCapacity:  10,
    GlobalSecondaryIndexes: gsi.ToList(),
}

// Let the Pulumi runtime create the table
userTable, err := dynamodb.NewTable(ctx, "User", tableArgs)
if err != nil {
    return err
}

// Export the name of the newly created table as an output in the stack
ctx.Export("TableName", userTable.ID())

Complete code
#

Here’s everything combined into a single runnable Go program:

package main

import (
	"github.com/pulumi/pulumi-aws/sdk/go/aws/dynamodb"
	"github.com/pulumi/pulumi/sdk/go/pulumi"
)

// DynamoAttribute represents an attribute for describing the key schema for the table and indexes.
type DynamoAttribute struct {
	Name string
	Type string
}

// DynamoAttributes is an array of DynamoAttribute
type DynamoAttributes []DynamoAttribute

// ToList takes a DynamoAttributes object and turns that into a slice of map[string]interface{} so it can be correctly passed to the Pulumi runtime
func (d DynamoAttributes) ToList() []map[string]interface{} {
	array := make([]map[string]interface{}, len(d))
	for idx, attr := range d {
		m := make(map[string]interface{})
		m["name"] = attr.Name
		m["type"] = attr.Type
		array[idx] = m
	}
	return array
}

// GlobalSecondaryIndex represents the properties of a global secondary index
type GlobalSecondaryIndex struct {
	Name           string
	HashKey        string
	ProjectionType string
	WriteCapacity  int
	ReadCapacity   int
}

// GlobalSecondaryIndexes is an array of GlobalSecondaryIndex
type GlobalSecondaryIndexes []GlobalSecondaryIndex

// ToList takes a GlobalSecondaryIndexes object and turns that into a slice of map[string]interface{} so it can be correctly passed to the Pulumi runtime
func (g GlobalSecondaryIndexes) ToList() []map[string]interface{} {
	array := make([]map[string]interface{}, len(g))
	for idx, attr := range g {
		m := make(map[string]interface{})
		m["name"] = attr.Name
		m["hash_key"] = attr.HashKey
		m["projection_type"] = attr.ProjectionType
		m["write_capacity"] = attr.WriteCapacity
		m["read_capacity"] = attr.ReadCapacity
		array[idx] = m
	}
	return array
}

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		// Create the attributes for ID and User
		dynamoAttributes := DynamoAttributes{
			DynamoAttribute{
				Name: "ID",
				Type: "S",
			},
			DynamoAttribute{
				Name: "User",
				Type: "S",
			},
		}

		// Create a Global Secondary Index for the user field
		gsi := GlobalSecondaryIndexes{
			GlobalSecondaryIndex{
				Name: "User",
				HashKey: "User",
				ProjectionType: "ALL",
				WriteCapacity: 10,
				ReadCapacity: 10,
			},
		}

		// Create a TableArgs struct that contains all the data
		tableArgs := &dynamodb.TableArgs{
			Attributes:    dynamoAttributes.ToList(),
			HashKey:       "ID",
			WriteCapacity: 10,
			ReadCapacity:  10,
			GlobalSecondaryIndexes: gsi.ToList(),
		}

		// Let the Pulumi runtime create the table
		userTable, err := dynamodb.NewTable(ctx, "User", tableArgs)
		if err != nil {
			return err
		}

		// Export the name of the newly created table as an output in the stack
		ctx.Export("TableName", userTable.ID())
	})
}

Pulumi up
#

Create a new Pulumi project and run pulumi up. To create a Go-based project:

pulumi new go \
--name builder \
--description "An awesome Pulumi infrastructure-as-code Stack" \
--stack retgits/builderstack

Replace the generated Go file with the code above and run pulumi up. For more detail on project setup, check out one of my previous posts.

Cover image by Tobias Fischer on Unsplash

Infrastructure as Code with Pulumi and Go - This article is part of a series.
Part 5: This Article

Related