1+ AWSTemplateFormatVersion : 2010-09-09
2+
3+ Description : Create an S3-hosted static website and deploy content from Github
4+
5+ Parameters :
6+
7+ RepoURL :
8+ Description : Github repository to upload into S3
9+ Type : String
10+ Default : ' https://github.com/linuxacademy/content-lambda-boto3/'
11+ MinLength : 1
12+ AllowedPattern : ' ^https?://[^\\s/$.?#].[^\\s]*$'
13+ ConstraintDescription : URL
14+ RepoSubdir :
15+ Description : Repository subdirectory to upload to S3
16+ Type : String
17+ Default : ' WebApps/Contact-Form/webapp'
18+ MinLength : 1
19+
20+
21+ Resources :
22+
23+ S3Bucket :
24+ Type : AWS::S3::Bucket
25+ Properties :
26+ AccessControl : PublicRead
27+ WebsiteConfiguration :
28+ IndexDocument : index.html
29+ DeletionPolicy : Retain
30+
31+ BucketPolicy :
32+ Type : AWS::S3::BucketPolicy
33+ Properties :
34+ PolicyDocument :
35+ Id : MyPolicy
36+ Version : 2012-10-17
37+ Statement :
38+ - Sid : PublicReadForGetBucketObjects
39+ Effect : Allow
40+ Principal : ' *'
41+ Action : ' s3:GetObject'
42+ Resource : !Sub "arn:aws:s3:::${S3Bucket}/*"
43+ Bucket : !Ref S3Bucket
44+
45+ LambdaDeployToS3Role :
46+ Type : AWS::IAM::Role
47+ Properties :
48+ AssumeRolePolicyDocument :
49+ Version : 2012-10-17
50+ Statement :
51+ - Effect : Allow
52+ Principal :
53+ Service :
54+ - lambda.amazonaws.com
55+ Action :
56+ - ' sts:AssumeRole'
57+ ManagedPolicyArns :
58+ - " arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
59+ Policies :
60+ - PolicyName : S3Policy
61+ PolicyDocument :
62+ Version : ' 2012-10-17'
63+ Statement :
64+ - Effect : Allow
65+ Action :
66+ - ' s3:PutObject'
67+ - ' S3:DeleteObject'
68+ Resource : !Sub "arn:aws:s3:::${S3Bucket}/*"
69+
70+ DeployToS3Function :
71+ Type : AWS::Lambda::Function
72+ Properties :
73+ Code :
74+ ZipFile : |
75+ import json
76+ import mimetypes
77+ import os
78+ import threading
79+ import urllib.request
80+ import zipfile
81+ from urllib.parse import urlparse
82+
83+ import boto3
84+ import cfnresponse
85+
86+ s3 = boto3.resource('s3')
87+
88+
89+ def lambda_handler(event, context):
90+ print(json.dumps(event))
91+ response_data = {}
92+ response_data['Data'] = None
93+
94+ # Setup timer to catch timeouts
95+ t = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5,
96+ timeout, args=[event, context])
97+ t.start()
98+
99+ try:
100+ bucket = event['ResourceProperties']['BucketName']
101+ repo_subdir = event['ResourceProperties']['RepoSubdir']
102+ repo_url = event['ResourceProperties']['RepoURL']
103+ repo_name = urlparse(repo_url).path.split('/')[-2]
104+ local_file = download_repo(repo_url)
105+ extract(local_file, '/tmp')
106+ upload_to_s3(bucket, f'/tmp/{repo_name}-master/{repo_subdir}')
107+ cfnresponse.send(event, context, cfnresponse.SUCCESS,
108+ response_data, "CustomResourcePhysicalID")
109+ except Exception as e:
110+ print("Error: " + str(e))
111+ cfnresponse.send(event, context, cfnresponse.FAILED,
112+ response_data, "CustomResourcePhysicalID")
113+ finally:
114+ # Cancel timer before exit
115+ t.cancel()
116+
117+
118+ # Function that executes just before lambda execution times out
119+ def timeout(event, context, logger):
120+ print("Execution is about to time out, sending failure message")
121+ cfnresponse.send(event, context, cfnresponse.FAILED,
122+ {}, "CustomResourcePhysicalID", reason="Execution timed out")
123+
124+
125+ def download_repo(url):
126+ # i.e. https://github.com/linuxacademy/content-lambda-boto3/archive/master.zip
127+ url += 'archive/master.zip'
128+ print('Downloading repo: ' + url)
129+ file_name = os.path.basename(url) # i.e. master.zip
130+ local_file = os.path.join('/tmp', file_name)
131+ urllib.request.urlretrieve(url, local_file)
132+ st = os.stat(local_file)
133+ print(f'Downloaded {st.st_size} bytes')
134+ return local_file
135+
136+
137+ def extract(file, target_dir):
138+ print(f'Extracting {file} to {target_dir}')
139+ with zipfile.ZipFile(file, "r") as zip:
140+ zip.printdir()
141+ zip.extractall(target_dir)
142+
143+
144+ def upload_to_s3(bucket, path):
145+ print(f'Uploading {path} to s3://{bucket}')
146+ bucket = s3.Bucket(bucket)
147+ for subdir, dirs, files in os.walk(path):
148+ for file in files:
149+ full_path = os.path.join(subdir, file)
150+ mime_type = mimetypes.MimeTypes().guess_type(full_path)[0]
151+ with open(full_path, 'rb') as data:
152+ print(f'Uploading: {full_path} [{mime_type}]')
153+ bucket.put_object(
154+ Key=full_path[len(path)+1:], Body=data, ContentType=mime_type)
155+
156+ Description : CloudFormation custom resource
157+ Handler : index.lambda_handler
158+ Runtime : python3.7
159+ Timeout : 60
160+ Role : !GetAtt LambdaDeployToS3Role.Arn
161+
162+ DeployToS3 :
163+ Type : Custom::DeployToS3
164+ Properties :
165+ ServiceToken : !GetAtt DeployToS3Function.Arn
166+ RepoURL : !Ref RepoURL
167+ RepoSubdir : !Ref RepoSubdir
168+ BucketName : !Ref S3Bucket
169+
170+
171+ Outputs :
172+
173+ WebsiteURL :
174+ Value : !GetAtt
175+ - S3Bucket
176+ - WebsiteURL
177+ Description : URL for website hosted on S3
178+
179+ S3BucketSecureURL :
180+ Value : !Join
181+ - ' '
182+ - - 'https://'
183+ - !GetAtt
184+ - S3Bucket
185+ - DomainName
186+ Description : Name of S3 bucket to hold website content
0 commit comments