Last active
June 29, 2020 14:24
-
-
Save Kailashcj/9a6bd2c5e76aff7e60c8e5ece78e7ae9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| AWSTemplateFormatVersion: '2010-09-09' | |
| Description: >- | |
| This template deploys a new VPC and creates a new subnet and custom route table. | |
| It uses a cloudformation custom resource which invokes a lambda function to | |
| set the custom route table as the defaul MAIN route table. | |
| Lambda function is using boto3(python sdk) function 'replace_route_table_association(**kwargs)' to replace the main routetable. | |
| replace_route_table_association(**kwargs) function itself needs two parameters to make this change. | |
| parameter1 # routetableassociationId of the existing main routetable. This is identified by iterating each route table in VPC | |
| parameter2 # routetableId of the custom routetable. This is a reference to the new route table resource created in this template. | |
| Parameters: | |
| VpcName: | |
| Description: Name of the New VPC | |
| Type: String | |
| AvailabilityZone: | |
| Description: AvailabilityZone for this deployment | |
| Type: AWS::EC2::AvailabilityZone::Name | |
| VpcCIDR: | |
| Description: CIDR for this VPC. For example, 192.168.0.0/20 | |
| Type: String | |
| AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([2][0]))$ # only /20 allowed | |
| Resources: | |
| VPC: | |
| Type: AWS::EC2::VPC | |
| Properties: | |
| CidrBlock: !Ref VpcCIDR | |
| EnableDnsSupport: true | |
| EnableDnsHostnames: true | |
| Tags: | |
| - Key: Name | |
| Value: !Ref VpcName | |
| InternetGateway: | |
| Type: AWS::EC2::InternetGateway | |
| Properties: | |
| Tags: | |
| - Key: Name | |
| Value: !Sub ${VpcName}-InternetGateway | |
| InternetGatewayAttachment: | |
| Type: AWS::EC2::VPCGatewayAttachment | |
| Properties: | |
| InternetGatewayId: !Ref InternetGateway | |
| VpcId: !Ref VPC | |
| MySubnet: | |
| Type: AWS::EC2::Subnet | |
| Properties: | |
| VpcId: !Ref VPC | |
| AvailabilityZone: !Ref AvailabilityZone | |
| CidrBlock: 192.168.0.0/22 | |
| MapPublicIpOnLaunch: true | |
| Tags: | |
| - Key: Name | |
| Value: !Sub ${VpcName}-Subnet1 | |
| MyCustomRouteTable: | |
| Type: AWS::EC2::RouteTable | |
| Properties: | |
| VpcId: !Ref VPC | |
| Tags: | |
| - Key: Name | |
| Value: !Sub ${VpcName}-MyCustomRouteTable | |
| MyCustomRoute: | |
| Type: AWS::EC2::Route | |
| DependsOn: InternetGatewayAttachment | |
| Properties: | |
| RouteTableId: !Ref MyCustomRouteTable | |
| DestinationCidrBlock: 0.0.0.0/0 | |
| GatewayId: !Ref InternetGateway | |
| MySubnetRouteTableAssociation: | |
| Type: AWS::EC2::SubnetRouteTableAssociation | |
| Properties: | |
| RouteTableId: !Ref MyCustomRouteTable | |
| SubnetId: !Ref MySubnet | |
| NoIngressSecurityGroup: | |
| Type: AWS::EC2::SecurityGroup | |
| Properties: | |
| GroupName: "no-ingress-sg" | |
| GroupDescription: "Security group with no ingress rule" | |
| VpcId: !Ref VPC | |
| CustomFunction: | |
| Type: Custom::ReplaceMainRouteTable | |
| Properties: | |
| ServiceToken: !GetAtt 'ReplaceMainRouteTableLambdaFunction.Arn' #reference to lambda function | |
| vpcid: !Ref VPC #parameter1 for lambda function (vpcid of new vpc) | |
| routetable_id: !Ref MyCustomRouteTable #parameter 2 for lambda function(routetableid of custom routetable) | |
| ReplaceMainRouteTableLambdaFunction: | |
| Type: AWS::Lambda::Function | |
| Properties: | |
| Role: !GetAtt CustomResourceLambdaExecutionRole.Arn #permissions to execute lambda function | |
| FunctionName: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:my-lambda-${VpcName}' | |
| Handler: "index.lambda_handler" | |
| Timeout: 20 | |
| Runtime: python2.7 | |
| Code: | |
| ZipFile: | | |
| import logging | |
| import boto3 | |
| import cfnresponse | |
| logger = logging.getLogger() | |
| logger.setLevel(logging.INFO) | |
| ec2 = boto3.resource('ec2') | |
| client = boto3.client('ec2') | |
| def replacert(vpcid, routetable_id): #function defined to replace RT | |
| vpc = ec2.Vpc(vpcid) | |
| route_tables = vpc.route_tables.all() | |
| newid = dict() | |
| for r in route_tables: #query each routetable in vpc | |
| for eachline in ec2.RouteTable(r.id).associations_attribute: | |
| if eachline['Main'] is True: # if this routetable is Main routetable, replace using below code | |
| newid= client.replace_route_table_association(AssociationId=eachline.get('RouteTableAssociationId'), RouteTableId=routetable_id) | |
| return newid.get('NewAssociationId') | |
| def get_default_rtid(vpcid): #Capture route tableId of VPC Default route table. This is needed during stack deletion. | |
| vpc = ec2.Vpc(vpcid) | |
| route_tables = vpc.route_tables.all() | |
| for r in route_tables: | |
| if not r.tags: | |
| return r.id | |
| def set_main(vpcid, routetable_id): | |
| vpc = ec2.Vpc(vpcid) | |
| route_tables = vpc.route_tables.all() | |
| newid = dict() | |
| for r in route_tables: | |
| for eachline in ec2.RouteTable(r.id).associations_attribute: | |
| if eachline['Main'] is True: | |
| newid= client.replace_route_table_association(AssociationId=eachline.get('RouteTableAssociationId'), RouteTableId=routetable_id) | |
| return newid.get('NewAssociationId') | |
| def lambda_handler(event, context): | |
| try: | |
| logger.info('got event {}'.format(event)) | |
| responseData = {} | |
| vpcid = event['ResourceProperties'].get('vpcid') #get parameter1 | |
| routetable_id = event['ResourceProperties'].get('routetable_id') #get parameter2 | |
| if event['RequestType'] == 'Create': #Create Resource | |
| logger.info('vpcid is {} and routetable_id is {}'.format(vpcid,routetable_id)) | |
| res = replacert(vpcid, routetable_id) #calling function replacert from lambda_handler | |
| logger.info('value returned by replacert {}'.format(res)) | |
| responseData['NewAssociationId'] = res | |
| logger.info('value of responseData is {}'.format(responseData['NewAssociationId'])) | |
| cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, responseData['NewAssociationId']) | |
| if event['RequestType'] == 'Delete': | |
| logger.info('vpcid is {}'.format(vpcid)) | |
| default_id = get_default_rtid(vpcid) | |
| logger.info('default_route_table_id is {}'.format(default_id)) | |
| unset = set_main(vpcid,default_id) | |
| logger.info('value returned by set_main {}'.format(unset)) | |
| responseData['NewAssociationId'] = unset | |
| cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) | |
| except: | |
| logger.info('FAILED!') | |
| cfnresponse.send(event, context, "FAILED", {"Message": "Exception during processing"}) | |
| CustomResourceLambdaExecutionRole: | |
| Type: AWS::IAM::Role | |
| Properties: | |
| AssumeRolePolicyDocument: | |
| Version: '2012-10-17' | |
| Statement: | |
| - Effect: Allow | |
| Principal: | |
| Service: | |
| - lambda.amazonaws.com | |
| Action: | |
| - sts:AssumeRole | |
| Condition: {} | |
| Path: / | |
| ManagedPolicyArns: | |
| - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole | |
| - arn:aws:iam::aws:policy/AmazonVPCFullAccess | |
| - arn:aws:iam::aws:policy/AmazonEC2FullAccess | |
| Outputs: | |
| MainRouteTable: | |
| Description: A reference to the new main route table | |
| Value: !Ref MyCustomRouteTable |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment