Skip to content

Commit cfd688e

Browse files
authoredNov 7, 2024
Add EventBridge S3Events notifications (#70)
1 parent 906610f commit cfd688e

File tree

2 files changed

+564
-0
lines changed

2 files changed

+564
-0
lines changed
 

‎Sources/AWSLambdaEvents/Cloudwatch.swift

+460
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,463 @@ public enum CloudwatchDetails {
135135
let type: any CloudwatchDetail.Type
136136
}
137137
}
138+
139+
// MARK: - S3 Event Notification
140+
141+
/// https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html
142+
143+
public typealias CloudWatchS3ObjectCreatedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectCreatedNotification>
144+
public typealias CloudWatchS3ObjectDeletedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectDeletedNotification>
145+
public typealias CloudWatchS3ObjectRestoreInitiatedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectRestoreInitiatedNotification>
146+
public typealias CloudWatchS3ObjectRestoreCompletedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectRestoreCompletedNotification>
147+
public typealias CloudWatchS3ObjectRestoreExpiredNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectRestoreExpiredNotification>
148+
public typealias CloudWatchS3ObjectStorageClassChangedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectStorageClassChangedNotification>
149+
public typealias CloudWatchS3ObjectAccessTierChangedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectAccessTierChangedNotification>
150+
public typealias CloudWatchS3ObjectACLUpdatedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectACLUpdatedNotification>
151+
public typealias CloudWatchS3ObjectTagsAddedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectTagsAddedNotification>
152+
public typealias CloudWatchS3ObjectTagsDeletedNotificationEvent = CloudwatchEvent<CloudwatchDetails.S3.ObjectTagsDeletedNotification>
153+
154+
extension CloudwatchDetails {
155+
public enum S3: Sendable {
156+
public struct ObjectCreatedNotification: CloudwatchDetail {
157+
public static let name: String = "Object Created"
158+
159+
public struct Bucket: Codable, Sendable {
160+
public let name: String
161+
}
162+
163+
public struct Object: Codable, Sendable {
164+
public let key: String
165+
public let size: UInt64
166+
public let etag: String
167+
public let versionId: String?
168+
public let sequencer: String
169+
170+
enum CodingKeys: String, CodingKey {
171+
case key
172+
case size
173+
case etag
174+
case versionId = "version-id"
175+
case sequencer
176+
}
177+
}
178+
179+
public enum Reason: String, Codable, Sendable {
180+
case putObject = "PutObject"
181+
case postObject = "POST Object"
182+
case copyObject = "CopyObject"
183+
case completeMultipartUpload = "CompleteMultipartUpload"
184+
}
185+
186+
public let version: String
187+
public let bucket: Bucket
188+
public let object: Object
189+
public let requestId: String
190+
public let requester: String
191+
public let sourceIpAddress: String
192+
public let reason: Reason
193+
194+
enum CodingKeys: String, CodingKey {
195+
case version
196+
case bucket
197+
case object
198+
case requestId = "request-id"
199+
case requester
200+
case sourceIpAddress = "source-ip-address"
201+
case reason
202+
}
203+
}
204+
205+
public struct ObjectDeletedNotification: CloudwatchDetail {
206+
public static let name: String = "Object Deleted"
207+
208+
public struct Bucket: Codable, Sendable {
209+
public let name: String
210+
}
211+
212+
public struct Object: Codable, Sendable {
213+
public let key: String
214+
public let etag: String
215+
public let versionId: String?
216+
public let sequencer: String
217+
218+
enum CodingKeys: String, CodingKey {
219+
case key
220+
case etag
221+
case versionId = "version-id"
222+
case sequencer
223+
}
224+
}
225+
226+
public enum Reason: String, Codable, Sendable {
227+
case deleteObject = "DeleteObject"
228+
case lifecycleExpiration = "Lifecycle Expiration"
229+
}
230+
231+
public enum DeletionType: String, Codable, Sendable {
232+
case permanentlyDeleted = "Permanently Deleted"
233+
case deleteMarkerCreated = "Delete Marker Created"
234+
}
235+
236+
public let version: String
237+
public let bucket: Bucket
238+
public let object: Object
239+
public let requestId: String
240+
public let requester: String
241+
public let sourceIpAddress: String
242+
public let reason: Reason
243+
public let deletionType: DeletionType
244+
245+
enum CodingKeys: String, CodingKey {
246+
case version
247+
case bucket
248+
case object
249+
case requestId = "request-id"
250+
case requester
251+
case sourceIpAddress = "source-ip-address"
252+
case reason
253+
case deletionType = "deletion-type"
254+
}
255+
}
256+
257+
public struct ObjectRestoreInitiatedNotification: CloudwatchDetail {
258+
public static let name: String = "Object Restore Initiated"
259+
260+
public struct Bucket: Codable, Sendable {
261+
public let name: String
262+
}
263+
264+
public struct Object: Codable, Sendable {
265+
public let key: String
266+
public let size: UInt64
267+
public let etag: String
268+
public let versionId: String?
269+
270+
enum CodingKeys: String, CodingKey {
271+
case key
272+
case size
273+
case etag
274+
case versionId = "version-id"
275+
}
276+
}
277+
278+
public enum SourceStorageClass: String, Codable, Sendable {
279+
case standard = "STANDARD"
280+
case reducedRedundancy = "REDUCED_REDUNDANCY"
281+
case standardIA = "STANDARD_IA"
282+
case onezoneIA = "ONEZONE_IA"
283+
case intelligentTiering = "INTELLIGENT_TIERING"
284+
case glacier = "GLACIER"
285+
case deepArchive = "DEEP_ARCHIVE"
286+
case outposts = "OUTPOSTS"
287+
case glacierIr = "GLACIER_IR"
288+
}
289+
290+
public let version: String
291+
public let bucket: Bucket
292+
public let object: Object
293+
public let requestId: String
294+
public let requester: String
295+
public let sourceIpAddress: String
296+
public let sourceStorageClass: SourceStorageClass
297+
298+
enum CodingKeys: String, CodingKey {
299+
case version
300+
case bucket
301+
case object
302+
case requestId = "request-id"
303+
case requester
304+
case sourceIpAddress = "source-ip-address"
305+
case sourceStorageClass = "source-storage-class"
306+
}
307+
}
308+
309+
public struct ObjectRestoreCompletedNotification: CloudwatchDetail {
310+
public static let name: String = "Object Restore Completed"
311+
312+
public struct Bucket: Codable, Sendable {
313+
public let name: String
314+
}
315+
316+
public struct Object: Codable, Sendable {
317+
public let key: String
318+
public let size: UInt64
319+
public let etag: String
320+
public let versionId: String?
321+
322+
enum CodingKeys: String, CodingKey {
323+
case key
324+
case size
325+
case etag
326+
case versionId = "version-id"
327+
}
328+
}
329+
330+
public enum SourceStorageClass: String, Codable, Sendable {
331+
case standard = "STANDARD"
332+
case reducedRedundancy = "REDUCED_REDUNDANCY"
333+
case standardIA = "STANDARD_IA"
334+
case onezoneIA = "ONEZONE_IA"
335+
case intelligentTiering = "INTELLIGENT_TIERING"
336+
case glacier = "GLACIER"
337+
case deepArchive = "DEEP_ARCHIVE"
338+
case outposts = "OUTPOSTS"
339+
case glacierIr = "GLACIER_IR"
340+
}
341+
342+
public let version: String
343+
public let bucket: Bucket
344+
public let object: Object
345+
public let requestId: String
346+
public let requester: String
347+
@ISO8601Coding
348+
public var restoreExpiryTime: Date
349+
public let sourceStorageClass: SourceStorageClass
350+
351+
enum CodingKeys: String, CodingKey {
352+
case version
353+
case bucket
354+
case object
355+
case requestId = "request-id"
356+
case requester
357+
case restoreExpiryTime = "restore-expiry-time"
358+
case sourceStorageClass = "source-storage-class"
359+
}
360+
}
361+
362+
public struct ObjectRestoreExpiredNotification: CloudwatchDetail {
363+
public static let name: String = "Object Restore Expired"
364+
365+
public struct Bucket: Codable, Sendable {
366+
public let name: String
367+
}
368+
369+
public struct Object: Codable, Sendable {
370+
public let key: String
371+
public let etag: String
372+
public let versionId: String?
373+
374+
enum CodingKeys: String, CodingKey {
375+
case key
376+
case etag
377+
case versionId = "version-id"
378+
}
379+
}
380+
381+
public let version: String
382+
public let bucket: Bucket
383+
public let object: Object
384+
public let requestId: String
385+
public let requester: String
386+
387+
enum CodingKeys: String, CodingKey {
388+
case version
389+
case bucket
390+
case object
391+
case requestId = "request-id"
392+
case requester
393+
}
394+
}
395+
396+
public struct ObjectStorageClassChangedNotification: CloudwatchDetail {
397+
public static let name: String = "Object Storage Class Changed"
398+
399+
public struct Bucket: Codable, Sendable {
400+
public let name: String
401+
}
402+
403+
public struct Object: Codable, Sendable {
404+
public let key: String
405+
public let size: UInt64
406+
public let etag: String
407+
public let versionId: String?
408+
409+
enum CodingKeys: String, CodingKey {
410+
case key
411+
case size
412+
case etag
413+
case versionId = "version-id"
414+
}
415+
}
416+
417+
public enum DestinationStorageClass: String, Codable, Sendable {
418+
case standard = "STANDARD"
419+
case reducedRedundancy = "REDUCED_REDUNDANCY"
420+
case standardIA = "STANDARD_IA"
421+
case onezoneIA = "ONEZONE_IA"
422+
case intelligentTiering = "INTELLIGENT_TIERING"
423+
case glacier = "GLACIER"
424+
case deepArchive = "DEEP_ARCHIVE"
425+
case outposts = "OUTPOSTS"
426+
case glacierIr = "GLACIER_IR"
427+
}
428+
429+
public let version: String
430+
public let bucket: Bucket
431+
public let object: Object
432+
public let requestId: String
433+
public let requester: String
434+
public let destinationStorageClass: DestinationStorageClass
435+
436+
enum CodingKeys: String, CodingKey {
437+
case version
438+
case bucket
439+
case object
440+
case requestId = "request-id"
441+
case requester
442+
case destinationStorageClass = "destination-storage-class"
443+
}
444+
}
445+
446+
public struct ObjectAccessTierChangedNotification: CloudwatchDetail {
447+
public static let name: String = "Object Access Tier Changed"
448+
449+
public struct Bucket: Codable, Sendable {
450+
public let name: String
451+
}
452+
453+
public struct Object: Codable, Sendable {
454+
public let key: String
455+
public let size: UInt64
456+
public let etag: String
457+
public let versionId: String?
458+
459+
enum CodingKeys: String, CodingKey {
460+
case key
461+
case size
462+
case etag
463+
case versionId = "version-id"
464+
}
465+
}
466+
467+
public enum DestinationAccessTier: String, Codable, Sendable {
468+
case archiveAccess = "ARCHIVE_ACCESS"
469+
case deepArchiveAccess = "DEEP_ARCHIVE_ACCESS"
470+
}
471+
472+
public let version: String
473+
public let bucket: Bucket
474+
public let object: Object
475+
public let requestId: String
476+
public let requester: String
477+
public let destinationAccessTier: DestinationAccessTier
478+
479+
enum CodingKeys: String, CodingKey {
480+
case version
481+
case bucket
482+
case object
483+
case requestId = "request-id"
484+
case requester
485+
case destinationAccessTier = "destination-access-tier"
486+
}
487+
}
488+
489+
public struct ObjectACLUpdatedNotification: CloudwatchDetail {
490+
public static let name: String = "Object ACL Updated"
491+
492+
public struct Bucket: Codable, Sendable {
493+
public let name: String
494+
}
495+
496+
public struct Object: Codable, Sendable {
497+
public let key: String
498+
public let etag: String
499+
public let versionId: String?
500+
501+
enum CodingKeys: String, CodingKey {
502+
case key
503+
case etag
504+
case versionId = "version-id"
505+
}
506+
}
507+
508+
public let version: String
509+
public let bucket: Bucket
510+
public let object: Object
511+
public let requestId: String
512+
public let requester: String
513+
public let sourceIpAddress: String
514+
515+
enum CodingKeys: String, CodingKey {
516+
case version
517+
case bucket
518+
case object
519+
case requestId = "request-id"
520+
case requester
521+
case sourceIpAddress = "source-ip-address"
522+
}
523+
}
524+
525+
public struct ObjectTagsAddedNotification: CloudwatchDetail {
526+
public static let name: String = "Object Tags Added"
527+
528+
public struct Bucket: Codable, Sendable {
529+
public let name: String
530+
}
531+
532+
public struct Object: Codable, Sendable {
533+
public let key: String
534+
public let etag: String
535+
public let versionId: String?
536+
537+
enum CodingKeys: String, CodingKey {
538+
case key
539+
case etag
540+
case versionId = "version-id"
541+
}
542+
}
543+
544+
public let version: String
545+
public let bucket: Bucket
546+
public let object: Object
547+
public let requestId: String
548+
public let requester: String
549+
public let sourceIpAddress: String
550+
551+
enum CodingKeys: String, CodingKey {
552+
case version
553+
case bucket
554+
case object
555+
case requestId = "request-id"
556+
case requester
557+
case sourceIpAddress = "source-ip-address"
558+
}
559+
}
560+
561+
public struct ObjectTagsDeletedNotification: CloudwatchDetail {
562+
public static let name: String = "Object Tags Deleted"
563+
564+
public struct Bucket: Codable, Sendable {
565+
public let name: String
566+
}
567+
568+
public struct Object: Codable, Sendable {
569+
public let key: String
570+
public let etag: String
571+
public let versionId: String?
572+
573+
enum CodingKeys: String, CodingKey {
574+
case key
575+
case etag
576+
case versionId = "version-id"
577+
}
578+
}
579+
580+
public let version: String
581+
public let bucket: Bucket
582+
public let object: Object
583+
public let requestId: String
584+
public let requester: String
585+
public let sourceIpAddress: String
586+
587+
enum CodingKeys: String, CodingKey {
588+
case version
589+
case bucket
590+
case object
591+
case requestId = "request-id"
592+
case requester
593+
case sourceIpAddress = "source-ip-address"
594+
}
595+
}
596+
}
597+
}

‎Tests/AWSLambdaEventsTests/CloudwatchTests.swift

+104
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,110 @@ class CloudwatchTests: XCTestCase {
101101
XCTAssertEqual(event.detail.instanceId, "0")
102102
XCTAssertEqual(event.detail.action, .terminate)
103103
}
104+
105+
func testS3ObjectCreatedEventFromJSON() {
106+
let eventBody = CloudwatchTests.eventBody(
107+
type: CloudwatchDetails.S3.ObjectCreatedNotification.name,
108+
details: "{ \"version\": \"0\", \"bucket\": { \"name\": \"amzn-s3-demo-bucket1\" }, \"object\": { \"key\": \"example-key\", \"size\":5, \"etag\": \"b1946ac92492d2347c6235b4d2611184\", \"version-id\": \"IYV3p45BT0ac8hjHg1houSdS1a.Mro8e\", \"sequencer\": \"617f08299329d189\" }, \"request-id\": \"N4N7GDK58NMKJ12R\", \"requester\": \"123456789012\", \"source-ip-address\": \"1.2.3.4\", \"reason\": \"PutObject\" }"
109+
110+
)
111+
let data = eventBody.data(using: .utf8)!
112+
var maybeEvent: CloudWatchS3ObjectCreatedNotificationEvent?
113+
XCTAssertNoThrow(
114+
maybeEvent = try JSONDecoder().decode(CloudWatchS3ObjectCreatedNotificationEvent.self, from: data)
115+
)
116+
117+
guard let event = maybeEvent else {
118+
return XCTFail("Expected to have an event")
119+
}
120+
121+
XCTAssertEqual(event.id, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c")
122+
XCTAssertEqual(event.source, "aws.events")
123+
XCTAssertEqual(event.accountId, "123456789012")
124+
XCTAssertEqual(event.time, Date(timeIntervalSince1970: 0))
125+
XCTAssertEqual(event.region, .us_east_1)
126+
XCTAssertEqual(event.resources, ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"])
127+
XCTAssertEqual(event.detail.version, "0")
128+
XCTAssertEqual(event.detail.bucket.name, "amzn-s3-demo-bucket1")
129+
XCTAssertEqual(event.detail.object.key, "example-key")
130+
XCTAssertEqual(event.detail.object.size, 5)
131+
XCTAssertEqual(event.detail.object.etag, "b1946ac92492d2347c6235b4d2611184")
132+
XCTAssertEqual(event.detail.object.versionId, "IYV3p45BT0ac8hjHg1houSdS1a.Mro8e")
133+
XCTAssertEqual(event.detail.object.sequencer, "617f08299329d189")
134+
XCTAssertEqual(event.detail.requestId, "N4N7GDK58NMKJ12R")
135+
XCTAssertEqual(event.detail.requester, "123456789012")
136+
XCTAssertEqual(event.detail.sourceIpAddress, "1.2.3.4")
137+
XCTAssertEqual(event.detail.reason, .putObject)
138+
}
139+
140+
func testS3ObjectDeletedEventFromJSON() {
141+
let eventBody = CloudwatchTests.eventBody(
142+
type: CloudwatchDetails.S3.ObjectDeletedNotification.name,
143+
details: "{ \"version\": \"0\", \"bucket\": { \"name\": \"amzn-s3-demo-bucket1\" }, \"object\": { \"key\": \"example-key\", \"etag\": \"d41d8cd98f00b204e9800998ecf8427e\", \"version-id\": \"1QW9g1Z99LUNbvaaYVpW9xDlOLU.qxgF\", \"sequencer\": \"617f0837b476e463\" }, \"request-id\": \"0BH729840619AG5K\", \"requester\": \"123456789012\", \"source-ip-address\": \"1.2.3.4\", \"reason\": \"DeleteObject\", \"deletion-type\": \"Delete Marker Created\" }"
144+
145+
)
146+
let data = eventBody.data(using: .utf8)!
147+
var maybeEvent: CloudWatchS3ObjectDeletedNotificationEvent?
148+
XCTAssertNoThrow(
149+
maybeEvent = try JSONDecoder().decode(CloudWatchS3ObjectDeletedNotificationEvent.self, from: data)
150+
)
151+
152+
guard let event = maybeEvent else {
153+
return XCTFail("Expected to have an event")
154+
}
155+
156+
XCTAssertEqual(event.id, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c")
157+
XCTAssertEqual(event.source, "aws.events")
158+
XCTAssertEqual(event.accountId, "123456789012")
159+
XCTAssertEqual(event.time, Date(timeIntervalSince1970: 0))
160+
XCTAssertEqual(event.region, .us_east_1)
161+
XCTAssertEqual(event.resources, ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"])
162+
XCTAssertEqual(event.detail.version, "0")
163+
XCTAssertEqual(event.detail.bucket.name, "amzn-s3-demo-bucket1")
164+
XCTAssertEqual(event.detail.object.key, "example-key")
165+
XCTAssertEqual(event.detail.object.etag, "d41d8cd98f00b204e9800998ecf8427e")
166+
XCTAssertEqual(event.detail.object.versionId, "1QW9g1Z99LUNbvaaYVpW9xDlOLU.qxgF")
167+
XCTAssertEqual(event.detail.object.sequencer, "617f0837b476e463")
168+
XCTAssertEqual(event.detail.requestId, "0BH729840619AG5K")
169+
XCTAssertEqual(event.detail.requester, "123456789012")
170+
XCTAssertEqual(event.detail.sourceIpAddress, "1.2.3.4")
171+
XCTAssertEqual(event.detail.reason, .deleteObject)
172+
XCTAssertEqual(event.detail.deletionType, .deleteMarkerCreated)
173+
}
174+
175+
func testS3ObjectRestoreCompletedEventFromJSON() {
176+
let eventBody = CloudwatchTests.eventBody(
177+
type: CloudwatchDetails.S3.ObjectRestoreCompletedNotification.name,
178+
details: "{ \"version\": \"0\", \"bucket\": { \"name\": \"amzn-s3-demo-bucket1\" }, \"object\": { \"key\": \"example-key\", \"size\": 5, \"etag\": \"b1946ac92492d2347c6235b4d2611184\", \"version-id\": \"KKsjUC1.6gIjqtvhfg5AdMI0eCePIiT3\" }, \"request-id\": \"189F19CB7FB1B6A4\", \"requester\": \"s3.amazonaws.com\", \"restore-expiry-time\": \"2021-11-13T00:00:00Z\", \"source-storage-class\": \"GLACIER\" }"
179+
180+
)
181+
let data = eventBody.data(using: .utf8)!
182+
var maybeEvent: CloudWatchS3ObjectRestoreCompletedNotificationEvent?
183+
XCTAssertNoThrow(
184+
maybeEvent = try JSONDecoder().decode(CloudWatchS3ObjectRestoreCompletedNotificationEvent.self, from: data)
185+
)
186+
187+
guard let event = maybeEvent else {
188+
return XCTFail("Expected to have an event")
189+
}
190+
191+
XCTAssertEqual(event.id, "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c")
192+
XCTAssertEqual(event.source, "aws.events")
193+
XCTAssertEqual(event.accountId, "123456789012")
194+
XCTAssertEqual(event.time, Date(timeIntervalSince1970: 0))
195+
XCTAssertEqual(event.region, .us_east_1)
196+
XCTAssertEqual(event.resources, ["arn:aws:events:us-east-1:123456789012:rule/ExampleRule"])
197+
XCTAssertEqual(event.detail.version, "0")
198+
XCTAssertEqual(event.detail.bucket.name, "amzn-s3-demo-bucket1")
199+
XCTAssertEqual(event.detail.object.key, "example-key")
200+
XCTAssertEqual(event.detail.object.size, 5)
201+
XCTAssertEqual(event.detail.object.etag, "b1946ac92492d2347c6235b4d2611184")
202+
XCTAssertEqual(event.detail.object.versionId, "KKsjUC1.6gIjqtvhfg5AdMI0eCePIiT3")
203+
XCTAssertEqual(event.detail.requestId, "189F19CB7FB1B6A4")
204+
XCTAssertEqual(event.detail.requester, "s3.amazonaws.com")
205+
XCTAssertEqual(event.detail.restoreExpiryTime.description, "2021-11-13 00:00:00 +0000")
206+
XCTAssertEqual(event.detail.sourceStorageClass, .glacier)
207+
}
104208

105209
func testCustomEventFromJSON() {
106210
struct Custom: CloudwatchDetail {

0 commit comments

Comments
 (0)
Please sign in to comment.