Skip to content

Commit 3563b20

Browse files
committed
Update
1 parent 847785c commit 3563b20

File tree

2 files changed

+278
-0
lines changed

2 files changed

+278
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import json
2+
import mimetypes
3+
import os
4+
import threading
5+
import urllib.request
6+
import zipfile
7+
from urllib.parse import urlparse
8+
9+
import boto3
10+
import cfnresponse
11+
12+
s3 = boto3.resource('s3')
13+
14+
15+
def lambda_handler(event, context):
16+
print(json.dumps(event))
17+
response_data = {}
18+
response_data['Data'] = None
19+
20+
# Setup timer to catch timeouts
21+
t = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5,
22+
timeout, args=[event, context])
23+
t.start()
24+
25+
try:
26+
bucket = event['ResourceProperties']['BucketName']
27+
repo_subdir = event['ResourceProperties']['RepoSubdir']
28+
repo_url = event['ResourceProperties']['RepoURL']
29+
repo_name = urlparse(repo_url).path.split('/')[-2]
30+
local_file = download_repo(repo_url)
31+
extract(local_file, '/tmp')
32+
upload_to_s3(bucket, f'/tmp/{repo_name}-master/{repo_subdir}')
33+
cfnresponse.send(event, context, cfnresponse.SUCCESS,
34+
response_data, "CustomResourcePhysicalID")
35+
except Exception as e:
36+
print("Error: " + str(e))
37+
cfnresponse.send(event, context, cfnresponse.FAILED,
38+
response_data, "CustomResourcePhysicalID")
39+
finally:
40+
# Cancel timer before exit
41+
t.cancel()
42+
43+
44+
# Function that executes just before lambda execution times out
45+
def timeout(event, context, logger):
46+
print("Execution is about to time out, sending failure message")
47+
cfnresponse.send(event, context, cfnresponse.FAILED,
48+
{}, "CustomResourcePhysicalID", reason="Execution timed out")
49+
50+
51+
def download_repo(url):
52+
# i.e. https://github.com/linuxacademy/content-lambda-boto3/archive/master.zip
53+
url += 'archive/master.zip'
54+
print('Downloading repo: ' + url)
55+
file_name = os.path.basename(url) # i.e. master.zip
56+
local_file = os.path.join('/tmp', file_name)
57+
urllib.request.urlretrieve(url, local_file)
58+
st = os.stat(local_file)
59+
print(f'Downloaded {st.st_size} bytes')
60+
return local_file
61+
62+
63+
def extract(file, target_dir):
64+
print(f'Extracting {file} to {target_dir}')
65+
with zipfile.ZipFile(file, "r") as zip:
66+
zip.printdir()
67+
zip.extractall(target_dir)
68+
69+
70+
def upload_to_s3(bucket, path):
71+
print(f'Uploading {path} to s3://{bucket}')
72+
bucket = s3.Bucket(bucket)
73+
for subdir, dirs, files in os.walk(path):
74+
for file in files:
75+
full_path = os.path.join(subdir, file)
76+
mime_type = mimetypes.MimeTypes().guess_type(full_path)[0]
77+
with open(full_path, 'rb') as data:
78+
print(f'Uploading: {full_path} [{mime_type}]')
79+
bucket.put_object(
80+
Key=full_path[len(path)+1:], Body=data, ContentType=mime_type)
81+
82+
83+
if __name__ == "__main__":
84+
event = {
85+
'ResourceProperties': {
86+
'BucketName': 'deploytos3-s3bucket-t64wa3xyw4fu',
87+
'RepoSubdir': 'WebApps/Contact-Form/webapp',
88+
'RepoURL': 'https://github.com/linuxacademy/content-lambda-boto3/'
89+
},
90+
'ResponseURL': 'https://example.com'
91+
}
92+
lambda_handler(event, {})

0 commit comments

Comments
 (0)