Generate CloudFormation with GoFormation

Overview

GoFormation generates GO structures out of the CloudFormation Resource specification.

This specification includes all information, also the link to documentation about AWS Resources.

For a DynamoDB Table - Attribute Definition this would be the Table:

  "AWS::DynamoDB::Table": {
      "Attributes": {
        "Arn": {
          "PrimitiveType": "String"
        },
        "StreamArn": {
          "PrimitiveType": "String"
        }
      },
...    
    "Properties": {
        "AttributeDefinitions": {
            "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-attributedef",
          "DuplicatesAllowed": true,
          "ItemType": "AttributeDefinition",
          "Required": false,
          "Type": "List",
          "UpdateType": "Conditional"
        },
...    

And the AttributeDefinition:

    "AWS::DynamoDB::Table.AttributeDefinition": {
      "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-attributedef.html",
      "Properties": {
        "AttributeName": {
          "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-attributedef.html#cfn-dynamodb-attributedef-attributename",
          "PrimitiveType": "String",
          "Required": true,
          "UpdateType": "Mutable"
        },
        "AttributeType": {
          "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-attributedef.html#cfn-dynamodb-attributedef-attributename-attributetype",
          "PrimitiveType": "String",
          "Required": true,
          "UpdateType": "Mutable"
        }
      }
    },

Code Generation

With that information the generation process:

git clone https://github.com/awslabs/goformation.git
cd goformation
go generate

can generate all GO structures. That means, you can support CloudFormation types immediately. This is a similiar process in CDK Cfnxxx types.

Coding a DynamodDB Table in GO

This Table struct is generated in goformation/cloudformation/dynamodb/aws-dynamodb-table.go:

type Table struct {

        // AttributeDefinitions AWS CloudFormation Property
        // Required: false
        // See: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#cfn-dynamodb-table-attributedef
        AttributeDefinitions []Table_AttributeDefinition `json:"AttributeDefinitions,omitempty"`

With that generated structures you may modell CloudFormation in the go program:

	template := cloudformation.NewTemplate()
	template.Resources["simpletable"] = &dynamodb.Table{
		AttributeDefinitions:                 []dynamodb.Table_AttributeDefinition{
			{
				AttributeName: "Username",
				AttributeType: "S",
			},
		},

Now I create a DynamoDB Table in goformation:

Process with goformation

See infrastructure-as-go/goformation/dynamodb/usertable.go in go-on-aws git.

  1. Create Template
  2. Define Resources
  3. create yaml or json CloudFormation

1) Create Template

 15   template := cloudformation.NewTemplate()

The template stucture holds all information about the template itself.

2) Define Resources

16   template.Resources["simpletable"] = &dynamodb.Table{
17     AttributeDefinitions:                 []dynamodb.Table_AttributeDefinition{
18       {
19         AttributeName: "Username",
20         AttributeType: "S",
21       },
22     },

Resources is just a map[string]Resource, so we identify the Table with its logical name. When CloudFormation creates an instance of the Table, the ID of the instance is called the physical name.

Inside goformation you can now reference this table e.g. with GetAtt which is defined as:

func GetAtt(logicalName string, attribute string) string

3) create yaml or json CloudFormation

43   y, err := template.YAML()
44   if err != nil {
45     fmt.Printf("Failed to generate YAML: %s\n", err)
46     return nil, err
47   }

Compare to CDK DynamoDB

Abstraction

In CDK the abstraction level is higher. That means you need less lines of code, but you give up some control:

table := dynamodb.NewTable(this, aws.String("table"), &dynamodb.TableProps{
    PartitionKey: &dynamodb.Attribute{
        Name: aws.String("username"),
        Type: dynamodb.AttributeType_STRING,
    },
...    
})

vs

	template.Resources["simpletable"] = &dynamodb.Table{
		AttributeDefinitions:                 []dynamodb.Table_AttributeDefinition{
			{
				AttributeName: "Username",
				AttributeType: "S",
			},
		},
		KeySchema:                            []dynamodb.Table_KeySchema{
			{
				AttributeName:                        "Username",
				KeyType:                              "HASH",
			},
		},

But you get more control. As of now (dec 2021) the table class is not yet available in CDK. With goformation, you can add it:

		TableClass:                           "STANDARD_INFREQUENT_ACCESS",

Speed

These are only one-time measures, so not accurate.

directory  framework  language
dynamodb goformation  GO
dynamodb-cdk-go CDK  GO
dynamodb-cdk-ts CDK  Typescript

Now I compare the execution times:

Framework  Language command time [sec]
CDK
 TypeScript   init  24.1
CDK
GO   init  0.3
goformation  GO   not needed  
CDK
 TypeScript/npx   synth  15.8 1st time, 5.5 2nd
CDK
 TypeScript   synth  3.1
CDK
GO   synth  3.9
goformation
 GO / with build   synth   0.9
goformation
 GO / binary   synth   0.03

You see that abstraction has its (speed) price. goformation with included build is 3..15 times faster. When you compile the programm before, you get are 100 times faster.

So usually you gain more benefits from the abstraction, but sometimes you need full control about Resources, that are not yet available as CDK constructs. Or you want to create cloudformation, but you need a mechanism to structure and cluster your files. With goformation you may create subdirectories with modules which all create on large CloudFormation template.

See also

Source

See the full source on github.