Repository: C2FO/vfs
Pull request #119 (2022-06-17 20:58:25)

Add support for deleting file versions to s3 and gs
### Changes Included:

- file.delete() and location.deleteFile() now support optional argument to specify delete options
- S3's implementation of file.delete() now removes all the versions of the file object if deleteOptions.DeleteAllVersions is passed in the arguments.
- GS's implementation of file.delete() now removes all the generations of the file object if deleteOptions.DeleteAllVersions is passed in the arguments.
- Other implementations of File interface, are noop for deleteOptions.DeleteAllVersions. (Azure implementation is pending)

### Design:

- In order to keep the delete interface non-passive, decided to change it to a variadic function that takes an interface as argument.
- If we wish to add additional options for delete, the new options will simply implement the options.DeleteOption interface and add any additional methods that are needed for that option.
- This approach also allows us to add support for options for other methods as well. For example, if we want to pass file permissions while creating a file, we will just create an interface like FilePermissionsOption and create implementations according to the need.
- Here is an example that shows the design https://github.com/C2FO/vfs/compare/judd-delete-options

codecov[bot]: # [Codecov](https://codecov.io/gh/C2FO/vfs/pull/119?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO) Report
> Merging [#119](https://codecov.io/gh/C2FO/vfs/pull/119?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO) (1664466) into [master](https://codecov.io/gh/C2FO/vfs/commit/c2562a48c6579f0c2a53d66a6cd47513fca5440e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO) (c2562a4) will **increase** coverage by `0.08%`.
> The diff coverage is `78.02%`.

```diff
@@            Coverage Diff             @@
##           master     #119      +/-   ##
==========================================
+ Coverage   68.71%   68.80%   +0.08%     
==========================================
  Files          34       34              
  Lines        3718     3783      +65     
==========================================
+ Hits         2555     2603      +48     
- Misses        916      928      +12     
- Partials      247      252       +5     
```


| [Impacted Files](https://codecov.io/gh/C2FO/vfs/pull/119?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO) | Coverage Δ | |
|---|---|---|
| [backend/gs/file.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9ncy9maWxlLmdv) | `36.22% <62.79%> (+3.56%)` | :arrow_up: |
| [backend/s3/file.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9zMy9maWxlLmdv) | `70.58% <87.87%> (+1.43%)` | :arrow_up: |
| [backend/azure/file.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9henVyZS9maWxlLmdv) | `49.26% <100.00%> (ø)` | |
| [backend/azure/location.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9henVyZS9sb2NhdGlvbi5nbw==) | `72.03% <100.00%> (ø)` | |
| [backend/gs/location.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9ncy9sb2NhdGlvbi5nbw==) | `87.31% <100.00%> (ø)` | |
| [backend/mem/file.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9tZW0vZmlsZS5nbw==) | `61.29% <100.00%> (ø)` | |
| [backend/mem/location.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9tZW0vbG9jYXRpb24uZ28=) | `84.28% <100.00%> (ø)` | |
| [backend/os/file.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9vcy9maWxlLmdv) | `69.53% <100.00%> (ø)` | |
| [backend/os/location.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9vcy9sb2NhdGlvbi5nbw==) | `61.16% <100.00%> (ø)` | |
| [backend/s3/location.go](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO#diff-YmFja2VuZC9zMy9sb2NhdGlvbi5nbw==) | `86.66% <100.00%> (ø)` | |
| ... and [3 more](https://codecov.io/gh/C2FO/vfs/pull/119/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/C2FO/vfs/pull/119?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/C2FO/vfs/pull/119?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO). Last update [c2562a4...1664466](https://codecov.io/gh/C2FO/vfs/pull/119?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=C2FO).

dhondgepooja: > You'll need to add entries in the changelog and don't forget to add/update any markdown as well.

Added changelog https://github.com/C2FO/vfs/pull/119/files#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4ed

Added documentation for options and delete packages: 
https://github.com/C2FO/vfs/pull/119/files#diff-2e046795c81fb2bcc2667705f88c6421fa988047f147ddb2333ec8da5f638b7b
https://github.com/C2FO/vfs/pull/119/files#diff-64fb09ea01e76d4736694d316893f1ddaeb64208ca1b82838c2b01112220667b

Updated GS and S3 docs:
https://github.com/C2FO/vfs/pull/119/files#diff-87c3714d7e579142350666afd456d922a7dd3e7d55e174799dfa2bced366df57
https://github.com/C2FO/vfs/pull/119/files#diff-a52680507e0d3f24ae27c6e7e315ee16156cc1d32b0a16a0dafca33499cf6f30
 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc31a44..6e7c52e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 ## [Unreleased]
 
+### Added
+- Add DeleteAllVersions option support for S3 to remove all versions of a file
+- Add DeleteAllVersions option support for GS to remove all versions of a file
+
 ## [6.3.0]
 ### Security
 - Update go.mod min version to Go 1.17
diff --git a/backend/azure/file.go b/backend/azure/file.go
index da7e53b..06b1986 100644
--- a/backend/azure/file.go
+++ b/backend/azure/file.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/backend"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -192,7 +193,7 @@ func (f *File) MoveToFile(file vfs.File) error {
 }
 
 // Delete deletes the file.
-func (f *File) Delete() error {
+func (f *File) Delete(opts ...options.DeleteOption) error {
 	if err := f.Close(); err != nil {
 		return err
 	}
diff --git a/backend/azure/location.go b/backend/azure/location.go
index 6848fa5..967a64d 100644
--- a/backend/azure/location.go
+++ b/backend/azure/location.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 
 	"github.com/c2fo/vfs/v6"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -183,13 +184,13 @@ func (l *Location) NewFile(relFilePath string) (vfs.File, error) {
 }
 
 // DeleteFile deletes the file at the given path, relative to the current location.
-func (l *Location) DeleteFile(relFilePath string) error {
+func (l *Location) DeleteFile(relFilePath string, opts ...options.DeleteOption) error {
 	file, err := l.NewFile(utils.RemoveLeadingSlash(relFilePath))
 	if err != nil {
 		return err
 	}
 
-	return file.Delete()
+	return file.Delete(opts...)
 }
 
 // URI returns a URI string for the azure location.
diff --git a/backend/gs/file.go b/backend/gs/file.go
index 0053d22..d3d6962 100644
--- a/backend/gs/file.go
+++ b/backend/gs/file.go
@@ -11,9 +11,12 @@ import (
 	"time"
 
 	"cloud.google.com/go/storage"
+	"google.golang.org/api/iterator"
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/backend"
+	"github.com/c2fo/vfs/v6/options"
+	"github.com/c2fo/vfs/v6/options/delete"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -206,17 +209,42 @@ func (f *File) MoveToFile(file vfs.File) error {
 }
 
 // Delete clears any local temp file, or write buffer from read/writes to the file, then makes
-// a DeleteObject call to GCS for the file. Returns any error returned by the API.
-func (f *File) Delete() error {
+// a DeleteObject call to GCS for the file. If DeleteAllVersions option is provided,
+// DeleteObject call is made to GCS for each version of the file. Returns any error returned by the API.
+func (f *File) Delete(opts ...options.DeleteOption) error {
 	f.writeBuffer = nil
 	if err := f.Close(); err != nil {
 		return err
 	}
-	handle, err := f.getObjectHandle()
-	if err != nil {
-		return err
+
+	var deleteAllVersions bool
+	for _, o := range opts {
+		switch o.(type) {
+		case delete.DeleteAllVersions:
+			deleteAllVersions = true
+		default:
+		}
+	}
+
+	if deleteAllVersions {
+		handles, err := f.getObjectGenerationHandles()
+		if err != nil {
+			return err
+		}
+		for _, handle := range handles {
+			err := handle.Delete(f.fileSystem.ctx)
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	} else {
+		handle, err := f.getObjectHandle()
+		if err != nil {
+			return err
+		}
+		return handle.Delete(f.fileSystem.ctx)
 	}
-	return handle.Delete(f.fileSystem.ctx)
 }
 
 // Touch creates a zero-length file on the vfs.File if no File exists.  Update File's last modified timestamp.
@@ -441,6 +469,30 @@ func (f *File) getObjectHandle() (ObjectHandleCopier, error) {
 	return &RetryObjectHandler{Retry: f.fileSystem.Retry(), handler: handler}, nil
 }
 
+// getObjectGenerationHandles returns Object generation structs for file
+func (f *File) getObjectGenerationHandles() ([]*storage.ObjectHandle, error) {
+	client, err := f.fileSystem.Client()
+	var handles []*storage.ObjectHandle
+	if err != nil {
+		return nil, err
+	}
+	it := client.Bucket(f.bucket).
+		Objects(f.fileSystem.ctx, &storage.Query{Versions: true, Prefix: utils.RemoveLeadingSlash(f.key)})
+
+	for {
+		attrs, err := it.Next()
+		if err == iterator.Done {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+		handle := client.Bucket(attrs.Bucket).Object(attrs.Name).Generation(attrs.Generation)
+		handles = append(handles, handle)
+	}
+	return handles, err
+}
+
 // getObjectAttrs returns the file's attributes
 func (f *File) getObjectAttrs() (*storage.ObjectAttrs, error) {
 	handle, err := f.getObjectHandle()
diff --git a/backend/gs/location.go b/backend/gs/location.go
index 2a17946..f4ad7d3 100644
--- a/backend/gs/location.go
+++ b/backend/gs/location.go
@@ -10,6 +10,7 @@ import (
 	"google.golang.org/api/iterator"
 
 	"github.com/c2fo/vfs/v6"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -176,13 +177,13 @@ func (l *Location) NewFile(filePath string) (vfs.File, error) {
 }
 
 // DeleteFile deletes the file at the given path, relative to the current location.
-func (l *Location) DeleteFile(fileName string) error {
+func (l *Location) DeleteFile(fileName string, opts ...options.DeleteOption) error {
 	file, err := l.NewFile(fileName)
 	if err != nil {
 		return err
 	}
 
-	return file.Delete()
+	return file.Delete(opts...)
 }
 
 // URI returns a URI string for the GCS location.
diff --git a/backend/mem/file.go b/backend/mem/file.go
index 36786be..9f921ec 100644
--- a/backend/mem/file.go
+++ b/backend/mem/file.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/backend"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -420,7 +421,7 @@ func (f *File) MoveToFile(file vfs.File) error {
 
 // Delete removes the file from the FileSystem. Sets it path in the fsMap to nil,
 // and also nils the file's members
-func (f *File) Delete() error {
+func (f *File) Delete(opts ...options.DeleteOption) error {
 	if f == nil {
 		return nilReference()
 	}
diff --git a/backend/mem/location.go b/backend/mem/location.go
index 23290aa..b524660 100644
--- a/backend/mem/location.go
+++ b/backend/mem/location.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 
 	"github.com/c2fo/vfs/v6"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -196,7 +197,7 @@ func (l *Location) NewFile(relFilePath string) (vfs.File, error) {
 }
 
 // DeleteFile locates the file given the fileName and calls delete on it
-func (l *Location) DeleteFile(relFilePath string) error {
+func (l *Location) DeleteFile(relFilePath string, opts ...options.DeleteOption) error {
 	l.fileSystem.Lock()
 	defer l.fileSystem.Unlock()
 	err := utils.ValidateRelativeFilePath(relFilePath)
diff --git a/backend/os/file.go b/backend/os/file.go
index a2cadb5..6082ec7 100644
--- a/backend/os/file.go
+++ b/backend/os/file.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/backend"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -30,7 +31,7 @@ type File struct {
 }
 
 // Delete unlinks the file returning any error or nil.
-func (f *File) Delete() error {
+func (f *File) Delete(opts ...options.DeleteOption) error {
 	err := os.Remove(f.Path())
 	if err == nil {
 		f.file = nil
diff --git a/backend/os/location.go b/backend/os/location.go
index 83b0a8b..3a34890 100644
--- a/backend/os/location.go
+++ b/backend/os/location.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"github.com/c2fo/vfs/v6"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -38,13 +39,13 @@ func (l *Location) NewFile(fileName string) (vfs.File, error) {
 
 // DeleteFile deletes the file of the given name at the location. This is meant to be a short cut for instantiating a
 // new file and calling delete on that with all the necessary error handling overhead.
-func (l *Location) DeleteFile(fileName string) error {
+func (l *Location) DeleteFile(fileName string, opts ...options.DeleteOption) error {
 	file, err := l.NewFile(fileName)
 	if err != nil {
 		return err
 	}
 
-	return file.Delete()
+	return file.Delete(opts...)
 }
 
 type fileTest func(fileName string) bool
diff --git a/backend/s3/file.go b/backend/s3/file.go
index ceeed11..61a7098 100644
--- a/backend/s3/file.go
+++ b/backend/s3/file.go
@@ -19,6 +19,8 @@ import (
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/backend"
 	"github.com/c2fo/vfs/v6/mocks"
+	"github.com/c2fo/vfs/v6/options"
+	"github.com/c2fo/vfs/v6/options/delete"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -181,8 +183,9 @@ func (f *File) CopyToLocation(location vfs.Location) (vfs.File, error) {
 // CRUD Operations
 
 // Delete clears any local temp file, or write buffer from read/writes to the file, then makes
-// a DeleteObject call to s3 for the file. Returns any error returned by the API.
-func (f *File) Delete() error {
+// a DeleteObject call to s3 for the file. If DeleteAllVersions option is provided,
+// DeleteObject call is made to s3 for each version of the file. Returns any error returned by the API.
+func (f *File) Delete(opts ...options.DeleteOption) error {
 	f.writeBuffer = nil
 	if err := f.Close(); err != nil {
 		return err
@@ -193,10 +196,37 @@ func (f *File) Delete() error {
 		return err
 	}
 
-	_, err = client.DeleteObject(&s3.DeleteObjectInput{
-		Key:    &f.key,
-		Bucket: &f.bucket,
-	})
+	var deleteAllVersions bool
+	for _, o := range opts {
+		switch o.(type) {
+		case delete.DeleteAllVersions:
+			deleteAllVersions = true
+		default:
+		}
+	}
+
+	if deleteAllVersions {
+		objectVersions, err := f.getAllObjectVersions(client)
+		if err != nil {
+			return err
+		}
+
+		for _, version := range objectVersions.Versions {
+			if _, err = client.DeleteObject(&s3.DeleteObjectInput{
+				Key:       &f.key,
+				Bucket:    &f.bucket,
+				VersionId: version.VersionId,
+			}); err != nil {
+				return err
+			}
+		}
+	} else {
+		_, err = client.DeleteObject(&s3.DeleteObjectInput{
+			Key:    &f.key,
+			Bucket: &f.bucket,
+		})
+	}
+
 	return err
 }
 
@@ -316,6 +346,15 @@ func (f *File) String() string {
 /*
 	Private helper functions
 */
+func (f *File) getAllObjectVersions(client s3iface.S3API) (*s3.ListObjectVersionsOutput, error) {
+	prefix := utils.RemoveLeadingSlash(f.key)
+	objVers, err := client.ListObjectVersions(&s3.ListObjectVersionsInput{
+		Bucket: &f.bucket,
+		Prefix: &prefix,
+	})
+	return objVers, err
+}
+
 func (f *File) getHeadObject() (*s3.HeadObjectOutput, error) {
 	headObjectInput := new(s3.HeadObjectInput).SetKey(f.key).SetBucket(f.bucket)
 	client, err := f.fileSystem.Client()
diff --git a/backend/s3/location.go b/backend/s3/location.go
index e6fe0fa..730eb56 100644
--- a/backend/s3/location.go
+++ b/backend/s3/location.go
@@ -10,6 +10,7 @@ import (
 	"github.com/aws/aws-sdk-go/service/s3"
 
 	"github.com/c2fo/vfs/v6"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -142,13 +143,13 @@ func (l *Location) NewFile(filePath string) (vfs.File, error) {
 }
 
 // DeleteFile removes the file at fileName path.
-func (l *Location) DeleteFile(fileName string) error {
+func (l *Location) DeleteFile(fileName string, opts ...options.DeleteOption) error {
 	file, err := l.NewFile(fileName)
 	if err != nil {
 		return err
 	}
 
-	return file.Delete()
+	return file.Delete(opts...)
 }
 
 // FileSystem returns a vfs.FileSystem interface of the location's underlying file system.
diff --git a/backend/sftp/file.go b/backend/sftp/file.go
index cf31f8f..04db325 100644
--- a/backend/sftp/file.go
+++ b/backend/sftp/file.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"github.com/c2fo/vfs/v6"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -225,7 +226,7 @@ func (f *File) CopyToLocation(location vfs.Location) (vfs.File, error) {
 // CRUD Operations
 
 // Delete removes the remote file.  Error is returned, if any.
-func (f *File) Delete() error {
+func (f *File) Delete(opts ...options.DeleteOption) error {
 	client, err := f.fileSystem.Client(f.Authority)
 	if err != nil {
 		return err
diff --git a/backend/sftp/location.go b/backend/sftp/location.go
index 3e436e4..ede4a0d 100644
--- a/backend/sftp/location.go
+++ b/backend/sftp/location.go
@@ -10,6 +10,7 @@ import (
 	"unicode/utf8"
 
 	"github.com/c2fo/vfs/v6"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -197,13 +198,13 @@ func (l *Location) NewFile(filePath string) (vfs.File, error) {
 }
 
 // DeleteFile removes the file at fileName path.
-func (l *Location) DeleteFile(fileName string) error {
+func (l *Location) DeleteFile(fileName string, opts ...options.DeleteOption) error {
 	file, err := l.NewFile(fileName)
 	if err != nil {
 		return err
 	}
 
-	return file.Delete()
+	return file.Delete(opts...)
 }
 
 // FileSystem returns a vfs.fileSystem interface of the location's underlying fileSystem.
diff --git a/docs/delete_options.md b/docs/delete_options.md
new file mode 100644
index 0000000..777b242
--- /dev/null
+++ b/docs/delete_options.md
@@ -0,0 +1,43 @@
+# Delete Options
+
+---
+
+Package delete consists of custom delete options
+
+## DeleteAllVersions
+Currently, we have DeleteAllVersions option that can be used to remove all the versions of a file upon delete.
+This is supported for all filesystems that have file versioning (E.g: S3, GS etc.)
+
+### Usage
+
+Delete file using file.delete():
+
+```go
+    import(
+        "github.com/c2fo/vfs/v6/options"
+        "github.com/c2fo/vfs/v6/options/delete"
+    )
+    
+    func DeleteFile() error {
+        file, err := fs.NewFile(bucketName, fileName)
+        ...
+        err = file.Delete(delete.WithDeleteAllVersions())
+        ...
+    }
+```
+
+OR
+
+Delete file using location.delete():
+
+```go
+    import(
+        "github.com/c2fo/vfs/v6/options"
+        "github.com/c2fo/vfs/v6/options/delete"
+    )
+    
+    func DeleteFileUsingLocation() error {
+        err = location.DeleteFile("filename.txt", delete.WithDeleteAllVersions())
+        ...
+    }
+```
\ No newline at end of file
diff --git a/docs/gs.md b/docs/gs.md
index c9ab9fd..8b87c75 100644
--- a/docs/gs.md
+++ b/docs/gs.md
@@ -131,11 +131,11 @@ to the new file.
 #### func (*File) Delete
 
 ```go
-func (f *File) Delete() error
+func (f *File) Delete(opts ...options.DeleteOption) error
 ```
 Delete clears any local temp file, or write buffer from read/writes to the file,
-then makes a DeleteObject call to s3 for the file. Returns any error returned by
-the API.
+then makes a DeleteObject call to GCS for the file. If opts is of type DeleteAllVersions, DeleteObject call is made to 
+GCS for each version of the file. Returns any error returned by the API. 
 
 #### func (*File) Exists
 
@@ -332,9 +332,9 @@ ChangeDir changes the current location's path to the new, relative path.
 #### func (*Location) DeleteFile
 
 ```go
-func (l *Location) DeleteFile(fileName string) error
+func (l *Location) DeleteFile(fileName string, opts ...options.DeleteOption) error
 ```
-DeleteFile deletes the file at the given path, relative to the current location.
+DeleteFile deletes the file at the given path, relative to the current location using given options.
 
 #### func (*Location) Exists
 
diff --git a/docs/options.md b/docs/options.md
new file mode 100644
index 0000000..c1c3624
--- /dev/null
+++ b/docs/options.md
@@ -0,0 +1,47 @@
+# options
+
+---
+
+
+Package options provides a means of creating custom options that can be used with operations that are performed on components of filesystem. 
+
+## DeleteOption
+Currently, we define DeleteOption interface that can be used to implement custom options that can be used for delete operation. One such implementation is the [DeleteAllVersions](./delete_options.md#DeleteAllVersions) option.
+
+## Development
+
+### Create new DeleteOption
+To create your own DeleteOption, you must create a type that implements the DeleteOption interface:
+
+```go
+    type MyTakeBackupDeleteOption {
+	    backupLocation string
+    }
+
+    func (o MyTakeBackupDeleteOption) DeleteOptionName() string {
+        return "take-backup"
+    }
+
+    func (o MyTakeBackupDeleteOption) BackupLocation() string {
+        return o.backupLocation
+    }
+```
+
+Now, in each implementation of file.Delete(... options.DeleteOptions), implement the behaviour for this new option according to your need:
+
+```go
+    func (f *File) Delete(opts ...options.DeleteOption) error {
+        for _, o := range opts {
+            switch o.(type) {
+            case delete.DeleteAllVersions:
+                deleteAllVersions = true
+			case delete.MyTakeBackupDeleteOption:
+                // do something to take backup	
+            default:
+            }
+        }
+        ...
+        ...
+    }
+```
+
diff --git a/docs/s3.md b/docs/s3.md
index 9c66b6a..483575b 100644
--- a/docs/s3.md
+++ b/docs/s3.md
@@ -148,10 +148,11 @@ the new file.
 #### func (*File) Delete
 
 ```go
-func (f *File) Delete() error
+func (f *File) Delete(opts ...options.DeleteOption) error
 ```
 Delete clears any local temp file, or write buffer from read/writes to the file,
-then makes a DeleteObject call to s3 for the file. Returns any error returned by
+then makes a DeleteObject call to s3 for the file. If opts is of type DeleteAllVersions, DeleteObject call is made to
+s3 for each version of the file. Returns any error returned by
 the API.
 
 #### func (*File) Exists
@@ -352,9 +353,9 @@ implementation there are no errors.
 #### func (*Location) DeleteFile
 
 ```go
-func (l *Location) DeleteFile(fileName string) error
+func (l *Location) DeleteFile(fileName string, opts ...options.DeleteOption) error
 ```
-DeleteFile removes the file at fileName path.
+DeleteFile removes the file at fileName path using given options.
 
 #### func (*Location) Exists
 
diff --git a/mocks/File.go b/mocks/File.go
index f3bfc0a..6818175 100644
--- a/mocks/File.go
+++ b/mocks/File.go
@@ -1,10 +1,15 @@
-// Code generated by mockery v1.0.0. DO NOT EDIT.
+// Code generated by mockery v2.12.3. DO NOT EDIT.
 
 package mocks
 
-import mock "github.com/stretchr/testify/mock"
-import time "time"
-import vfs "github.com/c2fo/vfs/v6"
+import (
+	options "github.com/c2fo/vfs/v6/options"
+	mock "github.com/stretchr/testify/mock"
+
+	time "time"
+
+	vfs "github.com/c2fo/vfs/v6"
+)
 
 // File is an autogenerated mock type for the File type
 type File struct {
@@ -62,13 +67,19 @@ func (_m *File) CopyToLocation(location vfs.Location) (vfs.File, error) {
 	return r0, r1
 }
 
-// Delete provides a mock function with given fields:
-func (_m *File) Delete() error {
-	ret := _m.Called()
+// Delete provides a mock function with given fields: deleteOpts
+func (_m *File) Delete(deleteOpts ...options.DeleteOption) error {
+	_va := make([]interface{}, len(deleteOpts))
+	for _i := range deleteOpts {
+		_va[_i] = deleteOpts[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func() error); ok {
-		r0 = rf()
+	if rf, ok := ret.Get(0).(func(...options.DeleteOption) error); ok {
+		r0 = rf(deleteOpts...)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -326,3 +337,18 @@ func (_m *File) Write(p []byte) (int, error) {
 
 	return r0, r1
 }
+
+type NewFileT interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewFile creates a new instance of File. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewFile(t NewFileT) *File {
+	mock := &File{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/mocks/Location.go b/mocks/Location.go
index ae8ff38..6e97c77 100644
--- a/mocks/Location.go
+++ b/mocks/Location.go
@@ -1,10 +1,15 @@
-// Code generated by mockery v1.0.0. DO NOT EDIT.
+// Code generated by mockery v2.12.3. DO NOT EDIT.
 
 package mocks
 
-import mock "github.com/stretchr/testify/mock"
-import regexp "regexp"
-import vfs "github.com/c2fo/vfs/v6"
+import (
+	options "github.com/c2fo/vfs/v6/options"
+	mock "github.com/stretchr/testify/mock"
+
+	regexp "regexp"
+
+	vfs "github.com/c2fo/vfs/v6"
+)
 
 // Location is an autogenerated mock type for the Location type
 type Location struct {
@@ -25,13 +30,20 @@ func (_m *Location) ChangeDir(relLocPath string) error {
 	return r0
 }
 
-// DeleteFile provides a mock function with given fields: relFilePath
-func (_m *Location) DeleteFile(relFilePath string) error {
-	ret := _m.Called(relFilePath)
+// DeleteFile provides a mock function with given fields: relFilePath, deleteOpts
+func (_m *Location) DeleteFile(relFilePath string, deleteOpts ...options.DeleteOption) error {
+	_va := make([]interface{}, len(deleteOpts))
+	for _i := range deleteOpts {
+		_va[_i] = deleteOpts[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, relFilePath)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(string) error); ok {
-		r0 = rf(relFilePath)
+	if rf, ok := ret.Get(0).(func(string, ...options.DeleteOption) error); ok {
+		r0 = rf(relFilePath, deleteOpts...)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -246,3 +258,18 @@ func (_m *Location) Volume() string {
 
 	return r0
 }
+
+type NewLocationT interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewLocation creates a new instance of Location. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewLocation(t NewLocationT) *Location {
+	mock := &Location{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/options/delete/deleteAllVersions.go b/options/delete/deleteAllVersions.go
new file mode 100644
index 0000000..2808d26
--- /dev/null
+++ b/options/delete/deleteAllVersions.go
@@ -0,0 +1,19 @@
+package delete
+
+import "github.com/c2fo/vfs/v6/options"
+
+const optionNameDeleteAllVersions = "deleteAllVersions"
+
+// WithDeleteAllVersions returns DeleteAllVersions implementation of delete.DeleteOption
+func WithDeleteAllVersions() options.DeleteOption {
+	return DeleteAllVersions{}
+}
+
+// DeleteAllVersions represents the DeleteOption that is used to remove all versions of files when deleted.
+// This will remove all versions of files for the filesystems that support file versioning.
+type DeleteAllVersions struct{}
+
+// DeleteOptionName returns the name of DeleteAllVersions option
+func (w DeleteAllVersions) DeleteOptionName() string {
+	return optionNameDeleteAllVersions
+}
diff --git a/options/options.go b/options/options.go
new file mode 100644
index 0000000..23cd7c4
--- /dev/null
+++ b/options/options.go
@@ -0,0 +1,16 @@
+package options
+
+// DeleteOption interface contains function that should be implemented by any custom option to qualify as a delete option.
+// Example:
+// ```
+//	type TakeBackupDeleteOption{}
+// 	func (o TakeBackupDeleteOption) DeleteOptionName() string {
+//		return "take backup"
+//	}
+//	func (o TakeBackupDeleteOption) BackupLocation() string {
+// 		return o.backupLocation
+//	}
+// ```
+type DeleteOption interface {
+	DeleteOptionName() string
+}
diff --git a/vfs.go b/vfs.go
index a8a0f96..f218dc0 100644
--- a/vfs.go
+++ b/vfs.go
@@ -5,6 +5,8 @@ import (
 	"io"
 	"regexp"
 	"time"
+
+	"github.com/c2fo/vfs/v6/options"
 )
 
 // FileSystem represents a file system with any authentication accounted for.
@@ -129,7 +131,7 @@ type Location interface {
 	// error handling overhead.
 	//
 	// * Accepts relative file path.
-	DeleteFile(relFilePath string) error
+	DeleteFile(relFilePath string, deleteOpts ...options.DeleteOption) error
 
 	// URI returns the fully qualified absolute URI for the Location.  IE, s3://bucket/some/path/
 	//
@@ -195,7 +197,7 @@ type File interface {
 	MoveToFile(file File) error
 
 	// Delete unlinks the File on the file system.
-	Delete() error
+	Delete(deleteOpts ...options.DeleteOption) error
 
 	// LastModified returns the timestamp the file was last modified (as *time.Time).
 	LastModified() (*time.Time, error)


Test patch:
diff --git a/backend/gs/file_test.go b/backend/gs/file_test.go
index 6691100..063383f 100644
--- a/backend/gs/file_test.go
+++ b/backend/gs/file_test.go
@@ -13,6 +13,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/suite"
 
+	"github.com/c2fo/vfs/v6/options/delete"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -113,6 +114,118 @@ func (ts *fileTestSuite) TestRead() {
 	ts.Equal(localFile.String(), contents, "Copying an gs file to a buffer should fill buffer with file's contents")
 }
 
+func (ts *fileTestSuite) TestDelete() {
+	contents := "hello world!"
+	bucketName := "bucki"
+	objectName := "some/path/file.txt"
+	server := fakestorage.NewServer(
+		Objects{
+			fakestorage.Object{
+				ObjectAttrs: fakestorage.ObjectAttrs{
+					BucketName:      bucketName,
+					Name:            objectName,
+					ContentType:     "text/plain",
+					ContentEncoding: "utf8",
+				},
+				Content: []byte(contents),
+			},
+		},
+	)
+	defer server.Stop()
+	client := server.Client()
+	fs := NewFileSystem().WithClient(client)
+
+	file, err := fs.NewFile(bucketName, "/"+objectName)
+	if err != nil {
+		ts.Fail("Shouldn't fail creating new file")
+	}
+
+	err = file.Delete()
+	if err != nil {
+		ts.Fail("Shouldn't fail deleting the file")
+	}
+
+	bucket := client.Bucket(bucketName)
+	assert.Equal(ts.T(), false, objectExists(bucket, objectName))
+}
+
+func (ts *fileTestSuite) TestDeleteError() {
+	contents := "hello world!"
+	bucketName := "bucki"
+	objectName := "some/path/file.txt"
+	server := fakestorage.NewServer(
+		Objects{
+			fakestorage.Object{
+				ObjectAttrs: fakestorage.ObjectAttrs{
+					BucketName:      bucketName,
+					Name:            objectName,
+					ContentType:     "text/plain",
+					ContentEncoding: "utf8",
+				},
+				Content: []byte(contents),
+			},
+		},
+	)
+	defer server.Stop()
+	client := server.Client()
+	fs := NewFileSystem().WithClient(client)
+
+	file, err := fs.NewFile(bucketName, "/invalidObject")
+	if err != nil {
+		ts.Fail("Shouldn't fail creating new file")
+	}
+
+	err = file.Delete()
+	ts.NotNil(err, "Should return an error if gs client had error")
+}
+
+func (ts *fileTestSuite) TestDeleteRemoveAllVersions() {
+	contents := "hello world!"
+	bucketName := "bucki"
+	objectName := "some/path/file.txt"
+	server := fakestorage.NewServer(
+		Objects{
+			fakestorage.Object{
+				ObjectAttrs: fakestorage.ObjectAttrs{
+					BucketName:      bucketName,
+					Name:            objectName,
+					ContentType:     "text/plain",
+					ContentEncoding: "utf8",
+				},
+				Content: []byte(contents),
+			},
+		},
+	)
+	defer server.Stop()
+	client := server.Client()
+	fs := NewFileSystem().WithClient(client)
+
+	file, err := fs.NewFile(bucketName, "/"+objectName)
+	if err != nil {
+		ts.Fail("Shouldn't fail creating new file")
+	}
+
+	f := file.(*File)
+	handles, err := f.getObjectGenerationHandles()
+	if err != nil {
+		ts.Fail("Shouldn't fail getting object generation handles")
+	}
+	assert.Equal(ts.T(), 1, len(handles))
+
+	err = file.Delete(delete.WithDeleteAllVersions())
+	if err != nil {
+		ts.Fail("Shouldn't fail deleting the file")
+	}
+
+	bucket := client.Bucket(bucketName)
+	assert.Equal(ts.T(), false, objectExists(bucket, objectName))
+	handles, err = f.getObjectGenerationHandles()
+	if err != nil {
+		ts.Fail("Shouldn't fail getting object generation handles")
+	}
+	assert.Nil(ts.T(), handles)
+}
+
 func (ts *fileTestSuite) TestWrite() {
 	contents := "hello world!"
 	bucketName := "bucki"
diff --git a/backend/s3/file_test.go b/backend/s3/file_test.go
index 5b29498..33ab7a4 100644
--- a/backend/s3/file_test.go
+++ b/backend/s3/file_test.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/mocks"
+	"github.com/c2fo/vfs/v6/options/delete"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -550,6 +551,50 @@ func (ts *fileTestSuite) TestDelete() {
 	s3apiMock.AssertExpectations(ts.T())
 }
 
+func (ts *fileTestSuite) TestDeleteError() {
+	s3apiMock.On("DeleteObject", mock.AnythingOfType("*s3.DeleteObjectInput")).Return(nil, errors.New("something went wrong"))
+	err := testFile.Delete()
+	ts.NotNil(err, "Delete should return an error if s3 api had error.")
+	ts.Equal(err.Error(), "something went wrong")
+	s3apiMock.AssertExpectations(ts.T())
+}
+
+func (ts *fileTestSuite) TestDeleteWithDeleteAllVersionsOption() {
+	var versions []*s3.ObjectVersion
+	verIds := [...]string{"ver1", "ver2"}
+	for i := range verIds {
+		versions = append(versions, &s3.ObjectVersion{VersionId: &verIds[i]})
+	}
+	versOutput := s3.ListObjectVersionsOutput{
+		Versions: versions,
+	}
+	s3apiMock.On("ListObjectVersions", mock.AnythingOfType("*s3.ListObjectVersionsInput")).Return(&versOutput, nil)
+	s3apiMock.On("DeleteObject", mock.AnythingOfType("*s3.DeleteObjectInput")).Return(&s3.DeleteObjectOutput{}, nil)
+
+	err := testFile.Delete(delete.WithDeleteAllVersions())
+	ts.Nil(err, "Successful delete should not return an error.")
+	s3apiMock.AssertExpectations(ts.T())
+	s3apiMock.AssertNumberOfCalls(ts.T(), "DeleteObject", 2)
+}
+
+func (ts *fileTestSuite) TestDeleteWithDeleteAllVersionsOptionError() {
+	var versions []*s3.ObjectVersion
+	verIds := [...]string{"ver1", "ver2"}
+	for i := range verIds {
+		versions = append(versions, &s3.ObjectVersion{VersionId: &verIds[i]})
+	}
+	versOutput := s3.ListObjectVersionsOutput{
+		Versions: versions,
+	}
+	s3apiMock.On("ListObjectVersions", mock.AnythingOfType("*s3.ListObjectVersionsInput")).Return(&versOutput, nil)
+	s3apiMock.On("DeleteObject", mock.AnythingOfType("*s3.DeleteObjectInput")).Return(nil, errors.New("something went wrong"))
+
+	err := testFile.Delete(delete.WithDeleteAllVersions())
+	ts.NotNil(err, "Delete should return an error if s3 api had error.")
+	s3apiMock.AssertExpectations(ts.T())
+	s3apiMock.AssertNumberOfCalls(ts.T(), "DeleteObject", 1)
+}
+
 func (ts *fileTestSuite) TestLastModified() {
 	now := time.Now()
 	s3apiMock.On("HeadObject", mock.AnythingOfType("*s3.HeadObjectInput")).Return(&s3.HeadObjectOutput{
diff --git a/backend/s3/location_test.go b/backend/s3/location_test.go
index e95f86c..9ddf2d5 100644
--- a/backend/s3/location_test.go
+++ b/backend/s3/location_test.go
@@ -11,6 +11,7 @@ import (
 	"github.com/stretchr/testify/suite"
 
 	"github.com/c2fo/vfs/v6/mocks"
+	"github.com/c2fo/vfs/v6/options/delete"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -299,6 +300,26 @@ func (lt *locationTestSuite) TestDeleteFile() {
 	lt.s3apiMock.AssertExpectations(lt.T())
 }
 
+func (lt *locationTestSuite) TestDeleteFileWithDeleteAllVersionsOption() {
+	var versions []*s3.ObjectVersion
+	verIds := [...]string{"ver1", "ver2"}
+	for i := range verIds {
+		versions = append(versions, &s3.ObjectVersion{VersionId: &verIds[i]})
+	}
+	versOutput := s3.ListObjectVersionsOutput{
+		Versions: versions,
+	}
+	lt.s3apiMock.On("ListObjectVersions", mock.AnythingOfType("*s3.ListObjectVersionsInput")).Return(&versOutput, nil)
+	lt.s3apiMock.On("DeleteObject", mock.AnythingOfType("*s3.DeleteObjectInput")).Return(&s3.DeleteObjectOutput{}, nil)
+	loc, err := lt.fs.NewLocation("bucket", "/old/")
+	lt.NoError(err)
+
+	err = loc.DeleteFile("filename.txt", delete.WithDeleteAllVersions())
+	lt.Nil(err, "Successful delete should not return an error.")
+	lt.s3apiMock.AssertExpectations(lt.T())
+	lt.s3apiMock.AssertNumberOfCalls(lt.T(), "DeleteObject", 2)
+}
+
 func TestLocation(t *testing.T) {
 	suite.Run(t, new(locationTestSuite))
 }