Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions events/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ 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"`
GlacierEventData *S3GlacierEventData `json:"glacierEventData,omitempty"`
RestoreEventData *S3RestoreEventData `json:"restoreEventData,omitempty"`
ReplicationEventData *S3ReplicationEventData `json:"replicationEventData,omitempty"`
IntelligentTieringEventData *S3IntelligentTieringEventData `json:"intelligentTieringEventData,omitempty"`
LifecycleEventData *S3LifecycleEventData `json:"lifecycleEventData,omitempty"`
}

type S3UserIdentity struct {
Expand Down Expand Up @@ -70,6 +75,35 @@ 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"`
S3Operation string `json:"s3Operation"`
RequestTime time.Time `json:"requestTime"`
FailureReason string `json:"failureReason"`
}

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"`
Expand Down
145 changes: 145 additions & 0 deletions events/s3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,148 @@ func TestS3TestEventMarshaling(t *testing.T) {
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 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 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 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")

// 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))
}
46 changes: 46 additions & 0 deletions events/testdata/s3-glacier-event.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
]
}
43 changes: 43 additions & 0 deletions events/testdata/s3-intelligenttier-event.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
45 changes: 45 additions & 0 deletions events/testdata/s3-lifecycle-event.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
]
}
47 changes: 47 additions & 0 deletions events/testdata/s3-replication-event.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
Loading
Loading