Created
September 8, 2022 07:12
-
-
Save huevos-y-bacon/cf875b757b975b132aea98ff33495112 to your computer and use it in GitHub Desktop.
AWS CloudFormation and Lambda - All-in-one cloudformation stack with embedded lambda custom resource. See https://missionimpossiblecode.io/building-a-best-practice-cloudformation-custom-resource-pattern
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
# Source: https://missionimpossiblecode.io/building-a-best-practice-cloudformation-custom-resource-pattern | |
Parameters: | |
SpecifyVPCToUse: | |
Description: > | |
DefaultVPC - finds the VPC and configures all of its subnets for you. Otherwise type | |
in the VPC id of a VPC in the same region where you run the template. | |
All subnets and azs of the chosen vpc will be used. | |
The VPC and chosen subnets must be setup in a way that allows the runner instances | |
to resolve the DNS name and connect to port 443 on the GitLab instance URL you provide. | |
Default: DefaultVPC | |
Type: String | |
# While it is tempting to make the above parameter of type "AWS::EC2::VPC::Id" | |
# this prevents automatic discovery and usage of the DefaultVPC. | |
# However, if your organization NEVER uses default VPCs or disables them, changing | |
# the type to AWS::EC2::VPC::Id actually improves the user experience because users do not have to | |
# lookup VPC ids in the console. | |
Resources: | |
#Arguments: Vpc-id or "DefaultVPC" | |
#Returns: vpc-id, number of subnets and ordered list of subnetids and az ids. | |
# The index of these two return lists are correlated if it is desirable to choose less than the whole list using the CloudFormation function "Select" against both lists. | |
VPCInfoLambda: | |
Type: 'AWS::Lambda::Function' | |
Properties: | |
Description: Returns the lowercase version of a string | |
MemorySize: 256 | |
Runtime: python3.8 | |
Handler: index.handler | |
Role: !GetAtt CFCustomResourceLambdaRole.Arn | |
Timeout: 240 | |
Code: | |
ZipFile: | | |
import logging | |
import traceback | |
import signal | |
import cfnresponse | |
import boto3 | |
LOGGER = logging.getLogger() | |
LOGGER.setLevel(logging.INFO) | |
def handler(event, context): | |
# Setup alarm for remaining runtime minus a second | |
signal.alarm((int(context.get_remaining_time_in_millis() / 1000)) - 1) | |
try: | |
LOGGER.info('REQUEST RECEIVED:\n %s', event) | |
LOGGER.info('REQUEST RECEIVED:\n %s', context) | |
if event['RequestType'] == 'Delete': | |
LOGGER.info('DELETE!') | |
cfnresponse.send(event, context, "SUCCESS", { | |
"Message": "Resource deletion successful!"}) | |
return | |
elif event['RequestType'] == 'Update': | |
LOGGER.info('UPDATE!') | |
cfnresponse.send(event, context, "SUCCESS",{ | |
"Message": "Resource update successful!"}) | |
elif event['RequestType'] == 'Create': | |
LOGGER.info('CREATE!') | |
request_properties = event.get('ResourceProperties', None) | |
VpcToGet = event['ResourceProperties'].get('VpcToGet', '') | |
ec2 = boto3.resource('ec2') | |
VpcCheckedList = [] | |
TargetVPC = None | |
vpclist = ec2.vpcs.all() | |
for vpc in vpclist: | |
VpcCheckedList.append(vpc.id) | |
if VpcToGet == "DefaultVPC" and vpc.is_default == True: | |
TargetVPC=vpc | |
elif vpc.vpc_id == VpcToGet: | |
TargetVPC=vpc | |
if TargetVPC == None: | |
raise Exception(f'VPC {VpcToGet} was not found among the ones in this account and region, VPC which are: {", ".join(VpcCheckedList)}') | |
else: | |
VPCOutput = TargetVPC.id | |
subidlist = [] | |
zoneidlist = [] | |
subnets = list(TargetVPC.subnets.all()) | |
for subnet in subnets: | |
subidlist.append(subnet.id) | |
zoneidlist.append(subnet.availability_zone) | |
subidOutput = ",".join(subidlist) | |
zoneidOutput = ",".join(zoneidlist) | |
if not subnets: | |
raise Exception(f'There are no subnets in VPC: {VpcToGet}') | |
LOGGER.info('subnet ids are: %s', subidOutput) | |
LOGGER.info('zone ids are: %s', zoneidOutput) | |
responseData = {} | |
responseData['VPC_id'] = VPCOutput | |
responseData['OrderedSubnetIdList'] = subidOutput | |
responseData['OrderedZoneIdList'] = zoneidOutput | |
responseData['SubnetCount'] = len(subidlist) | |
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) | |
except Exception as err: | |
AccountRegionInfo=f'Occured in Account {context.invoked_function_arn.split(":")[4]} in region {context.invoked_function_arn.split(":")[3]}' | |
FinalMsg=str(err) + ' ' + AccountRegionInfo | |
LOGGER.info('ERROR: %s', FinalMsg) | |
LOGGER.info('TRACEBACK %s', traceback.print_tb(err.__traceback__)) | |
cfnresponse.send(event, context, "FAILED", { | |
"Message": "{FinalMsg}"}) | |
def timeout_handler(_signal, _frame): | |
'''Handle SIGALRM''' | |
raise Exception('Time exceeded') | |
signal.signal(signal.SIGALRM, timeout_handler) | |
#Custom Function IAM Role Declaration | |
CFCustomResourceLambdaRole: | |
Type: AWS::IAM::Role | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: "2012-10-17" | |
Statement: | |
- Effect: "Allow" | |
Principal: | |
Service: | |
- "lambda.amazonaws.com" | |
Action: | |
- "sts:AssumeRole" | |
Policies: | |
- PolicyName: "lambda-write-logs" | |
PolicyDocument: | |
Version: "2012-10-17" | |
Statement: | |
- Effect: "Allow" | |
Action: | |
- "logs:CreateLogGroup" | |
- "logs:CreateLogStream" | |
- "logs:PutLogEvents" | |
Resource: "arn:aws:logs:*:*" | |
- PolicyName: "describe-vpcs-and-subnets" | |
PolicyDocument: | |
Version: "2012-10-17" | |
Statement: | |
- Effect: "Allow" | |
Action: | |
- "ec2:DescribeVpcs" | |
- "ec2:DescribeSubnets" | |
Resource: "*" | |
#Calling Function to Retrieve Data | |
LookupVPCInfo: | |
Type: Custom::VPCInfo | |
Properties: | |
ServiceToken: !GetAtt VPCInfoLambda.Arn | |
VpcToGet: !Ref SpecifyVPCToUse | |
Outputs: | |
VPCZoneIdentifier: | |
Description: VPCZoneIdentifier | |
Value: !GetAtt LookupVPCInfo.OrderedSubnetIdList | |
AvailabilityZones: | |
Description: AvailabilityZones | |
Value: !GetAtt LookupVPCInfo.OrderedZoneIdList | |
# Value: !Split [",",!GetAtt LookupVPCInfo.OrderedZoneIdList] | |
# Export: | |
# Name: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment