From 1191ffbc89482ee3eeef1e685ec700a8617a50c6 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:07:00 +0900 Subject: [PATCH 1/5] feat: add replication event data to S3EventRecord and corresponding test cases --- events/s3.go | 27 ++++++++----- events/s3_test.go | 25 ++++++++++++ events/testdata/s3-replication-event.json | 47 +++++++++++++++++++++++ 3 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 events/testdata/s3-replication-event.json diff --git a/events/s3.go b/events/s3.go index 15677997..dcf00967 100644 --- a/events/s3.go +++ b/events/s3.go @@ -15,15 +15,16 @@ type S3Event struct { // S3EventRecord which wrap record data type S3EventRecord struct { - EventVersion string `json:"eventVersion"` - EventSource string `json:"eventSource"` - AWSRegion string `json:"awsRegion"` - EventTime time.Time `json:"eventTime"` - EventName string `json:"eventName"` - PrincipalID S3UserIdentity `json:"userIdentity"` - RequestParameters S3RequestParameters `json:"requestParameters"` - ResponseElements map[string]string `json:"responseElements"` - S3 S3Entity `json:"s3"` + EventVersion string `json:"eventVersion"` + EventSource string `json:"eventSource"` + AWSRegion string `json:"awsRegion"` + EventTime time.Time `json:"eventTime"` + EventName string `json:"eventName"` + PrincipalID S3UserIdentity `json:"userIdentity"` + RequestParameters S3RequestParameters `json:"requestParameters"` + ResponseElements map[string]string `json:"responseElements"` + S3 S3Entity `json:"s3"` + ReplicationEventData *S3ReplicationEventData `json:"replicationEventData,omitempty"` } type S3UserIdentity struct { @@ -70,6 +71,14 @@ func (o *S3Object) UnmarshalJSON(data []byte) error { return nil } +type S3ReplicationEventData struct { + ReplicationRuleID string `json:"replicationRuleId"` + DestinationBucket string `json:"destinationBucket"` + S3Operation string `json:"s3Operation"` + RequestTime time.Time `json:"requestTime"` + FailureReason string `json:"failureReason"` +} + type S3TestEvent struct { Service string `json:"Service"` Bucket string `json:"Bucket"` diff --git a/events/s3_test.go b/events/s3_test.go index 968bfe6b..e9856a1a 100644 --- a/events/s3_test.go +++ b/events/s3_test.go @@ -56,3 +56,28 @@ func TestS3TestEventMarshaling(t *testing.T) { func TestS3MarshalingMalformedJSON(t *testing.T) { test.TestMalformedJson(t, S3Event{}) } + +func TestS3ReplicationEventMarshaling(t *testing.T) { + // 1. read JSON from file + inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-replication-event.json") + + // 2. de-serialize into Go object + var inputEvent S3Event + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + // 3. verify replicationEventData is correctly parsed + if inputEvent.Records[0].ReplicationEventData == nil { + t.Error("replicationEventData should not be nil for replication events") + } + + // 4. serialize to JSON + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + // 5. check result + assert.JSONEq(t, string(inputJSON), string(outputJSON)) +} diff --git a/events/testdata/s3-replication-event.json b/events/testdata/s3-replication-event.json new file mode 100644 index 00000000..300d7474 --- /dev/null +++ b/events/testdata/s3-replication-event.json @@ -0,0 +1,47 @@ +{ + "Records": [ + { + "eventVersion": "2.2", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "2024-09-05T21:04:32.527Z", + "eventName": "s3:Replication:OperationFailedReplication", + "userIdentity": { + "principalId": "s3.amazonaws.com" + }, + "requestParameters": { + "sourceIPAddress": "s3.amazonaws.com" + }, + "responseElements": { + "x-amz-request-id": "123bf045-2b4b-4ca8-a211-c34a63c59426", + "x-amz-id-2": "12VAWNDIHnwJsRhTccqQTeAPoXQmRt22KkewMV8G3XZihAuf9CLDdmkApgZzudaIe2KlLfDqGS0=" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "ReplicationEventName", + "bucket": { + "name": "amzn-s3-demo-bucket1", + "ownerIdentity": { + "principalId": "111122223333" + }, + "arn": "arn:aws:s3:::amzn-s3-demo-bucket1" + }, + "object": { + "key": "replication%2Dtest.png", + "urlDecodedKey": "replication-test.png", + "size": 520080, + "versionId": "abcdeH0Xp66ep__QDjR76LK7Gc9X4wKO", + "eTag": "e12345ca7e88a38428305d3ff7fcb99f", + "sequencer": "0066DA1CBF104C0D51" + } + }, + "replicationEventData": { + "replicationRuleId": "notification-test-replication-rule", + "destinationBucket": "arn:aws:s3:::amzn-s3-demo-bucket2", + "s3Operation": "OBJECT_PUT", + "requestTime": "2024-09-05T21:03:59.168Z", + "failureReason": "AssumeRoleNotPermitted" + } + } + ] +} From abb7ee75edc3bb96307b90c8d7f1928153718953 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sun, 21 Dec 2025 16:33:55 +0900 Subject: [PATCH 2/5] feat: add Glacier event data structure and corresponding test for S3 events --- events/s3.go | 10 ++++++ events/s3_test.go | 30 +++++++++++++++++ events/testdata/s3-glacier-event.json | 46 +++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 events/testdata/s3-glacier-event.json diff --git a/events/s3.go b/events/s3.go index dcf00967..1d65e5f4 100644 --- a/events/s3.go +++ b/events/s3.go @@ -24,6 +24,7 @@ type S3EventRecord struct { RequestParameters S3RequestParameters `json:"requestParameters"` ResponseElements map[string]string `json:"responseElements"` S3 S3Entity `json:"s3"` + GlacierEventData *S3GlacierEventData `json:"glacierEventData,omitempty"` ReplicationEventData *S3ReplicationEventData `json:"replicationEventData,omitempty"` } @@ -71,6 +72,15 @@ func (o *S3Object) UnmarshalJSON(data []byte) error { return nil } +type S3GlacierEventData struct { + RestoreEventData *S3RestoreEventData `json:"restoreEventData"` +} + +type S3RestoreEventData struct { + LifecycleRestorationExpiryTime time.Time `json:"lifecycleRestorationExpiryTime"` + LifecycleRestoreStorageClass string `json:"lifecycleRestoreStorageClass"` +} + type S3ReplicationEventData struct { ReplicationRuleID string `json:"replicationRuleId"` DestinationBucket string `json:"destinationBucket"` diff --git a/events/s3_test.go b/events/s3_test.go index e9856a1a..59de5d20 100644 --- a/events/s3_test.go +++ b/events/s3_test.go @@ -57,6 +57,36 @@ func TestS3MarshalingMalformedJSON(t *testing.T) { test.TestMalformedJson(t, S3Event{}) } +func TestS3GlacierEventMarshaling(t *testing.T) { + // 1. read JSON from file + inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-glacier-event.json") + + // 2. de-serialize into Go object + var inputEvent S3Event + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + // 3. verify glacierEventData is correctly parsed + if inputEvent.Records[0].GlacierEventData == nil { + t.Error("glacierEventData should not be nil for glacier restore events") + } + + // 4. verify restoreEventData is correctly parsed + if inputEvent.Records[0].GlacierEventData.RestoreEventData == nil { + t.Error("restoreEventData should not be nil") + } + + // 5. serialize to JSON + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + // 6. check result + assert.JSONEq(t, string(inputJSON), string(outputJSON)) +} + func TestS3ReplicationEventMarshaling(t *testing.T) { // 1. read JSON from file inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-replication-event.json") diff --git a/events/testdata/s3-glacier-event.json b/events/testdata/s3-glacier-event.json new file mode 100644 index 00000000..56eba8bf --- /dev/null +++ b/events/testdata/s3-glacier-event.json @@ -0,0 +1,46 @@ +{ + "Records": [ + { + "eventVersion": "2.1", + "eventSource": "aws:s3", + "awsRegion": "us-west-2", + "eventTime": "2023-05-10T15:00:00.123Z", + "eventName": "s3:ObjectRestore:Completed", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "EXAMPLE123456789", + "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "example-bucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::example-bucket" + }, + "object": { + "key": "glacier%2Dtest.txt", + "urlDecodedKey": "glacier-test.txt", + "size": 1024, + "versionId": "abcdeH0Xp66ep__QDjR76LK7Gc9X4wKO", + "eTag": "0123456789abcdef0123456789abcdef", + "sequencer": "0A1B2C3D4E5F678901" + } + }, + "glacierEventData": { + "restoreEventData": { + "lifecycleRestorationExpiryTime": "2023-05-17T15:00:00.456Z", + "lifecycleRestoreStorageClass": "STANDARD" + } + } + } + ] +} From 1f8f7e8ba325aabe0bd53104e06a2f7b6e4469ce Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:33:16 +0900 Subject: [PATCH 3/5] feat: add restore event data structure and corresponding test for S3 events --- events/s3.go | 1 + events/s3_test.go | 25 +++++++++++++++ events/testdata/s3-restore-event.json | 44 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 events/testdata/s3-restore-event.json diff --git a/events/s3.go b/events/s3.go index 1d65e5f4..e9f4aa7c 100644 --- a/events/s3.go +++ b/events/s3.go @@ -25,6 +25,7 @@ type S3EventRecord struct { ResponseElements map[string]string `json:"responseElements"` S3 S3Entity `json:"s3"` GlacierEventData *S3GlacierEventData `json:"glacierEventData,omitempty"` + RestoreEventData *S3RestoreEventData `json:"restoreEventData,omitempty"` ReplicationEventData *S3ReplicationEventData `json:"replicationEventData,omitempty"` } diff --git a/events/s3_test.go b/events/s3_test.go index 59de5d20..d2d5964f 100644 --- a/events/s3_test.go +++ b/events/s3_test.go @@ -87,6 +87,31 @@ func TestS3GlacierEventMarshaling(t *testing.T) { assert.JSONEq(t, string(inputJSON), string(outputJSON)) } +func TestS3RestoreEventMarshaling(t *testing.T) { + // 1. read JSON from file + inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-restore-event.json") + + // 2. de-serialize into Go object + var inputEvent S3Event + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + // 3. verify restoreEventData is correctly parsed + if inputEvent.Records[0].RestoreEventData == nil { + t.Error("restoreEventData should not be nil") + } + + // 4. serialize to JSON + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + // 5. check result + assert.JSONEq(t, string(inputJSON), string(outputJSON)) +} + func TestS3ReplicationEventMarshaling(t *testing.T) { // 1. read JSON from file inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-replication-event.json") diff --git a/events/testdata/s3-restore-event.json b/events/testdata/s3-restore-event.json new file mode 100644 index 00000000..6b1ac24e --- /dev/null +++ b/events/testdata/s3-restore-event.json @@ -0,0 +1,44 @@ +{ + "Records": [ + { + "eventVersion": "2.1", + "eventSource": "aws:s3", + "awsRegion": "us-west-2", + "eventTime": "2023-05-10T15:00:00.123Z", + "eventName": "s3:ObjectRestore:Completed", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "EXAMPLE123456789", + "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "example-bucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::example-bucket" + }, + "object": { + "key": "glacier%2Dtest.txt", + "urlDecodedKey": "glacier-test.txt", + "size": 1024, + "versionId": "abcdeH0Xp66ep__QDjR76LK7Gc9X4wKO", + "eTag": "0123456789abcdef0123456789abcdef", + "sequencer": "0A1B2C3D4E5F678901" + } + }, + "restoreEventData": { + "lifecycleRestorationExpiryTime": "2023-05-17T15:00:00.456Z", + "lifecycleRestoreStorageClass": "STANDARD" + } + } + ] +} From 5516bce0c30fb4780dc1741aea0b711e18c8d35f Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:47:57 +0900 Subject: [PATCH 4/5] feat: add intelligent tiering event data structure and corresponding test for S3 events --- events/s3.go | 29 +++++++------ events/s3_test.go | 30 +++++++++++++ events/testdata/s3-intelligenttier-event.json | 43 +++++++++++++++++++ 3 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 events/testdata/s3-intelligenttier-event.json diff --git a/events/s3.go b/events/s3.go index e9f4aa7c..b0ec4002 100644 --- a/events/s3.go +++ b/events/s3.go @@ -15,18 +15,19 @@ type S3Event struct { // S3EventRecord which wrap record data type S3EventRecord struct { - EventVersion string `json:"eventVersion"` - EventSource string `json:"eventSource"` - AWSRegion string `json:"awsRegion"` - EventTime time.Time `json:"eventTime"` - EventName string `json:"eventName"` - PrincipalID S3UserIdentity `json:"userIdentity"` - RequestParameters S3RequestParameters `json:"requestParameters"` - ResponseElements map[string]string `json:"responseElements"` - S3 S3Entity `json:"s3"` - GlacierEventData *S3GlacierEventData `json:"glacierEventData,omitempty"` - RestoreEventData *S3RestoreEventData `json:"restoreEventData,omitempty"` - ReplicationEventData *S3ReplicationEventData `json:"replicationEventData,omitempty"` + EventVersion string `json:"eventVersion"` + EventSource string `json:"eventSource"` + AWSRegion string `json:"awsRegion"` + EventTime time.Time `json:"eventTime"` + EventName string `json:"eventName"` + PrincipalID S3UserIdentity `json:"userIdentity"` + RequestParameters S3RequestParameters `json:"requestParameters"` + ResponseElements map[string]string `json:"responseElements"` + S3 S3Entity `json:"s3"` + GlacierEventData *S3GlacierEventData `json:"glacierEventData,omitempty"` + RestoreEventData *S3RestoreEventData `json:"restoreEventData,omitempty"` + ReplicationEventData *S3ReplicationEventData `json:"replicationEventData,omitempty"` + IntelligentTieringEventData *S3IntelligentTieringEventData `json:"intelligentTieringEventData,omitempty"` } type S3UserIdentity struct { @@ -90,6 +91,10 @@ type S3ReplicationEventData struct { FailureReason string `json:"failureReason"` } +type S3IntelligentTieringEventData struct { + DestinationAccessTier string `json:"destinationAccessTier"` +} + type S3TestEvent struct { Service string `json:"Service"` Bucket string `json:"Bucket"` diff --git a/events/s3_test.go b/events/s3_test.go index d2d5964f..95ae78b5 100644 --- a/events/s3_test.go +++ b/events/s3_test.go @@ -112,6 +112,36 @@ func TestS3RestoreEventMarshaling(t *testing.T) { assert.JSONEq(t, string(inputJSON), string(outputJSON)) } +func TestS3IntelligentTieringEventMarshaling(t *testing.T) { + // 1. read JSON from file + inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-intelligenttier-event.json") + + // 2. de-serialize into Go object + var inputEvent S3Event + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + // 3. verify intelligentTieringEventData is correctly parsed + if inputEvent.Records[0].IntelligentTieringEventData == nil { + t.Error("intelligentTieringEventData should not be nil for intelligent tiering events") + } + + // 4. verify destinationAccessTier is correctly parsed + if inputEvent.Records[0].IntelligentTieringEventData.DestinationAccessTier == "" { + t.Error("destinationAccessTier should not be empty") + } + + // 5. serialize to JSON + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + // 6. check result + assert.JSONEq(t, string(inputJSON), string(outputJSON)) +} + func TestS3ReplicationEventMarshaling(t *testing.T) { // 1. read JSON from file inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-replication-event.json") diff --git a/events/testdata/s3-intelligenttier-event.json b/events/testdata/s3-intelligenttier-event.json new file mode 100644 index 00000000..1a403868 --- /dev/null +++ b/events/testdata/s3-intelligenttier-event.json @@ -0,0 +1,43 @@ +{ + "Records": [ + { + "eventVersion": "2.3", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "2023-11-15T10:30:00.789Z", + "eventName": "s3:IntelligentTiering:TransitionAccess", + "userIdentity": { + "principalId": "s3.amazonaws.com" + }, + "requestParameters": { + "sourceIPAddress": "s3.amazonaws.com" + }, + "responseElements": { + "x-amz-request-id": "EXAMPLE123456789", + "x-amz-id-2": "EXAMPLEabcdefghijklmnopqrstuvwxyz" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "IntelligentTieringConfig", + "bucket": { + "name": "example-bucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::example-bucket" + }, + "object": { + "key": "intelligent%2Dtier%2Dtest.dat", + "urlDecodedKey": "intelligent-tier-test.dat", + "size": 5242880, + "versionId": "versionExample123", + "eTag": "d41d8cd98f00b204e9800998ecf8427e", + "sequencer": "0066ABC1234567890" + } + }, + "intelligentTieringEventData": { + "destinationAccessTier": "ARCHIVE_ACCESS" + } + } + ] +} From be52e48b133401d28c9f19b746989c4f1458b816 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:57:44 +0900 Subject: [PATCH 5/5] feat: add lifecycle event data structure and corresponding test for S3 events --- events/s3.go | 9 +++++ events/s3_test.go | 35 +++++++++++++++++++ events/testdata/s3-lifecycle-event.json | 45 +++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 events/testdata/s3-lifecycle-event.json diff --git a/events/s3.go b/events/s3.go index b0ec4002..a0f5d988 100644 --- a/events/s3.go +++ b/events/s3.go @@ -28,6 +28,7 @@ type S3EventRecord struct { RestoreEventData *S3RestoreEventData `json:"restoreEventData,omitempty"` ReplicationEventData *S3ReplicationEventData `json:"replicationEventData,omitempty"` IntelligentTieringEventData *S3IntelligentTieringEventData `json:"intelligentTieringEventData,omitempty"` + LifecycleEventData *S3LifecycleEventData `json:"lifecycleEventData,omitempty"` } type S3UserIdentity struct { @@ -95,6 +96,14 @@ type S3IntelligentTieringEventData struct { DestinationAccessTier string `json:"destinationAccessTier"` } +type S3LifecycleEventData struct { + TransitionEventData *S3TransitionEventData `json:"transitionEventData"` +} + +type S3TransitionEventData struct { + DestinationStorageClass string `json:"destinationStorageClass"` +} + type S3TestEvent struct { Service string `json:"Service"` Bucket string `json:"Bucket"` diff --git a/events/s3_test.go b/events/s3_test.go index 95ae78b5..8ec48fbf 100644 --- a/events/s3_test.go +++ b/events/s3_test.go @@ -142,6 +142,41 @@ func TestS3IntelligentTieringEventMarshaling(t *testing.T) { assert.JSONEq(t, string(inputJSON), string(outputJSON)) } +func TestS3LifecycleEventMarshaling(t *testing.T) { + // 1. read JSON from file + inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-lifecycle-event.json") + + // 2. de-serialize into Go object + var inputEvent S3Event + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + // 3. verify lifecycleEventData is correctly parsed + if inputEvent.Records[0].LifecycleEventData == nil { + t.Error("lifecycleEventData should not be nil for lifecycle events") + } + + // 4. verify transitionEventData is correctly parsed + if inputEvent.Records[0].LifecycleEventData.TransitionEventData == nil { + t.Error("transitionEventData should not be nil") + } + + // 5. verify destinationStorageClass is correctly parsed + if inputEvent.Records[0].LifecycleEventData.TransitionEventData.DestinationStorageClass == "" { + t.Error("destinationStorageClass should not be empty") + } + + // 6. serialize to JSON + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + // 7. check result + assert.JSONEq(t, string(inputJSON), string(outputJSON)) +} + func TestS3ReplicationEventMarshaling(t *testing.T) { // 1. read JSON from file inputJSON := test.ReadJSONFromFile(t, "./testdata/s3-replication-event.json") diff --git a/events/testdata/s3-lifecycle-event.json b/events/testdata/s3-lifecycle-event.json new file mode 100644 index 00000000..712c798c --- /dev/null +++ b/events/testdata/s3-lifecycle-event.json @@ -0,0 +1,45 @@ +{ + "Records": [ + { + "eventVersion": "2.3", + "eventSource": "aws:s3", + "awsRegion": "us-west-2", + "eventTime": "2023-08-20T14:25:00.321Z", + "eventName": "s3:LifecycleTransition", + "userIdentity": { + "principalId": "s3.amazonaws.com" + }, + "requestParameters": { + "sourceIPAddress": "s3.amazonaws.com" + }, + "responseElements": { + "x-amz-request-id": "LIFECYCLE123456789", + "x-amz-id-2": "LIFECYCLEabcdefghijklmnopqrstuvwxyz" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "LifecycleTransitionRule", + "bucket": { + "name": "example-bucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::example-bucket" + }, + "object": { + "key": "lifecycle%2Dtest.dat", + "urlDecodedKey": "lifecycle-test.dat", + "size": 10485760, + "versionId": "lifecycleVersion123", + "eTag": "abcd1234ef5678901234567890abcdef", + "sequencer": "0066LIFECYCLE890" + } + }, + "lifecycleEventData": { + "transitionEventData": { + "destinationStorageClass": "GLACIER" + } + } + } + ] +}