skip to Main Content

I’m trying to create a CloudFormation script that allows the user to select a Linux distribution from three options (ubuntu, redhat, centos). Based on the option the user selects I then want to select the right AMI for the region in which the CloudFormation stack is being created, from an existing mapping. Finally I want to use that region-specific AMI as the Image ID for an EC2 instance.

I have mappings for each distribution with the AMI for that region mapped.

I would like to have this all automated, so I can use just one CloudFormation script, and not one for each OS.

I have a web application that depending on client preferences will run on Ubuntu, CentOS or Red Hat. I don’t want to have to maintain three separate CloudFormation scripts for each distribution, especially considering that the only real thing that would be different on each is the ImageId for the EC2 Instance.

I am aware that I should probably be making use of CloudFormation’s condition functions, but I haven’t yet really figured out a way to do it.

I haven’t been able to find anything on the web to help that much. That’s either because it is really easy nobody has posted anything about it, or it’s not possible, I feel like it may be the latter, but would like to see some other views.

Here’s an excerpt of what I am currently working with.

Parameters:
  ApplicationServerLinuxDistribution:
    Type: "String"
    AllowedValues:
      - ubuntu
      - redhat
      - centos

Mappings:
# Ubuntu 18.04 AMIs
  AWSUbuntuAMIRegionMap:
    ap-northeast-1:
      HVM64: "ami-0cd744adeca97abb1"
    ap-northeast-2:
      HVM64: "ami-00379ec40a3e30f87"
# Red Hat 8 AMIs
  AWSRedHatAMIRegionMap:
    ap-northeast-1:
      HVM64: "ami-0c45b9b8b241f629f"
    ap-northeast-2:
      HVM64: "ami-090f71670acf741d8"
# CentOS 7 AMIs
  AWSCentOSAMIRegionMap:
    ap-northeast-1:
      HVM64: "ami-045f38c93733dd48d"
    ap-northeast-2:
      HVM64: "ami-06cf2a72dadf92410"

Resources:
   ApplicationServer:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: 

So to be clear what I want is if the user is in eu-west-1 and selects Ubuntu, I want to query my Ubuntu Region/AMI map for the eu-west-1 AMI, and use that AMI to create the EC2 instance, and if the user selects Red Hat or CentOS do the same, but for those maps instead.

2

Answers


  1. Chosen as BEST ANSWER

    This was actually a lot simpler to accomplish than I initially thought it might be, and can be done without using conditionals at all.

    Solution

    # Define the distribution's a user can choose from.
    Parameters:
      LinuxDistributionSelection:
        Type: "String"
        AllowedValues:
          - ubuntu
          - centos
          - redhat
    
    # Create a single map where each Region is a TopLevelKey, and each Parameter is a SecondLevelKey with the AMI as the key's value.
    Mappings:
      AMIMapping:
        ap-east-1:
          ubuntu: "ami-59780228"
          centos: "null"
          redhat: "ami-7b374d0a"
        ap-northeast-1:
          ubuntu: "ami-04a1c725d8678428c"
          centos: "ami-045f38c93733dd48d"
          redhat: "ami-0c45b9b8b241f629f"
        ap-northeast-2:
          ubuntu: "ami-0df5bf8255e3a317f"
          centos: "ami-06cf2a72dadf92410"
          redhat: "ami-090f71670acf741d8"
    
    # Query the map
    Resources:
       ApplicationServer:
        Type: AWS::EC2::Instance
        Properties:
          ImageId: !FindInMap [AMIMapping, !Ref "AWS::Region", !Ref LinuxDistributionSelection ]
    
    

    Why does this work?

    This works, because you can have multiple SecondLevelKey's underneath a TopLevelKey as the Fn::FindInMap documentation states: here. So the AMIs for each distribution in each region can just be key/value pairs underneath the region key. Example below.

    Fn::FindInMap: [ MapName, TopLevelKey=<region>, SecondLevelKey=<parameter-reference> ]
    

    CloudFormation can then work through things logically. So when you need to get an AMI for the ImageId parameter of an EC2 instance based on what a user picks, you tell CloudFormation to query your region map with !FindInMap (the shorthand for Fn::FindInMap). Then CloudFormation will reference the region you are running the script in by using !Ref "AWS::Region", and finally CloudFormation is able to reference the selected parameter with !Ref LinuxDistributionSelection.

    !FindInMap [AMIMapping, !Ref "AWS::Region", !Ref LinuxDistributionSelection ]
    

    I hope my answer is sufficient and explains it well enough for anybody struggling with the same issue. It's my first answer so please be nice, and feel free to critique it if it's misleading, or if I have misunderstood what is actually happening here.


  2. You’re nearly there, but because Fn::FindInMap can only return the value corresponding to keys in a two-level map I’d remove the HVM64 property from the map:

      AMIRegionMap:
        ap-northeast-1: 
          ubuntu: "ami-0cd744adeca97abb1"
          redhat: "ami-0c45b9b8b241f629f"
          centos: "ami-045f38c93733dd48d"
        ap-northeast-2: 
          ubuntu: "ami-00379ec40a3e30f87"
          redhat: "ami-090f71670acf741d8"
          centos: "ami-06cf2a72dadf92410"
    

    Then in the Resources section you can use the AWS::Region pseudo parameter along with your ApplicationServerLinuxDistribution parameter to access the AMI you need:

    Resources:
       ApplicationServer:
        Type: "AWS::EC2::Instance"
        Properties:
          ImageId: !FindInMap [AMIRegionMap, !Ref "AWS::Region", !Ref ApplicationServerLinuxDistribution]
    

    The region must be used as the first level key, if used as a second level key you will encounter Mappings attribute name 'ap-northeast-1' must contain only alphanumeric characters. The documentation states that:

    The name can contain only alphanumeric characters (A-Za-z0-9).

    But it isn’t clear that this only applies to second level keys!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search