Make VPC creation optional - amazon-web-services

I am trying to make VPC creation conditional in my cloudformation script. For example, if there is a VPC id provided in the parameters, then I want to create all resources in this VPC, otherwise to create a new one.
The problem starts when I want to reuse an existing VPC, but I have no way of deducing the subnets, which I need for one of my resources. So I suppose, I have to provide them as parameters. But if I provide them as parameters, in the case where I want to create a new VPC, it complains because the list of subnet ids is empty and it must be a valid one.
The error is Parameter validation failed: parameter value for parameter name VpcPrivateSubnetIds does not exist. Rollback requested by user. It is impossible to give any dummy value. Any ideas how to achieve this?
Here is my CF script:
VpcId:
Type: String
Description: Give the VPC id if you want to use an existing one. Leave empty for creating a new one.
VpcPublicSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: List of 3 public SubnetIds for the given VPC.
VpcPrivateSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: List of 3 private SubnetIds for the given VPC.
Conditions:
CreateVPC: !Equals [ !Ref VpcId, ""]
Resources:
(...)
Properties:
PrivateSubnetIds: !If
- CreateVPC
- !GetAtt VPCStack.Outputs.PrivateSubnets
- !Join [',', [!Select [0, !Ref VpcPrivateSubnetIds], !Select [1, !Ref VpcPrivateSubnetIds], !Select [2, !Ref VpcPrivateSubnetIds]]]
PublicSubnetIds: !If
- CreateVPC
- !GetAtt VPCStack.Outputs.PublicSubnets
- !Join [',', [!Select [0, !Ref VpcPublicSubnetIds], !Select [1, !Ref VpcPublicSubnetIds], !Select [2, !Ref VpcPublicSubnetIds]]]

One solution could be to treat the subnetId parameters as string, which can then be left as empty. (but user will have to manually type in the subnet id list when VPC is present).
In case the list is not empty (existing VPC to be used), use Cloudformation custom resource lambda to turn string (comma separated) into list & return to cloudformation for use in resource creation. So your stack would look something like
Parameters:
VpcId:
Type: String
Description: Give the VPC id if you want to use an existing one. Leave empty for creating a new one.
VpcPublicSubnetIds:
Type: String
Description: List of 3 public SubnetIds for the given VPC.
Default: ''
VpcPrivateSubnetIds:
Type: String
Description: List of 3 private SubnetIds for the given VPC.
Default: ''
Conditions:
CreateVPC: !Equals [ !Ref VpcId, ""]
CreateList: !Not [!Equals [ !Ref VpcId, ""]]
Resources:
CreateList:
Type: AWS::CloudFormation::CustomResource
Condition: CreateList
Properties:
ServiceToken:<some token>
Public: !Ref VpcPublicSubnetIds
Private: !Ref VpcPrivateSubnetIds
SomeResource:
Properties:
PrivateSubnetIds: !If
- CreateVPC
- !GetAtt VPCStack.Outputs.PrivateSubnets
- !GetAtt CreateList.PrivateSubnetIds
PublicSubnetIds: !If
- CreateVPC
- !GetAtt VPCStack.Outputs.PublicSubnets
- !GetAtt CreateList.PublicSubnetIds
Please note I have note validated this script, so you may have to make some corrections.

Related

Yaml not well formed error when trying to create cross stack reference

I am trying to create a cross stack reference. Think of the stack that I am referencing from as the main VPC and the stack that I am creating now is basically creating a subnet in the main VPC and then sharing it with another account (VPC/resource sharing). When I validate this template in aws designer I get the Yaml not well formed error on line 15 which is:
CidrBlock: !Select [ 0, !Cidr [!ImportValue
'Fn::Sub': '${NetworkStackParameter}-VPCCIDR', 3, 8]]
I am new to coding and cloud formation so I appreciate any help.
Please see template below:
AWSTemplateFormatVersion: "2010-09-09"
#Description:
Parameters:
NetworkStackParameter:
Type: String
Resources:
#Create Private Subnet ABC
PrivateSubnetABC:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Select [ 0, !Cidr [!ImportValue
'Fn::Sub': '${NetworkStackParameter}-VPCCIDR', 3, 8]]
VpcId: !ImportValue
'Fn::Sub': '${NetworkStackParameter}-VPCID'
AvailabilityZone: "us-east-1a"
Tags:
- Key: "name"
Value: "PrivateSubnetABC"
#Create Resource Share
PrivateSubnetABCShare:
Type: AWS::RAM::ResourceShare
Properties:
# AllowExternalPrincipals: Boolean
Name: "Service ABC"
# PermissionArns:
# - String
Principals:
- "1234567890"
ResourceArns:
- !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:subnet/${PrivateSubnetABC}'
# Tags:
# - Tag
In that case, its much easier to write it as individual blocks, rather then trying to do it in one line:
Resources:
#Create Private Subnet ABC
PrivateSubnetABC:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Select
- 0
- !Cidr
- !ImportValue
'Fn::Sub': '${NetworkStackParameter}-VPCCIDR'
- 3
- 8
VpcId: !ImportValue
'Fn::Sub': '${NetworkStackParameter}-VPCID'
AvailabilityZone: "us-east-1a"
Tags:
- Key: "name"
Value: "PrivateSubnetABC"

CloudFormation Nested Stacks: Unable to find parameters from parameter store for this account

I'm facing an issue parsing SSM parameters from a root stack into a child stack. When creating the stack, the first stack resource fails with "
Unable to fetch parameters [value1,value2,value3,value4] from parameter store for this account."
However, the value of the parameters is fetched. The values are strings too, which are supported by CloudFormation. Also, when using the same template on independent stacks, the stacks are deployed as intended. The values of the parameters in SSM are like this: subnet1,subnet2 hence the need to split the values afterwards during ALB creation.
Below are the stacks.
root template
Parameters:
PublicSubnetAZ:
Type: AWS::SSM::Parameter::Value<String>
Default: 'PublicSubnetAZ'
Description: "Public Subnet AZs"
AppSubnetAZ:
Type: AWS::SSM::Parameter::Value<String>
Default: 'AppSubnetAZ'
Description: "App Subnet AZs"
Resources:
LBStack:
Type: "AWS::CloudFormation::Stack"
Properties:
TemplateURL: "https://s3bucket.s3.eu-west-1.amazonaws.com/load_balancing.yaml"
Parameters:
PublicSubnetAZ: !Ref PublicSubnetAZ
AppSubnetAZ: !Ref AppSubnetAZ
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-lb"
nested template
Parameters:
PublicSubnetAZ:
Type: String
AppSubnetAZ:
Type: String
Resources:
LoadBalancerExternal:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Join ['-', [!Ref AWS::StackName, "external"]]
Scheme: "internet-facing"
Type: "application"
Subnets:
- !Select [0, !Split [',', !Ref PublicSubnetAZ]]
- !Select [1, !Split [',', !Ref PublicSubnetAZ]]
SecurityGroups:
- '{{resolve:ssm:ExternalLoadBalancerSecurityGroup}}'
IpAddressType: "ipv4"
LoadBalancerInternal:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
Name: !Join ['-', [!Ref AWS::StackName, "internal"]]
Scheme: "internal"
Type: "application"
Subnets:
- !Select [0, !Split [',', !Ref AppSubnetAZ]]
- !Select [1, !Split [',', !Ref AppSubnetAZ]]
SecurityGroups:
- '{{resolve:ssm:/InternalLoadBalancerSecurityGroup}}'
IpAddressType: "ipv4"
Any ideas?

Pass tags as parameters in clouldformation

I have created a simple template which i am going to use to create s3 buckets. My template looks like this.
Parameters:
Environment:
Type: String
Default: prod
AllowedPattern: '[a-z\-]+'
BucketName:
Type: String
AllowedPattern: '[a-z\-]+'
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub foo-${Environment}-${BucketName}
Tags:
- Key: key_1
Value: foo
- Key: key_2
Value: foo
DeletionPolicy: Retain
I want to make a generic template and not create a different template each time a i create an s3 bucket. Only thing that will vary between my s3 buckets is the # of tags i add to it. Some S3 buckets may have 2 tags and others may have more. At the most i will have 5 tags to my s3 bucket. So i am wondering if there is a way to pass tags through parameter such that if i pass 3 tags , 3 tags get created, if i pass 2 tags 2 tags get created.
At the most i will have 5 tags
Then you need 5 parameters:
Tag1:
Type: CommaDelimitedList
Default: ""
# ....
Tag5:
Type: CommaDelimitedList
Default: ""
and 5 conditions:
Conditions:
HasTag1:
!Not [!Equals [!Ref Tag1, ""] ]
# ...
HasTag5:
!Not [!Equals [!Ref Tag5, ""] ]
End then you use the conditions to populate your tags:
Tags:
- !If
- HasTag1
- Key: !Select [0, !Ref Tag1]
Value: !Select [1, !Ref Tag1]
- !Ref "AWS::NoValue"
# ...
- !If
- HasTag5
- Key: !Select [0, !Ref Tag5]
Value: !Select [1, !Ref Tag5]
- !Ref "AWS::NoValue"

Cloudformation Split & ImportValue

I have 2 stacks in CloudFormation. One is creating a vpc with a couple of subnets which are exported in order to be used in other stacks. The idea is to have those subnets to be used in other stacks.
The vpc stack exports the values properly but I am unable to Import them in the second stack because it uses a list of strings.
Stack 1:
PrivateSubnets:
Description: "A list of the public subnets"
Value: !Join [ ",", [ !Ref PrivateSubnet1, !Ref PrivateSubnet2 ]]
Export:
Name: !Sub "${StagingArea}-PrivateSubnets"
PublicSubnet1:
Description: "Reference to the publi subnet in the 1st Availability Zone"
Value: !Ref PublicSubnet1
PublicSubnet2:
Description: "A reference to the public subnet in the 2nd Availability Zone"
Value: !Ref PublicSubnet2
PrivateSubnet1:
Description: "A reference to the private subnet in the 1st Availability Zone"
Value: !Ref PrivateSubnet1
Export:
Name: !Sub "${StagingArea}-PrivateSubnet1"
PrivateSubnet2:
Description: "A reference to the private subnet in the 2nd Availability Zone"
Value: !Ref PrivateSubnet2
Export:
Name: !Sub "${StagingArea}-PrivateSubnet2"
When I try to ImportValue into my second stack it does not work. Stack 2 below:
DbSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: !Sub "${StagingArea} RDS DB Subnet Group"
SubnetIds:
- !ImportValue 'Fn::Sub': '{$StagingArea}-PrivateSubnet1'
- !ImportValue 'Fn::Sub': '{$StagingArea}-PrivateSubnet2'
Is it possible to get the values exported from the first stack into the second stack? I have tried various options but none of them seem to be working.
Sub is incorrect (wrong use of $) and its better to follow the docs and have the statement in two lines:
- Fn::ImportValue:
!Sub '${StagingArea}-PrivateSubnet1'
- Fn::ImportValue:
!Sub '${StagingArea}--PrivateSubnet2'

AWS CloudFormation: how do I refer to the default/main route table (that is created when a VPC is created) in a cloudformation template?

I have a CloudFormation template that creates a custom VPC.
The template creates the following resources - a VPC, an Internet Gateway, attaches the IGW to the VPC, and creates a Public Subnet.
I want to add a route (destination 0.0.0.0/0, target IGW) to the Route Table that gets created as part of the VPC.
I have read through the cloudformation documentation for routes, route tables to figure out how to do this, but to no avail.
I can use the Fn::Ref function to refer to resources or parameters that are explicitly created as part of the template, but how do I refer to resources that get created inherently with the VPC?
Any insights on how to re-use the existing route table, NACL and Security Group are much appreciated.
Thanks,
Good job so far - you have your internet gateway, route table, and a public subnet. Now you need to create the route and attach the route table to the subnet if you haven't already done so. If you're using YAML it might look something like this:
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Ref EnvironmentName
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: !Ref PublicSubnet1CIDR
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} Public Subnet (AZ1)
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName} Public Routes
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
Don't use the default route table (see https://serverfault.com/questions/588904/aws-vpc-default-route-table-in-cloudformation)
You can get default security group as per https://serverfault.com/questions/544439/aws-cloudformation-vpc-default-security-group
And finally you can also get the DefaultNetworkAcl in the same as DefaultSecurityGroup above. See also https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html)