diff --git a/backend.go b/backend.go index 2168a31..197cd2d 100644 --- a/backend.go +++ b/backend.go @@ -24,6 +24,19 @@ type Object struct { IsDeleteMarker bool } +type ObjectDeleteResult struct { + // Specifies whether the versioned object that was permanently deleted was + // (true) or was not (false) a delete marker. In a simple DELETE, this + // header indicates whether (true) or not (false) a delete marker was + // created. + IsDeleteMarker bool + + // Returns the version ID of the delete marker created as a result of the + // DELETE operation. If you delete a specific object version, the value + // returned by this header is the version ID of the object version deleted. + VersionID VersionID +} + // Backend provides a set of operations to be implemented in order to support // gofakes3. // @@ -84,6 +97,9 @@ type Backend interface { // DeleteObject deletes an object from the bucket. // + // If the backend is a VersionedBackend and versioning is enabled, this + // should introduce a delete marker rather than actually delete the object. + // // DeleteObject must return a gofakes3.ErrNoSuchBucket error if the bucket // does not exist. See gofakes3.BucketNotFound() for a convenient way to create one. // @@ -94,7 +110,7 @@ type Backend interface { // delete marker, which becomes the latest version of the object. If there // isn't a null version, Amazon S3 does not remove any objects. // - DeleteObject(bucketName, objectName string) error + DeleteObject(bucketName, objectName string) (ObjectDeleteResult, error) // HeadObject fetches the Object from the backend, but the Contents will be // a no-op ReadCloser. @@ -113,7 +129,7 @@ type Backend interface { DeleteMulti(bucketName string, objects ...string) (DeleteResult, error) } -// VersionedBucket may be optionally implemented by a Backend in order to support +// VersionedBackend may be optionally implemented by a Backend in order to support // operations on S3 object versions. // // If you don't implement VersionedBackend, requests to GoFakeS3 that attempt to @@ -133,5 +149,8 @@ type VersionedBackend interface { versionID VersionID, rangeRequest *ObjectRangeRequest) (*Object, error) + // DeleteObjectVersion permanently deletes a specific object version. + DeleteObjectVersion(bucketName, objectName string, versionID VersionID) (ObjectDeleteResult, error) + ListBucketVersions(bucketName string, prefix Prefix) (*ListBucketVersionsResult, error) } diff --git a/backend/s3afero/backend_test.go b/backend/s3afero/backend_test.go index 5e811a0..be8a3bf 100644 --- a/backend/s3afero/backend_test.go +++ b/backend/s3afero/backend_test.go @@ -223,7 +223,7 @@ func TestPutDelete(t *testing.T) { t.Fatal(err) } - if err := backend.DeleteObject("test", "foo"); err != nil { + if _, err := backend.DeleteObject("test", "foo"); err != nil { t.Fatal(err) } diff --git a/backend/s3afero/multi.go b/backend/s3afero/multi.go index 458308b..6c3667e 100644 --- a/backend/s3afero/multi.go +++ b/backend/s3afero/multi.go @@ -425,19 +425,19 @@ func (db *MultiBucketBackend) PutObject(bucketName, objectName string, meta map[ return nil } -func (db *MultiBucketBackend) DeleteObject(bucketName, objectName string) error { +func (db *MultiBucketBackend) DeleteObject(bucketName, objectName string) (result gofakes3.ObjectDeleteResult, rerr error) { db.lock.Lock() defer db.lock.Unlock() // Another slighly racy check: exists, err := afero.Exists(db.bucketFs, bucketName) if err != nil { - return err + return result, err } else if !exists { - return gofakes3.BucketNotFound(bucketName) + return result, gofakes3.BucketNotFound(bucketName) } - return db.deleteObjectLocked(bucketName, objectName) + return result, db.deleteObjectLocked(bucketName, objectName) } func (db *MultiBucketBackend) deleteObjectLocked(bucketName, objectName string) error { diff --git a/backend/s3afero/single.go b/backend/s3afero/single.go index 79f3d2d..addb57a 100644 --- a/backend/s3afero/single.go +++ b/backend/s3afero/single.go @@ -358,15 +358,15 @@ func (db *SingleBucketBackend) DeleteMulti(bucketName string, objects ...string) return result, nil } -func (db *SingleBucketBackend) DeleteObject(bucketName, objectName string) error { +func (db *SingleBucketBackend) DeleteObject(bucketName, objectName string) (result gofakes3.ObjectDeleteResult, rerr error) { if bucketName != db.name { - return gofakes3.BucketNotFound(bucketName) + return result, gofakes3.BucketNotFound(bucketName) } db.lock.Lock() defer db.lock.Unlock() - return db.deleteObjectLocked(bucketName, objectName) + return result, db.deleteObjectLocked(bucketName, objectName) } func (db *SingleBucketBackend) deleteObjectLocked(bucketName, objectName string) error { diff --git a/backend/s3bolt/backend.go b/backend/s3bolt/backend.go index 820e768..7f888ae 100644 --- a/backend/s3bolt/backend.go +++ b/backend/s3bolt/backend.go @@ -309,8 +309,8 @@ func (db *Backend) PutObject(bucketName, objectName string, meta map[string]stri }) } -func (db *Backend) DeleteObject(bucketName, objectName string) error { - return db.bolt.Update(func(tx *bolt.Tx) error { +func (db *Backend) DeleteObject(bucketName, objectName string) (result gofakes3.ObjectDeleteResult, rerr error) { + return result, db.bolt.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(bucketName)) if b == nil { return gofakes3.BucketNotFound(bucketName) diff --git a/backend/s3mem/backend.go b/backend/s3mem/backend.go index 167e22b..6bacfe9 100644 --- a/backend/s3mem/backend.go +++ b/backend/s3mem/backend.go @@ -207,19 +207,19 @@ func (db *Backend) PutObject(bucketName, objectName string, meta map[string]stri return nil } -func (db *Backend) DeleteObject(bucketName, objectName string) error { +func (db *Backend) DeleteObject(bucketName, objectName string) (result gofakes3.ObjectDeleteResult, rerr error) { db.lock.Lock() defer db.lock.Unlock() bucket := db.buckets[bucketName] if bucket == nil { - return gofakes3.BucketNotFound(bucketName) + return result, gofakes3.BucketNotFound(bucketName) } // S3 does not report an error when attemping to delete a key that does not exist: delete(bucket.data, objectName) - return nil + return result, nil } func (db *Backend) DeleteMulti(bucketName string, objects ...string) (result gofakes3.DeleteResult, err error) { diff --git a/gofakes3.go b/gofakes3.go index 5257763..2c365e8 100644 --- a/gofakes3.go +++ b/gofakes3.go @@ -454,14 +454,45 @@ func (g *GoFakeS3) createObject(bucket, object string, w http.ResponseWriter, r return nil } -// deleteObject deletes a S3 object from the bucket. func (g *GoFakeS3) deleteObject(bucket, object string, w http.ResponseWriter, r *http.Request) error { g.log.Print(LogInfo, "DELETE:", bucket, object) - if err := g.storage.DeleteObject(bucket, object); err != nil { + result, err := g.storage.DeleteObject(bucket, object) + if err != nil { return err } - w.Header().Set("x-amz-delete-marker", "false") - w.Write([]byte{}) + + if result.IsDeleteMarker { + w.Header().Set("x-amz-delete-marker", "true") + } else { + w.Header().Set("x-amz-delete-marker", "false") + } + + if result.VersionID != "" { + w.Header().Set("x-amz-version-id", string(result.VersionID)) + } + + return nil +} + +func (g *GoFakeS3) deleteObjectVersion(bucket, object string, version VersionID, w http.ResponseWriter, r *http.Request) error { + if g.versioned == nil { + return ErrNotImplemented + } + result, err := g.versioned.DeleteObjectVersion(bucket, object, version) + if err != nil { + return err + } + + if result.IsDeleteMarker { + w.Header().Set("x-amz-delete-marker", "true") + } else { + w.Header().Set("x-amz-delete-marker", "false") + } + + if result.VersionID != "" { + w.Header().Set("x-amz-version-id", string(result.VersionID)) + } + return nil } diff --git a/routing.go b/routing.go index 7a2d263..bed8839 100644 --- a/routing.go +++ b/routing.go @@ -159,6 +159,8 @@ func (g *GoFakeS3) routeVersion(bucket, object string, versionID VersionID, w ht return g.getObject(bucket, object, versionID, w, r) case "HEAD": return g.headObject(bucket, object, versionID, w, r) + case "DELETE": + return g.deleteObjectVersion(bucket, object, versionID, w, r) default: return ErrMethodNotAllowed }