stephencoetzee
stephencoetzee

Reputation: 123

AWS Cloudformation : Fn::Join within a Fn::FindInMap statement?

Trying to use Fn::Join within Fn::FindInMap, as below:

"SubnetId": {
    "Fn::FindInMap": [
        {
            "Ref": "OrganizationName"
        },
        "AZ",
        {
            "Fn::Join": [
                "",
                [
                    {
                        "Ref": "Environment"
                    },
                    {
                        "Ref": "Member1AZ"
                    }
                ]
            ]
        }
    ]
}

The OrganizationName, Environment and Member1AZ are all parameters. Essentially it should hook up to my mappings and produce, for example :

"SubnetId" : { "Fn::FindInMap" : [ "Organization2", "AZ", "prod1c" ]}

However, it does not seem to be taking the output from the Fn::Join as a single entity on the Fn::FindInMap, it validates correctly if I hardcode that section of the template.

A client error (ValidationError) occurred when calling the ValidateTemplate operation: Template error: every Fn::FindInMap object requires three parameters, the map name, map key and the attribute for return value

My Mappings are as follows:

Mappings" : {
      "OrganizationDefaults" : {
            "AZ" : {
                "prod1a" : "subnet-foobar1",
                "qa1a" : "subnet-foobar2",
                "prod1c" : "subnet-foobar3",
                "qa1c" : "subnet-foobar4"
            }
      },
      "OrganizationTwo" : {
            "AZ" : {
                "prod1a" : "subnet-foobar5",
                "qa1a" : "subnet-foobar6",
                "prod1c" : "subnet-foobar7",
                "qa1c" : "subnet-foobar8"
            }
      },
},

Can anyone help on this, or had to do something similar before? I need to use the same template for any organizations listed, so Mappings should solve this for me, if I can get it right.

Upvotes: 8

Views: 15669

Answers (2)

Lee Netherton
Lee Netherton

Reputation: 22532

As @Jason explained, one solution is to refactor your map to work within the 2D limitations of the maps in CloudFormation. There are however, two ways you can work around this:

  1. Apply the AWS::LanguageExtensions transform to your template and use Fn::Join in Fn:FindInMap just as you described above.
  2. Use nested Fn::FindInMap calls to achieve a "3rd dimension" in your map

Under solution 1, you'll add the AWS::LanguageExtensions transform to the root of your template. This will let you use intrinsic functions and other functionalities not included by default in AWS CloudFormation. With this transform, Fn::FindInMap is enhanced to allow an extended set of nested intrinsic functions as well as an optional field to return a default value. It just so happens that Fn::Join is one of the functions that is supported by this enhanced version of the Fn::FindInMap. As such, with the language extension applied, you'll be able to use Fn::Join as a nested function within Fn::FindInMap exactly as you wrote in your question.


Under solution 2, the standard Fn::FindInMap intrinsic function only supports the following nested functions:

  • Fn::FindInMap
  • Ref

Using a Join in this case will give you the slightly cryptic error you post above. However, because you can nest FindInMap calls, you can achieve a "3rd dimension" for the map by creating another lookup map:

Mappings" : {
  "OrganizationDefaults" : {
        "AZ" : {
            "prod1a" : "subnet-foobar1",
            "qa1a" : "subnet-foobar2",
            "prod1c" : "subnet-foobar3",
            "qa1c" : "subnet-foobar4"
        }
  },
  "OrganizationTwo" : {
        "AZ" : {
            "prod1a" : "subnet-foobar5",
            "qa1a" : "subnet-foobar6",
            "prod1c" : "subnet-foobar7",
            "qa1c" : "subnet-foobar8"
        }
  },
  "EnvMemberMap" : {
        "prod": {
            "1a" : "prod1a",
            "1c" : "prod1c",
        },
        "qa": {
            "1a" : "qa1a",
            "1c" : "qa1c",
        }    
  }
},

And then perform the map retrieval like this:

"SubnetId": {
    "Fn::FindInMap": [
        {
            "Ref": "OrganizationName"
        },
        "AZ",
        {
            "Fn::FindInMap": [
                "EnvMemberMap",
                {
                    "Ref": "Environment"
                },
                {
                    "Ref": "Member1AZ"
                }
            ]
        }
    ]
}

Upvotes: 7

Jason
Jason

Reputation: 2105

I suggest you refactor your mappings to avoid the nested Fn::Join.

Mappings" : {
      "OrganizationDefaults" : {
            "1a" : {
                "prod" : "subnet-foobar1",
                "qa" : "subnet-foobar2"
            },
            "1c"
                "prod" : "subnet-foobar3",
                "qa" : "subnet-foobar4"
            }
      },
      "OrganizationTwo" : {
            "1a" : {
                "prod" : "subnet-foobar5",
                "qa" : "subnet-foobar6"
            },
            "1c" : {
                "prod" : "subnet-foobar7",
                "qa" : "subnet-foobar8"
            }
      },
},

This simplifies your reference.

"SubnetId" : { "Fn::FindInMap" : [ { "Ref" : "OrganizationName" }, { "Ref" : "Member1AZ" }, { "Ref" : "Environment" }]}

Upvotes: 4

Related Questions