Typed CloudFormation with a json file and GoFormation types

Overview

Overview

Now I show you how to use a json file for the additional AnalyticsConfigurationProperty object.

All CloudFormation Resources are defined within the CDK itself. The AnalyticsConfigurationProperty in GO CDK is defined as:

type CfnBucket_AnalyticsConfigurationProperty struct {
	Id *string `json:"id" yaml:"id"`
	StorageClassAnalysis interface{} `json:"storageClassAnalysis" yaml:"storageClassAnalysis"`
	Prefix *string `json:"prefix" yaml:"prefix"`
	TagFilters interface{} `json:"tagFilters" yaml:"tagFilters"`
}

Because the type of StorageClassAnalysis is interface, which means “any type”, the StorageClassAnalysis property will not be typechecked.

So for example if you make an error and do not define AnalyticsConfigurationProperty as a list, the rendered CloudFormation will be like:

buckyE21EA0FF:
    Type: AWS::S3::Bucket
    Properties:
      AnalyticsConfigurations:
        Id: AnalyticsConfigurationId
        StorageClassAnalysis:

CDK will synth and deploy, but the CloudFormation will give you an error at deploy time:

Value of property AnalyticsConfigurations must be of type List

Because what it should be is:

 buckyE21EA0FF:
    Type: AWS::S3::Bucket
    Properties:
      AnalyticsConfigurations:
        - Id: AnalyticsConfigurationId
          Prefix: AnalyticsConfigurationPrefix
          StorageClassAnalysis:

So you have wasted about a minute each time you change your code. We don`t want that.

You could use cfn-lint before you deploy.

When you change to the base directory of this chapter, * infrastructure-as-go/cdk-go/escape-hatch*, you would type this to chexk the *string* stack:

cfn-lint cdk.out/string.template.json

Which will detect the error:

 cfn-lint cdk.out/string.template.json
E3002 Property is an object instead of List at Resources/buckyE21EA0FF/Properties/AnalyticsConfigurations
cdk.out/string.template.json:14:9

To prevent this kind if error, we use strong typing support.

Using goformation for strong typing Support

In goformation (4:) all CloudFormation schemata are generated as GO structs.

type Bucket struct {
    // ...
    AnalyticsConfigurations []Bucket_AnalyticsConfiguration `json:"AnalyticsConfigurations,omitempty"`

As you see AnalyticsConfigurations is not an interface{} type, but has its own struct. So we have the type information.

In type Bucket_AnalyticsConfiguration you see:

type Bucket_AnalyticsConfiguration struct {
	Id string `json:"Id,omitempty"`
	Prefix string `json:"Prefix,omitempty"`
	StorageClassAnalysis *Bucket_StorageClassAnalysis `json:"StorageClassAnalysis,omitempty"`
    //...

The tagged JSON information will be used to render CloudFormation. So you could call the variable Id also AnalyticsID and the CloudFormation json will render Id, because of

	AnalyticsID string `json:"Id,omitempty"`

Now we can use this type definition of GOformation in the CDK app ( see escape-hatch-file.go):

  1. Add goformation to import:
import (
	"github.com/awslabs/goformation/v5/cloudformation/s3"
)
  1. Define Array of in AnalyticsConfiguration a json file:
[
    {
    "Id": "AnalyticsConfigurationId",
    "StorageClassAnalysis": {
        "DataExport": {
            "Destination": {
                "BucketArn": {
                    "Fn::GetAtt": [
                        "Helper",
                        "Arn"
                    ]
                },
                "Format": "CSV",
                "Prefix": "AnalyticsDestinationPrefix"
            },
            "OutputSchemaVersion": "V_1"
        }
    },
    "Prefix": "AnalyticsConfigurationPrefix",
    "TagFilters": [
        {
            "Key": "AnalyticsTagKey",
            "Value": "AnalyticsTagValue"
        }
    ]
    }
]
  1. UnMarshal into a struct:
	var analyticsConfigurationFromFile []s3.Bucket_AnalyticsConfiguration
	data, err := os.ReadFile("testdata/analyticsconfig.json")
	json.Unmarshal(data, &analyticsConfigurationFromFile)
  1. Modify data if needed:

The helper bucket is dynamically generated so you have to set this reference:

	analyticsConfigurationFromFile[0].StorageClassAnalysis.DataExport.Destination.BucketArn = *helper.BucketArn()
  1. Deploy

Now you can deploy to CloudFormation without errors.

Summary

With this approach you can support strong typing on all CloudFormation resources. If you have Upper/lowercase errors in names like storageclassAnalysis instead of StorageClassAnalysis, the umarshal process will ignore the case and render the correct name defined in the json tag json:"StorageClassAnalysis,omitempty" of the GO structure.

If you add additional, not cfn-supported objects in the json file, they will be ignored. See file testdata/analyticsconfig-bad.json for an example

Read about how to directly create the structure in the next chapter.

See also

Source

See the full source on github.

Sources