diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 1b1000281..425ffc73d 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -129,6 +129,7 @@ const ( ErrFilterNameSuffix ErrFilterValueInvalid ErrOverlappingConfigs + ErrUnsupportedNotification // S3 extended errors. ErrContentSHA256Mismatch @@ -552,6 +553,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{ Description: "Configurations overlap. Configurations on the same bucket cannot share a common event type.", HTTPStatusCode: http.StatusBadRequest, }, + ErrUnsupportedNotification: { + Code: "UnsupportedNotification", + Description: "Minio server does not support Topic or Cloud Function based notifications.", + HTTPStatusCode: http.StatusBadRequest, + }, ErrInvalidCopyPartRange: { Code: "InvalidArgument", Description: "The x-amz-copy-source-range value must be of the form bytes=first-last where first and last are the zero-based offsets of the first and last bytes to copy", diff --git a/cmd/bucket-notification-datatypes.go b/cmd/bucket-notification-datatypes.go index 8398da11f..083dafcad 100644 --- a/cmd/bucket-notification-datatypes.go +++ b/cmd/bucket-notification-datatypes.go @@ -67,6 +67,7 @@ type notificationConfig struct { XMLName xml.Name `xml:"NotificationConfiguration"` QueueConfigs []queueConfig `xml:"QueueConfiguration"` LambdaConfigs []lambdaConfig `xml:"CloudFunctionConfiguration"` + TopicConfigs []topicConfig `xml:"TopicConfiguration"` } // listenerConfig structure represents run-time notification diff --git a/cmd/bucket-notification-handlers_test.go b/cmd/bucket-notification-handlers_test.go index e07cf61ff..1af07fc73 100644 --- a/cmd/bucket-notification-handlers_test.go +++ b/cmd/bucket-notification-handlers_test.go @@ -247,6 +247,95 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType, bucketName } } +func TestPutBucketNotificationHandler(t *testing.T) { + ExecObjectLayerAPITest(t, testPutBucketNotificationHandler, []string{ + "PutBucketNotification", + }) +} + +func testPutBucketNotificationHandler(obj ObjectLayer, instanceType, + bucketName string, apiRouter http.Handler, credentials credential, + t *testing.T) { + + // declare sample configs + filterRules := []filterRule{ + { + Name: "prefix", + Value: "minio", + }, + { + Name: "suffix", + Value: "*.jpg", + }, + } + sampleSvcCfg := ServiceConfig{ + []string{"s3:ObjectRemoved:*", "s3:ObjectCreated:*"}, + filterStruct{ + keyFilter{filterRules}, + }, + "1", + } + sampleNotifCfg := notificationConfig{ + QueueConfigs: []queueConfig{ + { + ServiceConfig: sampleSvcCfg, + QueueARN: "testqARN", + }, + }, + } + + { + sampleNotifCfg.LambdaConfigs = []lambdaConfig{ + { + sampleSvcCfg, "testLARN", + }, + } + xmlBytes, err := xml.Marshal(sampleNotifCfg) + if err != nil { + t.Fatalf("%s: Unexpected err: %#v", instanceType, err) + } + rec := httptest.NewRecorder() + req, err := newTestSignedRequestV4("PUT", + getPutBucketNotificationURL("", bucketName), + int64(len(xmlBytes)), bytes.NewReader(xmlBytes), + credentials.AccessKey, credentials.SecretKey) + if err != nil { + t.Fatalf("%s: Failed to create HTTP testRequest for PutBucketNotification: %v", + instanceType, err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusBadRequest { + t.Fatalf("Unexpected http response %d", rec.Code) + } + } + + { + sampleNotifCfg.LambdaConfigs = nil + sampleNotifCfg.TopicConfigs = []topicConfig{ + { + sampleSvcCfg, "testTARN", + }, + } + xmlBytes, err := xml.Marshal(sampleNotifCfg) + if err != nil { + t.Fatalf("%s: Unexpected err: %#v", instanceType, err) + } + rec := httptest.NewRecorder() + req, err := newTestSignedRequestV4("PUT", + getPutBucketNotificationURL("", bucketName), + int64(len(xmlBytes)), bytes.NewReader(xmlBytes), + credentials.AccessKey, credentials.SecretKey) + if err != nil { + t.Fatalf("%s: Failed to create HTTP testRequest for PutBucketNotification: %v", + instanceType, err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusBadRequest { + t.Fatalf("Unexpected http response %d", rec.Code) + } + } +} + func TestListenBucketNotificationNilHandler(t *testing.T) { ExecObjectLayerAPITest(t, testListenBucketNotificationNilHandler, []string{ "ListenBucketNotification", @@ -281,26 +370,28 @@ func testListenBucketNotificationNilHandler(obj ObjectLayer, instanceType, bucke } } -func testRemoveNotificationConfig(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, - credentials credential, t *testing.T) { +func testRemoveNotificationConfig(obj ObjectLayer, instanceType, + bucketName string, apiRouter http.Handler, credentials credential, + t *testing.T) { + invalidBucket := "Invalid\\Bucket" // get random bucket name. randBucket := bucketName - sampleNotificationBytes := []byte("" + - "s3:ObjectCreated:*s3:ObjectRemoved:*" + - "arn:minio:sns:us-east-1:1474332374:listen" + - "") - - // Set sample bucket notification on randBucket. - testRec := httptest.NewRecorder() - testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket), - int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes), - credentials.AccessKey, credentials.SecretKey) - if tErr != nil { - t.Fatalf("%s: Failed to create HTTP testRequest for PutBucketNotification: %v", instanceType, tErr) + nCfg := notificationConfig{ + QueueConfigs: []queueConfig{ + { + ServiceConfig: ServiceConfig{ + Events: []string{"s3:ObjectRemoved:*", + "s3:ObjectCreated:*"}, + }, + QueueARN: "testqARN", + }, + }, + } + if err := persistNotificationConfig(randBucket, &nCfg, obj); err != nil { + t.Fatalf("Unexpected error: %#v", err) } - apiRouter.ServeHTTP(testRec, testReq) testCases := []struct { bucketName string diff --git a/cmd/bucket-notification-utils.go b/cmd/bucket-notification-utils.go index 211addcd8..762792031 100644 --- a/cmd/bucket-notification-utils.go +++ b/cmd/bucket-notification-utils.go @@ -235,6 +235,12 @@ func checkDuplicateQueueConfigs(configs []queueConfig) APIErrorCode { // if one of the config is malformed or has invalid data it is rejected. // Configuration is never applied partially. func validateNotificationConfig(nConfig notificationConfig) APIErrorCode { + // Minio server does not support lambda/topic configurations + // currently. Such configuration is rejected. + if len(nConfig.LambdaConfigs) > 0 || len(nConfig.TopicConfigs) > 0 { + return ErrUnsupportedNotification + } + // Validate all queue configs. if s3Error := validateQueueConfigs(nConfig.QueueConfigs); s3Error != ErrNone { return s3Error