12
12
class TestHandler (unittest .TestCase ):
13
13
def setUp (self ):
14
14
logger = logging .getLogger ()
15
- logger .addHandler (logging .NullHandler ())
16
15
17
16
# clean up old aws.out file (from previous runs)
18
17
try : os .remove ("aws.out" )
19
18
except OSError : pass
20
19
21
20
def test_invalid_request (self ):
22
21
resp = invoke_handler ("Create" , {}, expected_status = "FAILED" )
23
- self .assertEqual (resp ["Reason" ], "missing request resource property 'SourceBucketName'" )
22
+ self .assertEqual (resp ["Reason" ], "missing request resource property 'SourceBucketName'. props: {} " )
24
23
25
24
def test_create_update (self ):
26
25
invoke_handler ("Create" , {
@@ -34,6 +33,20 @@ def test_create_update(self):
34
33
"s3 sync --delete contents.zip s3://<dest-bucket-name>/"
35
34
)
36
35
36
+ def test_create_with_backslash_prefix_same_as_no_prefix (self ):
37
+ invoke_handler ("Create" , {
38
+ "SourceBucketName" : "<source-bucket>" ,
39
+ "SourceObjectKey" : "<source-object-key>" ,
40
+ "DestinationBucketName" : "<dest-bucket-name>" ,
41
+ "DestinationBucketKeyPrefix" : "/"
42
+ })
43
+
44
+ self .assertAwsCommands (
45
+ "s3 cp s3://<source-bucket>/<source-object-key> archive.zip" ,
46
+ "s3 sync --delete contents.zip s3://<dest-bucket-name>/"
47
+ )
48
+
49
+
37
50
def test_create_update_with_dest_key (self ):
38
51
invoke_handler ("Create" , {
39
52
"SourceBucketName" : "<source-bucket>" ,
@@ -47,12 +60,13 @@ def test_create_update_with_dest_key(self):
47
60
"s3 sync --delete contents.zip s3://<dest-bucket-name>/<dest-key-prefix>"
48
61
)
49
62
50
- def test_delete (self ):
63
+ def test_delete_no_retain (self ):
51
64
invoke_handler ("Delete" , {
52
65
"SourceBucketName" : "<source-bucket>" ,
53
66
"SourceObjectKey" : "<source-object-key>" ,
54
- "DestinationBucketName" : "<dest-bucket-name>"
55
- })
67
+ "DestinationBucketName" : "<dest-bucket-name>" ,
68
+ "RetainOnDelete" : "false"
69
+ }, physical_id = "<physicalid>" )
56
70
57
71
self .assertAwsCommands ("s3 rm s3://<dest-bucket-name>/ --recursive" )
58
72
@@ -61,18 +75,30 @@ def test_delete_with_dest_key(self):
61
75
"SourceBucketName" : "<source-bucket>" ,
62
76
"SourceObjectKey" : "<source-object-key>" ,
63
77
"DestinationBucketName" : "<dest-bucket-name>" ,
64
- "DestinationBucketKeyPrefix" : "<dest-key-prefix>"
65
- })
78
+ "DestinationBucketKeyPrefix" : "<dest-key-prefix>" ,
79
+ "RetainOnDelete" : "false"
80
+ }, physical_id = "<physicalid>" )
66
81
67
82
self .assertAwsCommands ("s3 rm s3://<dest-bucket-name>/<dest-key-prefix> --recursive" )
68
83
69
- def test_delete_with_retain (self ):
84
+ def test_delete_with_retain_explicit (self ):
70
85
invoke_handler ("Delete" , {
71
86
"SourceBucketName" : "<source-bucket>" ,
72
87
"SourceObjectKey" : "<source-object-key>" ,
73
88
"DestinationBucketName" : "<dest-bucket-name>" ,
74
89
"RetainOnDelete" : "true"
75
- })
90
+ }, physical_id = "<physicalid>" )
91
+
92
+ # no aws commands (retain)
93
+ self .assertAwsCommands ()
94
+
95
+ # RetainOnDelete=true is the default
96
+ def test_delete_with_retain_implicit_default (self ):
97
+ invoke_handler ("Delete" , {
98
+ "SourceBucketName" : "<source-bucket>" ,
99
+ "SourceObjectKey" : "<source-object-key>" ,
100
+ "DestinationBucketName" : "<dest-bucket-name>"
101
+ }, physical_id = "<physicalid>" )
76
102
77
103
# no aws commands (retain)
78
104
self .assertAwsCommands ()
@@ -83,7 +109,7 @@ def test_delete_with_retain_explicitly_false(self):
83
109
"SourceObjectKey" : "<source-object-key>" ,
84
110
"DestinationBucketName" : "<dest-bucket-name>" ,
85
111
"RetainOnDelete" : "false"
86
- })
112
+ }, physical_id = "<physicalid>" )
87
113
88
114
self .assertAwsCommands (
89
115
"s3 rm s3://<dest-bucket-name>/ --recursive"
@@ -100,7 +126,7 @@ def test_update_same_dest(self):
100
126
"DestinationBucketName" : "<dest-bucket-name>" ,
101
127
}, old_resource_props = {
102
128
"DestinationBucketName" : "<dest-bucket-name>" ,
103
- })
129
+ }, physical_id = "<physical-id>" )
104
130
105
131
self .assertAwsCommands (
106
132
"s3 cp s3://<source-bucket>/<source-object-key> archive.zip" ,
@@ -115,62 +141,136 @@ def test_update_new_dest_retain(self):
115
141
}, old_resource_props = {
116
142
"DestinationBucketName" : "<dest-bucket-name>" ,
117
143
"RetainOnDelete" : "true"
118
- })
144
+ }, physical_id = "<physical-id>" )
119
145
120
146
self .assertAwsCommands (
121
147
"s3 cp s3://<source-bucket>/<source-object-key> archive.zip" ,
122
148
"s3 sync --delete contents.zip s3://<dest-bucket-name>/"
123
149
)
124
150
125
- def test_update_new_dest_no_retain_explicit (self ):
151
+ def test_update_new_dest_no_retain (self ):
126
152
invoke_handler ("Update" , {
127
153
"SourceBucketName" : "<source-bucket>" ,
128
154
"SourceObjectKey" : "<source-object-key>" ,
129
155
"DestinationBucketName" : "<new-dest-bucket-name>" ,
156
+ "RetainOnDelete" : "false"
130
157
}, old_resource_props = {
131
158
"DestinationBucketName" : "<old-dest-bucket-name>" ,
132
159
"DestinationBucketKeyPrefix" : "<old-dest-prefix>" ,
133
160
"RetainOnDelete" : "false"
134
- })
161
+ }, physical_id = "<physical-id>" )
135
162
136
163
self .assertAwsCommands (
137
164
"s3 rm s3://<old-dest-bucket-name>/<old-dest-prefix> --recursive" ,
138
165
"s3 cp s3://<source-bucket>/<source-object-key> archive.zip" ,
139
166
"s3 sync --delete contents.zip s3://<new-dest-bucket-name>/"
140
167
)
141
168
142
- def test_update_new_dest_no_retain_implicit (self ):
169
+ def test_update_new_dest_retain_implicit (self ):
143
170
invoke_handler ("Update" , {
144
171
"SourceBucketName" : "<source-bucket>" ,
145
172
"SourceObjectKey" : "<source-object-key>" ,
146
173
"DestinationBucketName" : "<new-dest-bucket-name>" ,
147
174
}, old_resource_props = {
148
175
"DestinationBucketName" : "<old-dest-bucket-name>" ,
149
176
"DestinationBucketKeyPrefix" : "<old-dest-prefix>"
150
- })
177
+ }, physical_id = "<physical-id>" )
151
178
152
179
self .assertAwsCommands (
153
- "s3 rm s3://<old-dest-bucket-name>/<old-dest-prefix> --recursive" ,
154
180
"s3 cp s3://<source-bucket>/<source-object-key> archive.zip" ,
155
181
"s3 sync --delete contents.zip s3://<new-dest-bucket-name>/"
156
182
)
157
183
158
- def test_update_new_dest_prefix_no_retain_implicit (self ):
184
+ def test_update_new_dest_prefix_no_retain (self ):
159
185
invoke_handler ("Update" , {
160
186
"SourceBucketName" : "<source-bucket>" ,
161
187
"SourceObjectKey" : "<source-object-key>" ,
162
188
"DestinationBucketName" : "<dest-bucket-name>" ,
163
- "DestinationBucketKeyPrefix" : "<new-dest-prefix>"
189
+ "DestinationBucketKeyPrefix" : "<new-dest-prefix>" ,
190
+ "RetainOnDelete" : "false"
164
191
}, old_resource_props = {
165
192
"DestinationBucketName" : "<dest-bucket-name>" ,
166
- })
193
+ "RetainOnDelete" : "false"
194
+ }, physical_id = "<physical id>" )
167
195
168
196
self .assertAwsCommands (
169
197
"s3 rm s3://<dest-bucket-name>/ --recursive" ,
170
198
"s3 cp s3://<source-bucket>/<source-object-key> archive.zip" ,
171
199
"s3 sync --delete contents.zip s3://<dest-bucket-name>/<new-dest-prefix>"
172
200
)
173
201
202
+ def test_update_new_dest_prefix_retain_implicit (self ):
203
+ invoke_handler ("Update" , {
204
+ "SourceBucketName" : "<source-bucket>" ,
205
+ "SourceObjectKey" : "<source-object-key>" ,
206
+ "DestinationBucketName" : "<dest-bucket-name>" ,
207
+ "DestinationBucketKeyPrefix" : "<new-dest-prefix>"
208
+ }, old_resource_props = {
209
+ "DestinationBucketName" : "<dest-bucket-name>" ,
210
+ }, physical_id = "<physical id>" )
211
+
212
+ self .assertAwsCommands (
213
+ "s3 cp s3://<source-bucket>/<source-object-key> archive.zip" ,
214
+ "s3 sync --delete contents.zip s3://<dest-bucket-name>/<new-dest-prefix>"
215
+ )
216
+
217
+ #
218
+ # physical id
219
+ #
220
+
221
+ def test_physical_id_allocated_on_create_and_reused_afterwards (self ):
222
+ create_resp = invoke_handler ("Create" , {
223
+ "SourceBucketName" : "<source-bucket>" ,
224
+ "SourceObjectKey" : "<source-object-key>" ,
225
+ "DestinationBucketName" : "<dest-bucket-name>" ,
226
+ })
227
+
228
+ phid = create_resp ['PhysicalResourceId' ]
229
+ self .assertTrue (phid .startswith ('aws.cdk.s3deployment' ))
230
+
231
+ # now issue an update and pass in the physical id. expect the same
232
+ # one to be returned back
233
+ update_resp = invoke_handler ("Update" , {
234
+ "SourceBucketName" : "<source-bucket>" ,
235
+ "SourceObjectKey" : "<source-object-key>" ,
236
+ "DestinationBucketName" : "<new-dest-bucket-name>" ,
237
+ }, old_resource_props = {
238
+ "DestinationBucketName" : "<dest-bucket-name>" ,
239
+ }, physical_id = phid )
240
+ self .assertEqual (update_resp ['PhysicalResourceId' ], phid )
241
+
242
+ # now issue a delete, and make sure this also applies
243
+ delete_resp = invoke_handler ("Delete" , {
244
+ "SourceBucketName" : "<source-bucket>" ,
245
+ "SourceObjectKey" : "<source-object-key>" ,
246
+ "DestinationBucketName" : "<dest-bucket-name>" ,
247
+ "RetainOnDelete" : "false"
248
+ }, physical_id = phid )
249
+ self .assertEqual (delete_resp ['PhysicalResourceId' ], phid )
250
+
251
+ def test_fails_when_physical_id_not_present_in_update (self ):
252
+ update_resp = invoke_handler ("Update" , {
253
+ "SourceBucketName" : "<source-bucket>" ,
254
+ "SourceObjectKey" : "<source-object-key>" ,
255
+ "DestinationBucketName" : "<new-dest-bucket-name>" ,
256
+ }, old_resource_props = {
257
+ "DestinationBucketName" : "<dest-bucket-name>" ,
258
+ }, expected_status = "FAILED" )
259
+
260
+ self .assertEqual (update_resp ['Reason' ], "invalid request: request type is 'Update' but 'PhysicalResourceId' is not defined" )
261
+
262
+ def test_fails_when_physical_id_not_present_in_delete (self ):
263
+ update_resp = invoke_handler ("Delete" , {
264
+ "SourceBucketName" : "<source-bucket>" ,
265
+ "SourceObjectKey" : "<source-object-key>" ,
266
+ "DestinationBucketName" : "<new-dest-bucket-name>" ,
267
+ }, old_resource_props = {
268
+ "DestinationBucketName" : "<dest-bucket-name>" ,
269
+ }, expected_status = "FAILED" )
270
+
271
+ self .assertEqual (update_resp ['Reason' ], "invalid request: request type is 'Delete' but 'PhysicalResourceId' is not defined" )
272
+
273
+
174
274
# asserts that a given list of "aws xxx" commands have been invoked (in order)
175
275
def assertAwsCommands (self , * expected ):
176
276
actual = read_aws_out ()
@@ -193,7 +293,7 @@ def read_aws_out():
193
293
# requestType: CloudFormation request type ("Create", "Update", "Delete")
194
294
# resourceProps: map to pass to "ResourceProperties"
195
295
# expected_status: "SUCCESS" or "FAILED"
196
- def invoke_handler (requestType , resourceProps , old_resource_props = None , expected_status = 'SUCCESS' ):
296
+ def invoke_handler (requestType , resourceProps , old_resource_props = None , physical_id = None , expected_status = 'SUCCESS' ):
197
297
response_url = '<response-url>'
198
298
199
299
event = {
@@ -208,6 +308,9 @@ def invoke_handler(requestType, resourceProps, old_resource_props=None, expected
208
308
if old_resource_props :
209
309
event ['OldResourceProperties' ] = old_resource_props
210
310
311
+ if physical_id :
312
+ event ['PhysicalResourceId' ] = physical_id
313
+
211
314
class ContextMock : log_stream_name = 'log_stream'
212
315
class ResponseMock : reason = 'OK'
213
316
0 commit comments