Modify CloudFormation directly

Overview

Overview

In some cases the CDK Construct does not implement all CloudFormation attributes. Then you have to change the generated CloudFormation itself.

As an example we take an S3 Bucket. The CDK S3 Bucket Construct does not have a property for the AnalyticsConfigurations. So we have to add this manually.

The example from the AWS documentation (2:) shows the following AnalyticsConfigurations-json:

 {"AnalyticsConfigurations": [
                    {
                        "Id": "AnalyticsConfigurationId",
                        "StorageClassAnalysis": {
                            "DataExport": {
                                "Destination": {
                                    "BucketArn": {
                                        "Fn::GetAtt": [
                                            "Helper",
                                            "Arn"
                                        ]
                                    },
                                    "Format": "CSV",
                                    "Prefix": "AnalyticsDestinationPrefix"
                                },
                                "OutputSchemaVersion": "V_1"
                            }
                        },
                        "Prefix": "AnalyticsConfigurationPrefix",
                        "TagFilters": [
                            {
                                "Key": "AnalyticsTagKey",
                                "Value": "AnalyticsTagValue"
                            }
                        ]
                    }
                ]
 }                

Escape Hatch

The CDK way in the other languages is to cast the Construct Bucket to the corresponding CloudFormation object CfnBucket.

In GO this downcasting is not allowed (3: conversions) so we have to make a workaround, we use jsii.Get:

winterSoldier := awss3.NewBucket(stack, aws.String("bucky"), &awss3.BucketProps{
    BlockPublicAccess:      awss3.BlockPublicAccess_BLOCK_ALL(),
    BucketName:             aws.String("bucky"),
})

var cap awss3.CfnBucket

jsii.Get(winterSoldier.Node(), "defaultChild", &cap)

Now I have an awss3.CfnBucket and can assign the properties directly to the CloudFormation variable cap. If we would assign the AnalyticsConfigurations-json as a string, then in json it would be represented as string with escaped " , which is not usable:

 "AnalyticsConfigurationsEscaped": "\n\t[\n    {\n    \"Id\": \"AnalyticsConfigurationId\",\n    \"StorageClassAnalysis\": {\n        \"DataExport\": {\n            \"Destination\": {\n                \"BucketArn\": {\n                    \"Fn::GetAtt\": [\n                        \"Helper\",\n                        \"Arn\"\n                    ]\n                },\n                \"Format\": \"CSV\",\n                \"Prefix\": \"AnalyticsDestinationPrefix\"\n            },\n            \"OutputSchemaVersion\": \"V_1\"\n        }\n    },\n    \"Prefix\": \"AnalyticsConfigurationPrefix\",\n    \"TagFilters\": [\n        {\n            \"Key\": \"AnalyticsTagKey\",\n            \"Value\": \"AnalyticsTagValue\"\n        }\n    ]\n    }\n]\n"

So we have to create a proper structure.

Define a structure

&[]map[string]interface{}{
    {
    "Id": "AnalyticsConfigurationId",
    "StorageClassAnalysis": map[string]interface{}{
        "DataExport": map[string]interface{}{
            "Destination": map[string]interface{}{
                "BucketArn": winterSoldier.BucketArn(),
            },
            "Format": "CSV",
            "Prefix": "AnalyticsDestinationPrefix",
        },
    },
    //...

See Code.

As the mental transition from json to GO structures is not easy, here comes some help:

You can use the following translation rules from json to GO:

Type Json  GO Struct
String “Id”: “AnalyticsConfigurationId” &map[string]interface{}{ “Id” : “AnalyticsConfigurationId}
Object  “StorageClassAnalysis”: { … }  “StorageClassAnalysis”: map[string]interface{}{…}
Array  “TagFilters”: [{}]  “TagFilters”: []map[string]string{}

The rules in detail:

JSON Strings

In GO this is a map (:4) where the string key “Id” points to anything. Anything is defined by interface{}.

This leads to &map[string]interface{}. As an array it’s &[]map[string]interface{}.

Arrays in GO are defined with {}, but referenced with [].

Don’t forget the “,” also after the last entry.

JSON Object

The interface type has a dynamic type and can store strings and objects.

JSON Array

Arrays in GO are defined by {}.

Therefore the JSON

{
     "TagFilters": [
              {
                "Key": "AnalyticsTagKey",
                "Value": "AnalyticsTagValue"
              }
            ]
}

should be translated as

"TagFilters": []map[string]string{
                            {
                                "Key": "AnalyticsTagKey",
                                "Value": "AnalyticsTagValue",
                            },
				},

This works, and is ok for simple JSON objects, for complex objects you should use the predefined types from GoFormation.

See next chapter for GoFormation escape hatches.

See also

Source

See the full source on github.

Sources