sst 2.30.3 → 2.31.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.
@@ -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
-