func UpdateLockTableSetSSEncryptionOnIfNecessary(tableName string, client *dynamodb.DynamoDB, terragruntOptions *options.TerragruntOptions) error {
	tableSSEncrypted, err := LockTableCheckSSEncryptionIsOn(tableName, client)
	if err != nil {
		return errors.WithStackTrace(err)
	}
	if tableSSEncrypted {
		terragruntOptions.Logger.Printf("Table %s already has encryption enabled", tableName)
		return nil
	}
	tableCreateDeleteSemaphore.Acquire()
	defer tableCreateDeleteSemaphore.Release()
	terragruntOptions.Logger.Printf("Enabling server-side encryption on table %s in AWS DynamoDB", tableName)
	input := &dynamodb.UpdateTableInput{
		SSESpecification: &dynamodb.SSESpecification{
			Enabled: aws.Bool(true),
			SSEType: aws.String("KMS"),
		},
		TableName: aws.String(tableName),
	}
	if _, err := client.UpdateTable(input); err != nil {
		if isTableAlreadyBeingCreatedOrUpdatedError(err) {
			terragruntOptions.Logger.Printf("Looks like someone is already updating table %s at the same time. Will wait for that update to complete.", tableName)
		} else {
			return errors.WithStackTrace(err)
		}
	}
	if err := waitForEncryptionToBeEnabled(tableName, client, terragruntOptions); err != nil {
		return errors.WithStackTrace(err)
	}
	return waitForTableToBeActive(tableName, client, MAX_RETRIES_WAITING_FOR_TABLE_TO_BE_ACTIVE, SLEEP_BETWEEN_TABLE_STATUS_CHECKS, terragruntOptions)
}
Encrypt the TFState Lock table - If Necessary
package dynamodb
import (
	"fmt"
	"time"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/gruntwork-io/terragrunt/aws_helper"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
)
var tableCreateDeleteSemaphore = NewCountingSemaphore(10)
const ATTR_LOCK_ID = "LockID"
const MAX_RETRIES_WAITING_FOR_TABLE_TO_BE_ACTIVE = 30
const SLEEP_BETWEEN_TABLE_STATUS_CHECKS = 10 * time.Second
const DYNAMODB_PAY_PER_REQUEST_BILLING_MODE = "PAY_PER_REQUEST"
func CreateDynamoDbClient(config *aws_helper.AwsSessionConfig, terragruntOptions *options.TerragruntOptions) (*dynamodb.DynamoDB, error) {
	session, err := aws_helper.CreateAwsSession(config, terragruntOptions)
	if err != nil {
		return nil, err
	}
	return dynamodb.New(session), nil
}
func CreateLockTableIfNecessary(tableName string, tags map[string]string, client *dynamodb.DynamoDB, terragruntOptions *options.TerragruntOptions) error {
	tableExists, err := LockTableExistsAndIsActive(tableName, client)
	if err != nil {
		return err
	}
	if !tableExists {
		terragruntOptions.Logger.Printf("Lock table %s does not exist in DynamoDB. Will need to create it just this first time.", tableName)
		return CreateLockTable(tableName, tags, client, terragruntOptions)
	}
	return nil
}
func LockTableExistsAndIsActive(tableName string, client *dynamodb.DynamoDB) (bool, error) {
	output, err := client.DescribeTable(&dynamodb.DescribeTableInput{TableName: aws.String(tableName)})
	if err != nil {
		if awsErr, isAwsErr := err.(awserr.Error); isAwsErr && awsErr.Code() == "ResourceNotFoundException" {
			return false, nil
		} else {
			return false, errors.WithStackTrace(err)
		}
	}
	return *output.Table.TableStatus == dynamodb.TableStatusActive, nil
}
func LockTableCheckSSEncryptionIsOn(tableName string, client *dynamodb.DynamoDB) (bool, error) {
	output, err := client.DescribeTable(&dynamodb.DescribeTableInput{TableName: aws.String(tableName)})
	if err != nil {
		return false, errors.WithStackTrace(err)
	}
	return output.Table.SSEDescription != nil && aws.StringValue(output.Table.SSEDescription.Status) == dynamodb.SSEStatusEnabled, nil
}
func CreateLockTable(tableName string, tags map[string]string, client *dynamodb.DynamoDB, terragruntOptions *options.TerragruntOptions) error {
	tableCreateDeleteSemaphore.Acquire()
	defer tableCreateDeleteSemaphore.Release()
	terragruntOptions.Logger.Printf("Creating table %s in DynamoDB", tableName)
	attributeDefinitions := []*dynamodb.AttributeDefinition{
		{AttributeName: aws.String(ATTR_LOCK_ID), AttributeType: aws.String(dynamodb.ScalarAttributeTypeS)},
	}
	keySchema := []*dynamodb.KeySchemaElement{
		{AttributeName: aws.String(ATTR_LOCK_ID), KeyType: aws.String(dynamodb.KeyTypeHash)},
	}
	createTableOutput, err := client.CreateTable(&dynamodb.CreateTableInput{
		TableName:            aws.String(tableName),
		BillingMode:          aws.String(DYNAMODB_PAY_PER_REQUEST_BILLING_MODE),
		AttributeDefinitions: attributeDefinitions,
		KeySchema:            keySchema,
	})
	if err != nil {
		if isTableAlreadyBeingCreatedOrUpdatedError(err) {
			terragruntOptions.Logger.Printf("Looks like someone created table %s at the same time. Will wait for it to be in active state.", tableName)
		} else {
			return errors.WithStackTrace(err)
		}
	}
	err = waitForTableToBeActive(tableName, client, MAX_RETRIES_WAITING_FOR_TABLE_TO_BE_ACTIVE, SLEEP_BETWEEN_TABLE_STATUS_CHECKS, terragruntOptions)
	if err != nil {
		return err
	}
	if createTableOutput != nil && createTableOutput.TableDescription != nil && createTableOutput.TableDescription.TableArn != nil {
		err = tagTableIfTagsGiven(tags, createTableOutput.TableDescription.TableArn, client, terragruntOptions)
		if err != nil {
			return errors.WithStackTrace(err)
		}
	}
	return nil
}
func tagTableIfTagsGiven(tags map[string]string, tableArn *string, client *dynamodb.DynamoDB, terragruntOptions *options.TerragruntOptions) error {
	if tags == nil || len(tags) == 0 {
		terragruntOptions.Logger.Printf("No tags for lock table given.")
		return nil
	}
	terragruntOptions.Logger.Printf("Adding tags to lock table: %s", tags)
	var tagsConverted []*dynamodb.Tag
	for k, v := range tags {
		tagsConverted = append(tagsConverted, &dynamodb.Tag{Key: aws.String(k), Value: aws.String(v)})
	}
	var input = dynamodb.TagResourceInput{
		ResourceArn: tableArn,
		Tags:        tagsConverted}
	_, err := client.TagResource(&input)
	return err
}
func DeleteTable(tableName string, client *dynamodb.DynamoDB) error {
	tableCreateDeleteSemaphore.Acquire()
	defer tableCreateDeleteSemaphore.Release()
	_, err := client.DeleteTable(&dynamodb.DeleteTableInput{TableName: aws.String(tableName)})
	return err
}
func isTableAlreadyBeingCreatedOrUpdatedError(err error) bool {
	awsErr, isAwsErr := err.(awserr.Error)
	return isAwsErr && awsErr.Code() == "ResourceInUseException"
}
func waitForTableToBeActive(tableName string, client *dynamodb.DynamoDB, maxRetries int, sleepBetweenRetries time.Duration, terragruntOptions *options.TerragruntOptions) error {
	return waitForTableToBeActiveWithRandomSleep(tableName, client, maxRetries, sleepBetweenRetries, sleepBetweenRetries, terragruntOptions)
}
func waitForTableToBeActiveWithRandomSleep(tableName string, client *dynamodb.DynamoDB, maxRetries int, sleepBetweenRetriesMin time.Duration, sleepBetweenRetriesMax time.Duration, terragruntOptions *options.TerragruntOptions) error {
	for i := 0; i < maxRetries; i++ {
		tableReady, err := LockTableExistsAndIsActive(tableName, client)
		if err != nil {
			return err
		}
		if tableReady {
			terragruntOptions.Logger.Printf("Success! Table %s is now in active state.", tableName)
			return nil
		}
		sleepBetweenRetries := util.GetRandomTime(sleepBetweenRetriesMin, sleepBetweenRetriesMax)
		terragruntOptions.Logger.Printf("Table %s is not yet in active state. Will check again after %s.", tableName, sleepBetweenRetries)
		time.Sleep(sleepBetweenRetries)
	}
	return errors.WithStackTrace(TableActiveRetriesExceeded{TableName: tableName, Retries: maxRetries})
}