sst 2.30.4 → 2.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constructs/App.js +7 -7
- package/constructs/BaseSite.d.ts +2 -13
- package/constructs/Distribution.d.ts +5 -10
- package/constructs/Distribution.js +6 -4
- package/constructs/Function.d.ts +6 -6
- package/constructs/Function.js +3 -3
- package/constructs/NextjsSite.d.ts +14 -2
- package/constructs/NextjsSite.js +204 -39
- package/constructs/RDS.js +2 -2
- package/constructs/Service.d.ts +0 -1
- package/constructs/Service.js +0 -1
- package/constructs/SsrSite.d.ts +58 -88
- package/constructs/SsrSite.js +136 -238
- package/constructs/StaticSite.d.ts +46 -33
- package/constructs/StaticSite.js +60 -76
- package/constructs/deprecated/NextjsSite.js +57 -71
- package/package.json +5 -4
- package/support/base-site-archiver.mjs +18 -18
- package/support/custom-resources/index.mjs +14875 -6761
- package/support/base-site-custom-resource/s3-handler.py +0 -195
- package/support/base-site-custom-resource/s3-upload.py +0 -89
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import os
|
|
3
|
-
import tempfile
|
|
4
|
-
import json
|
|
5
|
-
import glob
|
|
6
|
-
import traceback
|
|
7
|
-
import logging
|
|
8
|
-
import shutil
|
|
9
|
-
import boto3
|
|
10
|
-
import contextlib
|
|
11
|
-
import asyncio
|
|
12
|
-
import functools
|
|
13
|
-
from datetime import datetime
|
|
14
|
-
from uuid import uuid4
|
|
15
|
-
from botocore.config import Config
|
|
16
|
-
|
|
17
|
-
from urllib.request import Request, urlopen
|
|
18
|
-
from zipfile import ZipFile
|
|
19
|
-
|
|
20
|
-
logger = logging.getLogger()
|
|
21
|
-
logger.setLevel(logging.INFO)
|
|
22
|
-
|
|
23
|
-
config = Config(
|
|
24
|
-
read_timeout=900,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
s3 = boto3.resource('s3')
|
|
28
|
-
awslambda = boto3.client('lambda', config=config)
|
|
29
|
-
|
|
30
|
-
CFN_SUCCESS = "SUCCESS"
|
|
31
|
-
CFN_FAILED = "FAILED"
|
|
32
|
-
|
|
33
|
-
def handler(event, context):
|
|
34
|
-
|
|
35
|
-
def cfn_error(message=None):
|
|
36
|
-
logger.error("| cfn_error: %s" % message)
|
|
37
|
-
cfn_send(event, context, CFN_FAILED, reason=message)
|
|
38
|
-
|
|
39
|
-
try:
|
|
40
|
-
logger.info(event)
|
|
41
|
-
|
|
42
|
-
# cloudformation request type (create/update/delete)
|
|
43
|
-
request_type = event['RequestType']
|
|
44
|
-
|
|
45
|
-
# extract resource properties
|
|
46
|
-
props = event['ResourceProperties']
|
|
47
|
-
old_props = event.get('OldResourceProperties', {})
|
|
48
|
-
physical_id = event.get('PhysicalResourceId', None)
|
|
49
|
-
|
|
50
|
-
try:
|
|
51
|
-
sources = props['Sources']
|
|
52
|
-
dest_bucket_name = props['DestinationBucketName']
|
|
53
|
-
filenames = props.get('Filenames', None)
|
|
54
|
-
file_options = props.get('FileOptions', [])
|
|
55
|
-
replace_values = props.get('ReplaceValues', [])
|
|
56
|
-
except KeyError as e:
|
|
57
|
-
cfn_error("missing request resource property %s. props: %s" % (str(e), props))
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
# if we are creating a new resource, allocate a physical id for it
|
|
61
|
-
# otherwise, we expect physical id to be relayed by cloudformation
|
|
62
|
-
if request_type == "Create":
|
|
63
|
-
physical_id = "aws.cdk.s3deployment.%s" % str(uuid4())
|
|
64
|
-
else:
|
|
65
|
-
if not physical_id:
|
|
66
|
-
cfn_error("invalid request: request type is '%s' but 'PhysicalResourceId' is not defined" % request_type)
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
# delete or create/update
|
|
70
|
-
if request_type == "Update" or request_type == "Create":
|
|
71
|
-
loop = asyncio.get_event_loop()
|
|
72
|
-
loop.run_until_complete(s3_deploy_all(sources, dest_bucket_name, file_options, replace_values))
|
|
73
|
-
# purge old items
|
|
74
|
-
if filenames:
|
|
75
|
-
s3_purge(filenames, dest_bucket_name)
|
|
76
|
-
|
|
77
|
-
cfn_send(event, context, CFN_SUCCESS, physicalResourceId=physical_id)
|
|
78
|
-
except KeyError as e:
|
|
79
|
-
cfn_error("invalid request. Missing key %s" % str(e))
|
|
80
|
-
except Exception as e:
|
|
81
|
-
logger.exception(e)
|
|
82
|
-
cfn_error(str(e))
|
|
83
|
-
|
|
84
|
-
#---------------------------------------------------------------------------------------------------
|
|
85
|
-
# populate all files
|
|
86
|
-
async def s3_deploy_all(sources, dest_bucket_name, file_options, replace_values):
|
|
87
|
-
logger.info("| s3_deploy_all")
|
|
88
|
-
|
|
89
|
-
loop = asyncio.get_running_loop()
|
|
90
|
-
function_name = os.environ['UPLOADER_FUNCTION_NAME']
|
|
91
|
-
|
|
92
|
-
logger.info("| s3_deploy_all: uploader function: %s" % function_name)
|
|
93
|
-
|
|
94
|
-
await asyncio.gather(
|
|
95
|
-
*[
|
|
96
|
-
loop.run_in_executor(None, functools.partial(
|
|
97
|
-
s3_deploy,
|
|
98
|
-
function_name,
|
|
99
|
-
source,
|
|
100
|
-
dest_bucket_name,
|
|
101
|
-
file_options,
|
|
102
|
-
replace_values
|
|
103
|
-
))
|
|
104
|
-
for source in sources
|
|
105
|
-
]
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
#---------------------------------------------------------------------------------------------------
|
|
109
|
-
# populate all files
|
|
110
|
-
def s3_deploy(function_name, source, dest_bucket_name, file_options, replace_values):
|
|
111
|
-
logger.info("| s3_deploy")
|
|
112
|
-
|
|
113
|
-
response = awslambda.invoke(
|
|
114
|
-
FunctionName=function_name,
|
|
115
|
-
InvocationType="RequestResponse",
|
|
116
|
-
Payload=bytes(json.dumps({
|
|
117
|
-
'SourceBucketName': source['BucketName'],
|
|
118
|
-
'SourceObjectKey': source['ObjectKey'],
|
|
119
|
-
'DestinationBucketName': dest_bucket_name,
|
|
120
|
-
'FileOptions': file_options,
|
|
121
|
-
'ReplaceValues': replace_values,
|
|
122
|
-
}), encoding='utf8')
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
payload = response['Payload'].read()
|
|
126
|
-
result = json.loads(payload.decode("utf8"))
|
|
127
|
-
logger.info(result)
|
|
128
|
-
if (result['Status'] != True):
|
|
129
|
-
raise Exception("failed to upload to s3")
|
|
130
|
-
|
|
131
|
-
#---------------------------------------------------------------------------------------------------
|
|
132
|
-
# remove old files
|
|
133
|
-
def s3_purge(filenames, dest_bucket_name):
|
|
134
|
-
logger.info("| s3_purge")
|
|
135
|
-
|
|
136
|
-
source_bucket_name = filenames['BucketName']
|
|
137
|
-
source_object_key = filenames['ObjectKey']
|
|
138
|
-
s3_source = "s3://%s/%s" % (source_bucket_name, source_object_key)
|
|
139
|
-
|
|
140
|
-
# create a temporary working directory
|
|
141
|
-
workdir=tempfile.mkdtemp()
|
|
142
|
-
logger.info("| workdir: %s" % workdir)
|
|
143
|
-
|
|
144
|
-
# download the archive from the source and extract to "contents"
|
|
145
|
-
target_path=os.path.join(workdir, str(uuid4()))
|
|
146
|
-
logger.info("target_path: %s" % target_path)
|
|
147
|
-
aws_command("s3", "cp", s3_source, target_path)
|
|
148
|
-
with open(target_path) as f:
|
|
149
|
-
filepaths = f.read().splitlines()
|
|
150
|
-
|
|
151
|
-
#s3_dest get S3 files
|
|
152
|
-
for file in s3.Bucket(dest_bucket_name).objects.all():
|
|
153
|
-
if (file.key not in filepaths):
|
|
154
|
-
logger.info("| removing file %s", file.key)
|
|
155
|
-
file.delete()
|
|
156
|
-
|
|
157
|
-
#---------------------------------------------------------------------------------------------------
|
|
158
|
-
# executes an "aws" cli command
|
|
159
|
-
def aws_command(*args):
|
|
160
|
-
aws="/opt/awscli/aws" # from AwsCliLayer
|
|
161
|
-
logger.info("| aws %s" % ' '.join(args))
|
|
162
|
-
subprocess.check_call([aws] + list(args))
|
|
163
|
-
|
|
164
|
-
#---------------------------------------------------------------------------------------------------
|
|
165
|
-
# sends a response to cloudformation
|
|
166
|
-
def cfn_send(event, context, responseStatus, responseData={}, physicalResourceId=None, noEcho=False, reason=None):
|
|
167
|
-
|
|
168
|
-
responseUrl = event['ResponseURL']
|
|
169
|
-
logger.info(responseUrl)
|
|
170
|
-
|
|
171
|
-
responseBody = {}
|
|
172
|
-
responseBody['Status'] = responseStatus
|
|
173
|
-
responseBody['Reason'] = reason or ('See the details in CloudWatch Log Stream: ' + context.log_stream_name)
|
|
174
|
-
responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name
|
|
175
|
-
responseBody['StackId'] = event['StackId']
|
|
176
|
-
responseBody['RequestId'] = event['RequestId']
|
|
177
|
-
responseBody['LogicalResourceId'] = event['LogicalResourceId']
|
|
178
|
-
responseBody['NoEcho'] = noEcho
|
|
179
|
-
responseBody['Data'] = responseData
|
|
180
|
-
|
|
181
|
-
body = json.dumps(responseBody)
|
|
182
|
-
logger.info("| response body:\n" + body)
|
|
183
|
-
|
|
184
|
-
headers = {
|
|
185
|
-
'content-type' : '',
|
|
186
|
-
'content-length' : str(len(body))
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
try:
|
|
190
|
-
request = Request(responseUrl, method='PUT', data=bytes(body.encode('utf-8')), headers=headers)
|
|
191
|
-
with contextlib.closing(urlopen(request)) as response:
|
|
192
|
-
logger.info("| status code: " + response.reason)
|
|
193
|
-
except Exception as e:
|
|
194
|
-
logger.error("| unable to send response to CloudFormation")
|
|
195
|
-
logger.exception(e)
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import os
|
|
3
|
-
import tempfile
|
|
4
|
-
import glob
|
|
5
|
-
import logging
|
|
6
|
-
import shutil
|
|
7
|
-
import boto3
|
|
8
|
-
from uuid import uuid4
|
|
9
|
-
from zipfile import ZipFile
|
|
10
|
-
|
|
11
|
-
logger = logging.getLogger()
|
|
12
|
-
logger.setLevel(logging.INFO)
|
|
13
|
-
|
|
14
|
-
s3 = boto3.resource('s3')
|
|
15
|
-
|
|
16
|
-
def handler(event, context):
|
|
17
|
-
logger.info(event)
|
|
18
|
-
source_bucket_name = event['SourceBucketName']
|
|
19
|
-
source_object_key = event['SourceObjectKey']
|
|
20
|
-
dest_bucket_name = event['DestinationBucketName']
|
|
21
|
-
file_options = event.get('FileOptions', [])
|
|
22
|
-
replace_values = event.get('ReplaceValues', [])
|
|
23
|
-
s3_source_zip = "s3://%s/%s" % (source_bucket_name, source_object_key)
|
|
24
|
-
s3_dest = "s3://%s" % (dest_bucket_name)
|
|
25
|
-
logger.info("| s3_source_zip: %s" % s3_source_zip)
|
|
26
|
-
logger.info("| s3_dest: %s" % s3_dest)
|
|
27
|
-
s3_deploy(s3_source_zip, s3_dest, file_options, replace_values)
|
|
28
|
-
return { "Status": True }
|
|
29
|
-
|
|
30
|
-
#---------------------------------------------------------------------------------------------------
|
|
31
|
-
# populate all files from s3_source_zip to a destination bucket
|
|
32
|
-
def s3_deploy(s3_source_zip, s3_dest, file_options, replace_values):
|
|
33
|
-
|
|
34
|
-
# create a temporary working directory
|
|
35
|
-
workdir=tempfile.mkdtemp()
|
|
36
|
-
logger.info("| workdir: %s" % workdir)
|
|
37
|
-
|
|
38
|
-
# create a directory into which we extract the contents of the zip file
|
|
39
|
-
contents_dir=os.path.join(workdir, 'contents')
|
|
40
|
-
os.mkdir(contents_dir)
|
|
41
|
-
|
|
42
|
-
# download the archive from the source and extract to "contents"
|
|
43
|
-
archive=os.path.join(workdir, str(uuid4()))
|
|
44
|
-
logger.info("archive: %s" % archive)
|
|
45
|
-
aws_command("s3", "cp", s3_source_zip, archive)
|
|
46
|
-
logger.info("| extracting archive to: %s\n" % contents_dir)
|
|
47
|
-
with ZipFile(archive, "r") as zip:
|
|
48
|
-
zip.extractall(contents_dir)
|
|
49
|
-
|
|
50
|
-
# replace values in files
|
|
51
|
-
logger.info("replacing values: %s" % replace_values)
|
|
52
|
-
for replace_value in replace_values:
|
|
53
|
-
pattern = "%s/%s" % (contents_dir, replace_value['files'])
|
|
54
|
-
logger.info("| replacing pattern: %s", pattern)
|
|
55
|
-
for filepath in glob.iglob(pattern, recursive=True):
|
|
56
|
-
logger.info("| replacing pattern in file %s", filepath)
|
|
57
|
-
# In Gatsby, you can have folders named like a file
|
|
58
|
-
# ie. 404.html/ and it will get matched by "**/*.html"
|
|
59
|
-
if os.path.isfile(filepath):
|
|
60
|
-
with open(filepath) as file:
|
|
61
|
-
ori = file.read()
|
|
62
|
-
new = ori.replace(replace_value['search'], replace_value['replace'])
|
|
63
|
-
if ori != new:
|
|
64
|
-
logger.info("| updated")
|
|
65
|
-
with open(filepath, "w") as file:
|
|
66
|
-
file.write(new)
|
|
67
|
-
|
|
68
|
-
# sync from "contents" to destination
|
|
69
|
-
for file_option in file_options:
|
|
70
|
-
s3_command = ["s3", "cp"]
|
|
71
|
-
s3_command.extend([contents_dir, s3_dest])
|
|
72
|
-
s3_command.append("--recursive")
|
|
73
|
-
logger.info(file_option)
|
|
74
|
-
s3_command.extend(file_option)
|
|
75
|
-
aws_command(*s3_command)
|
|
76
|
-
|
|
77
|
-
s3_command = ["s3", "sync"]
|
|
78
|
-
s3_command.extend([contents_dir, s3_dest])
|
|
79
|
-
aws_command(*s3_command)
|
|
80
|
-
|
|
81
|
-
shutil.rmtree(workdir)
|
|
82
|
-
|
|
83
|
-
#---------------------------------------------------------------------------------------------------
|
|
84
|
-
# executes an "aws" cli command
|
|
85
|
-
def aws_command(*args):
|
|
86
|
-
aws="/opt/awscli/aws" # from AwsCliLayer
|
|
87
|
-
logger.info("| aws %s" % ' '.join(args))
|
|
88
|
-
subprocess.check_call([aws] + list(args))
|
|
89
|
-
|