Repository: C2FO/vfs
Pull request #219 (2024-12-23 23:09:20)

add support for assumed role credentials in the s3 backend
Add support for role based authentication in s3 backend.


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 775ccc5..3cf9206 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -14,7 +14,7 @@ jobs:
       fail-fast: false
       matrix:
         go-version: [1.22, 1.23]
-        os: [ubuntu-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Install Go
@@ -24,5 +24,17 @@ jobs:
           cache: false
       - name: Checkout code
         uses: actions/checkout@v4
-      - name: Test
-        run: go test ./...
+      - name: Set TMPDIR for Windows
+        if: matrix.os == 'windows-latest'
+        run: |
+          echo "TEMP=C:\Temp" >> $GITHUB_ENV
+          echo "TMP=C:\Temp" >> $GITHUB_ENV
+        shell: cmd
+
+      - name: Run Tests
+        env:
+          TEMP: ${{ env.TEMP || '' }}
+          TMP: ${{ env.TMP || '' }}
+        run: |
+          echo "Temporary directory: $TMPDIR"
+          go test ./...
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0c0d13..214079d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Added
+- Add support for role based authentication in s3 backend.
+
+## [6.25.1] - 2025-01-09
+### Fixed
+- The intersection of features #208 and #213 was missed (during rebase).
+
+## [6.25.0] - 2025-01-08
+### Added
+- Added NewFile option for Content-Type.
+- Windows support in the os backend.
+### Fixed
+- Ability to run all unit tests on Windows.
+- Deprecated delete.WithDeleteAllVersions in favor of delete.WithAllVersions.
+### Security
+- Switched to new Azure SDK (from deprecated github.com/Azure/azure-storage-blob-go)
+- Updated dependencies
 
 ## [6.24.0] - 2024-12-16
 ### Security
diff --git a/backend/azure/client.go b/backend/azure/client.go
index 8302374..a401e4f 100644
--- a/backend/azure/client.go
+++ b/backend/azure/client.go
@@ -2,14 +2,17 @@ package azure
 
 import (
 	"context"
-	"fmt"
+	"errors"
 	"io"
-	"net/url"
 	"strings"
 	"time"
 
-	"github.com/Azure/azure-pipeline-go/pipeline"
-	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/utils"
@@ -24,11 +27,11 @@ type Client interface {
 
 	// SetMetadata should add the metadata specified by the parameter metadata for the blob specified by the parameter
 	// file.
-	SetMetadata(file vfs.File, metadata map[string]string) error
+	SetMetadata(file vfs.File, metadata map[string]*string) error
 
 	// Upload should create or update the blob specified by the file parameter with the contents of the content
 	// parameter
-	Upload(file vfs.File, content io.ReadSeeker) error
+	Upload(file vfs.File, content io.ReadSeeker, contentType string) error
 
 	// Download should return a reader for the blob specified by the file parameter
 	Download(file vfs.File) (io.ReadCloser, error)
@@ -48,7 +51,7 @@ type Client interface {
 
 // DefaultClient is the main implementation that actually makes the calls to Azure Blob Storage
 type DefaultClient struct {
-	pipeline pipeline.Pipeline
+	credential any
 }
 
 // NewClient initializes a new DefaultClient
@@ -58,43 +61,39 @@ func NewClient(options *Options) (*DefaultClient, error) {
 		return nil, err
 	}
 
-	// This configures the client to use the default retry policy.  The default policy uses exponential backoff with
-	// maxRetries = 4.  If this behavior needs to be changed, add the Retry member to azblob.PipelineOptions.  For
-	// more information on azure retry policies see https://pkg.go.dev/github.com/Azure/azure-storage-blob-go/azblob#RetryOptions
-	//
-	//  Example (this is not the default):
-	//	RetryOptions{
-	//		Policy:        RetryPolicyExponential, // Use exponential backoff as opposed to linear
-	//		MaxTries:      3,                      // Try at most 3 times to perform the operation (set to 1 to disable retries)
-	//		TryTimeout:    time.Second * 3,        // Maximum time allowed for any single try
-	//		RetryDelay:    time.Second * 1,        // Backoff amount for each retry (exponential or linear)
-	//		MaxRetryDelay: time.Second * 3,        // Max delay between retries
-	//	}
-	pl := azblob.NewPipeline(credential, azblob.PipelineOptions{})
-
-	return &DefaultClient{pl}, nil
+	return &DefaultClient{credential}, nil
+}
+
+func (a *DefaultClient) newContainerClient(containerURL string) (*container.Client, error) {
+	switch cred := a.credential.(type) {
+	case azcore.TokenCredential:
+		return container.NewClient(containerURL, cred, nil)
+	case *container.SharedKeyCredential:
+		return container.NewClientWithSharedKeyCredential(containerURL, cred, nil)
+	default:
+		return container.NewClientWithNoCredential(containerURL, nil)
+	}
 }
 
 // Properties fetches the properties for the blob specified by the parameters containerURI and filePath
 func (a *DefaultClient) Properties(containerURI, filePath string) (*BlobProperties, error) {
-	URL, err := url.Parse(containerURI)
+	cli, err := a.newContainerClient(containerURI)
 	if err != nil {
 		return nil, err
 	}
-	containerURL := azblob.NewContainerURL(*URL, a.pipeline)
 
 	if filePath == "" {
 		// this is only used to check for the existence of a container so we don't care about anything but the
 		// error
-		_, err := containerURL.GetProperties(context.Background(), azblob.LeaseAccessConditions{})
+		_, err := cli.GetProperties(context.Background(), nil)
 		if err != nil {
 			return nil, err
 		}
 		return nil, nil
 	}
 
-	blobURL := containerURL.NewBlockBlobURL(utils.RemoveLeadingSlash(filePath))
-	resp, err := blobURL.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
+	blobURL := cli.NewBlockBlobClient(utils.RemoveLeadingSlash(filePath))
+	resp, err := blobURL.GetProperties(context.Background(), nil)
 	if err != nil {
 		return nil, err
 	}
@@ -102,46 +101,49 @@ func (a *DefaultClient) Properties(containerURI, filePath string) (*BlobProperti
 }
 
 // Upload uploads a new file to Azure Blob Storage
-func (a *DefaultClient) Upload(file vfs.File, content io.ReadSeeker) error {
-	URL, err := url.Parse(file.Location().(*Location).ContainerURL())
+func (a *DefaultClient) Upload(file vfs.File, content io.ReadSeeker, contentType string) error {
+	cli, err := a.newContainerClient(file.Location().(*Location).ContainerURL())
 	if err != nil {
 		return err
 	}
-
-	containerURL := azblob.NewContainerURL(*URL, a.pipeline)
-	blobURL := containerURL.NewBlockBlobURL(utils.RemoveLeadingSlash(file.Path()))
-	_, err = blobURL.Upload(context.Background(), content, azblob.BlobHTTPHeaders{}, azblob.Metadata{},
-		azblob.BlobAccessConditions{}, azblob.DefaultAccessTier, nil, azblob.ClientProvidedKeyOptions{}, azblob.ImmutabilityPolicyOptions{})
+	blobURL := cli.NewBlockBlobClient(utils.RemoveLeadingSlash(file.Path()))
+	body, ok := content.(io.ReadSeekCloser)
+	if !ok {
+		body = streaming.NopCloser(content)
+	}
+	var opts *blockblob.UploadOptions
+	if contentType != "" {
+		opts = &blockblob.UploadOptions{
+			HTTPHeaders: &blob.HTTPHeaders{BlobContentType: &contentType},
+		}
+	}
+	_, err = blobURL.Upload(context.Background(), body, opts)
 	return err
 }
 
 // SetMetadata sets the given metadata for the blob
-func (a *DefaultClient) SetMetadata(file vfs.File, metadata map[string]string) error {
-	URL, err := url.Parse(file.Location().(*Location).ContainerURL())
+func (a *DefaultClient) SetMetadata(file vfs.File, metadata map[string]*string) error {
+	cli, err := a.newContainerClient(file.Location().(*Location).ContainerURL())
 	if err != nil {
 		return err
 	}
-
-	containerURL := azblob.NewContainerURL(*URL, a.pipeline)
-	blobURL := containerURL.NewBlockBlobURL(utils.RemoveLeadingSlash(file.Path()))
-	_, err = blobURL.SetMetadata(context.Background(), metadata, azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
+	blobURL := cli.NewBlockBlobClient(utils.RemoveLeadingSlash(file.Path()))
+	_, err = blobURL.SetMetadata(context.Background(), metadata, nil)
 	return err
 }
 
 // Download returns an io.ReadCloser for the given vfs.File
 func (a *DefaultClient) Download(file vfs.File) (io.ReadCloser, error) {
-	URL, err := url.Parse(file.Location().(*Location).ContainerURL())
+	cli, err := a.newContainerClient(file.Location().(*Location).ContainerURL())
 	if err != nil {
 		return nil, err
 	}
-
-	containerURL := azblob.NewContainerURL(*URL, a.pipeline)
-	blobURL := containerURL.NewBlockBlobURL(utils.RemoveLeadingSlash(file.Path()))
-	get, err := blobURL.Download(context.Background(), 0, 0, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
+	blobURL := cli.NewBlockBlobClient(utils.RemoveLeadingSlash(file.Path()))
+	get, err := blobURL.DownloadStream(context.Background(), nil)
 	if err != nil {
 		return nil, err
 	}
-	return get.Body(azblob.RetryReaderOptions{}), nil
+	return get.Body, nil
 }
 
 // Copy copies srcFile to the destination tgtFile within Azure Blob Storage.  Note that in the case where we get
@@ -149,58 +151,54 @@ func (a *DefaultClient) Download(file vfs.File) (io.ReadCloser, error) {
 // error.
 func (a *DefaultClient) Copy(srcFile, tgtFile vfs.File) error {
 	// Can't use url.PathEscape here since that will escape everything (even the directory separators)
-	srcURL, err := url.Parse(strings.Replace(srcFile.URI(), "%", "%25", -1))
-	if err != nil {
-		return err
-	}
+	srcURL := strings.Replace(srcFile.URI(), "%", "%25", -1)
+
+	tgtURL := tgtFile.Location().(*Location).ContainerURL()
 
-	tgtURL, err := url.Parse(tgtFile.Location().(*Location).ContainerURL())
+	cli, err := a.newContainerClient(tgtURL)
 	if err != nil {
 		return err
 	}
-
-	containerURL := azblob.NewContainerURL(*tgtURL, a.pipeline)
-	blobURL := containerURL.NewBlockBlobURL(utils.RemoveLeadingSlash(tgtFile.Path()))
+	blobURL := cli.NewBlockBlobClient(utils.RemoveLeadingSlash(tgtFile.Path()))
 	ctx := context.Background()
-	resp, err := blobURL.StartCopyFromURL(ctx, *srcURL, azblob.Metadata{}, azblob.ModifiedAccessConditions{},
-		azblob.BlobAccessConditions{}, azblob.DefaultAccessTier, nil)
+	resp, err := blobURL.StartCopyFromURL(ctx, srcURL, nil)
 	if err != nil {
 		return err
 	}
 
-	for resp.CopyStatus() == azblob.CopyStatusPending {
+	for *resp.CopyStatus == blob.CopyStatusTypePending {
 		time.Sleep(2 * time.Second)
 	}
 
-	if resp.CopyStatus() == azblob.CopyStatusSuccess {
+	if *resp.CopyStatus == blob.CopyStatusTypeSuccess {
 		return nil
 	}
 
-	return fmt.Errorf("copy failed ERROR[%s]", resp.ErrorCode())
+	return errors.New("copy failed")
 }
 
 // List will return a listing of the contents of the given location.  Each item in the list will contain the full key
 // as specified by the azure blob (including the virtual 'path').
 func (a *DefaultClient) List(l vfs.Location) ([]string, error) {
-	URL, err := url.Parse(l.(*Location).ContainerURL())
+	cli, err := a.newContainerClient(l.(*Location).ContainerURL())
 	if err != nil {
 		return []string{}, err
 	}
 
-	containerURL := azblob.NewContainerURL(*URL, a.pipeline)
+	pager := cli.NewListBlobsHierarchyPager("/", &container.ListBlobsHierarchyOptions{
+		Prefix:  to.Ptr(utils.RemoveLeadingSlash(l.Path())),
+		Include: container.ListBlobsInclude{Metadata: true, Tags: true},
+	})
 	ctx := context.Background()
 	var list []string
-	for marker := (azblob.Marker{}); marker.NotDone(); {
-		listBlob, err := containerURL.ListBlobsHierarchySegment(ctx, marker, "/",
-			azblob.ListBlobsSegmentOptions{Prefix: utils.RemoveLeadingSlash(l.Path())})
+	for pager.More() {
+		listBlob, err := pager.NextPage(ctx)
 		if err != nil {
 			return []string{}, err
 		}
 
-		marker = listBlob.NextMarker
-
-		for i := range listBlob.Segment.BlobItems {
-			list = append(list, listBlob.Segment.BlobItems[i].Name)
+		for i := range listBlob.ListBlobsHierarchySegmentResponse.Segment.BlobItems {
+			list = append(list, *listBlob.ListBlobsHierarchySegmentResponse.Segment.BlobItems[i].Name)
 		}
 	}
 	return list, nil
@@ -208,14 +206,12 @@ func (a *DefaultClient) List(l vfs.Location) ([]string, error) {
 
 // Delete deletes the given file from Azure Blob Storage.
 func (a *DefaultClient) Delete(file vfs.File) error {
-	URL, err := url.Parse(file.Location().(*Location).ContainerURL())
+	cli, err := a.newContainerClient(file.Location().(*Location).ContainerURL())
 	if err != nil {
 		return err
 	}
-
-	containerURL := azblob.NewContainerURL(*URL, a.pipeline)
-	blobURL := containerURL.NewBlockBlobURL(utils.RemoveLeadingSlash(file.Path()))
-	_, err = blobURL.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
+	blobURL := cli.NewBlockBlobClient(utils.RemoveLeadingSlash(file.Path()))
+	_, err = blobURL.Delete(context.Background(), nil)
 	return err
 }
 
@@ -224,22 +220,24 @@ func (a *DefaultClient) Delete(file vfs.File) error {
 // If soft deletion is enabled for blobs in the storage account, each version will be marked for deletion and will be
 // permanently deleted by Azure as per the soft deletion policy.
 func (a *DefaultClient) DeleteAllVersions(file vfs.File) error {
-	URL, err := url.Parse(file.Location().(*Location).ContainerURL())
+	cli, err := a.newContainerClient(file.Location().(*Location).ContainerURL())
 	if err != nil {
 		return err
 	}
+	blobURL := cli.NewBlockBlobClient(utils.RemoveLeadingSlash(file.Path()))
 
-	containerURL := azblob.NewContainerURL(*URL, a.pipeline)
-	blobURL := containerURL.NewBlockBlobURL(utils.RemoveLeadingSlash(file.Path()))
-
-	versions, err := a.getBlobVersions(containerURL, utils.RemoveLeadingSlash(file.Path()))
+	versions, err := a.getBlobVersions(cli, utils.RemoveLeadingSlash(file.Path()))
 	if err != nil {
 		return err
 	}
 
 	for _, version := range versions {
 		// Delete a specific version
-		_, err = blobURL.WithVersionID(*version).Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
+		cli, err := blobURL.WithVersionID(*version)
+		if err != nil {
+			return err
+		}
+		_, err = cli.Delete(context.Background(), nil)
 		if err != nil {
 			return err
 		}
@@ -248,23 +246,21 @@ func (a *DefaultClient) DeleteAllVersions(file vfs.File) error {
 	return err
 }
 
-func (a *DefaultClient) getBlobVersions(containerURL azblob.ContainerURL, blobName string) ([]*string, error) {
+func (a *DefaultClient) getBlobVersions(cli *container.Client, blobName string) ([]*string, error) {
 	ctx := context.Background()
+	pager := cli.NewListBlobsFlatPager(&container.ListBlobsFlatOptions{
+		Prefix:  &blobName,
+		Include: container.ListBlobsInclude{Versions: true},
+	})
 	var versions []*string
-	for marker := (azblob.Marker{}); marker.NotDone(); {
-		listBlob, err := containerURL.ListBlobsFlatSegment(ctx, marker,
-			azblob.ListBlobsSegmentOptions{Prefix: blobName, Details: azblob.BlobListingDetails{Versions: true}})
+	for pager.More() {
+		listBlob, err := pager.NextPage(ctx)
 		if err != nil {
-			return nil, err
+			return []*string{}, err
 		}
 
-		marker = listBlob.NextMarker
-
-		for i := range listBlob.Segment.BlobItems {
-			blobItem := listBlob.Segment.BlobItems[i]
-			if blobItem.VersionID != nil {
-				versions = append(versions, blobItem.VersionID)
-			}
+		for i := range listBlob.ListBlobsFlatSegmentResponse.Segment.BlobItems {
+			versions = append(versions, listBlob.ListBlobsFlatSegmentResponse.Segment.BlobItems[i].VersionID)
 		}
 	}
 	return versions, nil
diff --git a/backend/azure/doc.go b/backend/azure/doc.go
index bb3a529..2a2e409 100644
--- a/backend/azure/doc.go
+++ b/backend/azure/doc.go
@@ -56,7 +56,7 @@ preferring the first location found:
     to containers from multiple storage accounts.
  2. The ENV vars VFS_AZURE_STORAGE_ACCOUNT and VFS_AZURE_STORAGE_KEY, a shared key authenticator is used.  This will
     allow access to any containers owned by the designated storage account.
- 3. If none of the above are present, then an anonymous authenticator is created and only publicly accessible blobs
+ 3. If none of the above are present, then no credentials are used and only publicly accessible blobs
     will be available
 */
 package azure
diff --git a/backend/azure/file.go b/backend/azure/file.go
index 1c74a06..d2ee1d9 100644
--- a/backend/azure/file.go
+++ b/backend/azure/file.go
@@ -9,12 +9,14 @@ import (
 	"strings"
 	"time"
 
-	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
 
 	"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/options/newfile"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -23,6 +25,7 @@ type File struct {
 	fileSystem *FileSystem
 	container  string
 	name       string
+	opts       []options.NewFileOption
 	tempFile   *os.File
 	isDirty    bool
 }
@@ -47,7 +50,16 @@ func (f *File) Close() error {
 		}
 
 		if f.isDirty {
-			if err := client.Upload(f, f.tempFile); err != nil {
+			var contentType string
+			for _, o := range f.opts {
+				switch o := o.(type) {
+				case *newfile.ContentType:
+					contentType = *(*string)(o)
+				default:
+				}
+			}
+
+			if err := client.Upload(f, f.tempFile, contentType); err != nil {
 				return utils.WrapCloseError(err)
 			}
 		}
@@ -120,7 +132,7 @@ func (f *File) Exists() (bool, error) {
 	}
 	_, err = client.Properties(f.Location().(*Location).ContainerURL(), f.Path())
 	if err != nil {
-		if err.(azblob.StorageError).ServiceCode() != "BlobNotFound" {
+		if !bloberror.HasCode(err, bloberror.BlobNotFound) {
 			return false, err
 		}
 		return false, nil
@@ -141,7 +153,7 @@ func (f *File) Location() vfs.Location {
 // name at the given location. If the given location is also azure, the azure API for copying
 // files will be utilized, otherwise, standard io.Copy will be done to the new file.
 func (f *File) CopyToLocation(location vfs.Location) (vfs.File, error) {
-	newFile, err := location.NewFile(utils.RemoveLeadingSlash(f.Name()))
+	newFile, err := location.NewFile(utils.RemoveLeadingSlash(f.Name()), f.opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -225,7 +237,7 @@ func (f *File) MoveToFile(file vfs.File) error {
 }
 
 // Delete deletes the file.
-// If DeleteAllVersions option is provided, each version of the file is deleted. NOTE: if soft deletion is enabled,
+// If delete.AllVersions option is provided, each version of the file is deleted. NOTE: if soft deletion is enabled,
 // it will mark all versions as soft deleted, and they will be removed by Azure as per soft deletion policy.
 // Returns any error returned by the API.
 func (f *File) Delete(opts ...options.DeleteOption) error {
@@ -238,11 +250,11 @@ func (f *File) Delete(opts ...options.DeleteOption) error {
 		return err
 	}
 
-	var deleteAllVersions bool
+	var allVersions bool
 	for _, o := range opts {
 		switch o.(type) {
-		case delete.DeleteAllVersions:
-			deleteAllVersions = true
+		case delete.AllVersions, delete.DeleteAllVersions:
+			allVersions = true
 		default:
 		}
 	}
@@ -251,7 +263,7 @@ func (f *File) Delete(opts ...options.DeleteOption) error {
 		return err
 	}
 
-	if deleteAllVersions {
+	if allVersions {
 		return client.DeleteAllVersions(f)
 	}
 
@@ -281,7 +293,7 @@ func (f *File) Size() (uint64, error) {
 	if err != nil {
 		return 0, err
 	}
-	return props.Size, nil
+	return uint64(*props.Size), nil
 }
 
 // Path returns full path with leading slash.
@@ -308,7 +320,16 @@ func (f *File) Touch() error {
 	}
 
 	if !exists {
-		return client.Upload(f, strings.NewReader(""))
+		var contentType string
+		for _, o := range f.opts {
+			switch o := o.(type) {
+			case *newfile.ContentType:
+				contentType = *(*string)(o)
+			default:
+			}
+		}
+
+		return client.Upload(f, strings.NewReader(""), contentType)
 	}
 
 	props, err := client.Properties(f.Location().(*Location).ContainerURL(), f.Path())
@@ -316,8 +337,8 @@ func (f *File) Touch() error {
 		return err
 	}
 
-	newMetadata := make(map[string]string)
-	newMetadata["updated"] = "true"
+	newMetadata := make(map[string]*string)
+	newMetadata["updated"] = to.Ptr("true")
 	if err := client.SetMetadata(f, newMetadata); err != nil {
 		return err
 	}
diff --git a/backend/azure/fileSystem.go b/backend/azure/fileSystem.go
index 2328020..c6d5437 100644
--- a/backend/azure/fileSystem.go
+++ b/backend/azure/fileSystem.go
@@ -4,13 +4,13 @@ import (
 	"errors"
 	"fmt"
 	"net/url"
-	"os"
 	"path"
 	"regexp"
 	"strings"
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/backend"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -58,7 +58,7 @@ func (fs *FileSystem) Client() (Client, error) {
 }
 
 // NewFile returns the azure implementation of vfs.File
-func (fs *FileSystem) NewFile(volume, absFilePath string) (vfs.File, error) {
+func (fs *FileSystem) NewFile(volume, absFilePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	if fs == nil {
 		return nil, errors.New(errNilFileSystemReceiver)
 	}
@@ -75,6 +75,7 @@ func (fs *FileSystem) NewFile(volume, absFilePath string) (vfs.File, error) {
 		fileSystem: fs,
 		container:  volume,
 		name:       path.Clean(absFilePath),
+		opts:       opts,
 	}, nil
 }
 
@@ -133,11 +134,8 @@ func ParsePath(p string) (host, pth string, err error) {
 	if p == "/" {
 		return "", "", errors.New("no container specified for Azure path")
 	}
-	var isLocation bool
-	if p[len(p)-1:] == string(os.PathSeparator) {
-		isLocation = true
-	}
-	l := strings.Split(p, string(os.PathSeparator))
+	isLocation := strings.HasSuffix(p, "/")
+	l := strings.Split(p, "/")
 	p = utils.EnsureLeadingSlash(path.Join(l[2:]...))
 	if isLocation {
 		p = utils.EnsureTrailingSlash(p)
diff --git a/backend/azure/location.go b/backend/azure/location.go
index 967a64d..0637bba 100644
--- a/backend/azure/location.go
+++ b/backend/azure/location.go
@@ -167,7 +167,7 @@ func (l *Location) FileSystem() vfs.FileSystem {
 }
 
 // NewFile returns a new file instance at the given path, relative to the current location.
-func (l *Location) NewFile(relFilePath string) (vfs.File, error) {
+func (l *Location) NewFile(relFilePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	if l == nil {
 		return nil, errors.New(errNilLocationReceiver)
 	}
@@ -180,6 +180,7 @@ func (l *Location) NewFile(relFilePath string) (vfs.File, error) {
 		name:       utils.EnsureLeadingSlash(path.Join(l.path, relFilePath)),
 		container:  l.container,
 		fileSystem: l.fileSystem,
+		opts:       opts,
 	}, nil
 }
 
diff --git a/backend/azure/mock_client.go b/backend/azure/mock_client.go
index 55e1be8..e6965c5 100644
--- a/backend/azure/mock_client.go
+++ b/backend/azure/mock_client.go
@@ -2,19 +2,20 @@ package azure
 
 import (
 	"io"
-	"net/http"
 
-	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
 
 	"github.com/c2fo/vfs/v6"
 )
 
 // MockAzureClient is a mock implementation of azure.Client.
 type MockAzureClient struct {
-	PropertiesError  error
-	PropertiesResult *BlobProperties
-	ExpectedError    error
-	ExpectedResult   interface{}
+	PropertiesError   error
+	PropertiesResult  *BlobProperties
+	ExpectedError     error
+	ExpectedResult    interface{}
+	UploadContentType string
 }
 
 // Properties returns a PropertiesResult if it exists, otherwise it will return the value of PropertiesError
@@ -26,12 +27,13 @@ func (a *MockAzureClient) Properties(locationURI, filePath string) (*BlobPropert
 }
 
 // SetMetadata returns the value of ExpectedError
-func (a *MockAzureClient) SetMetadata(file vfs.File, metadata map[string]string) error {
+func (a *MockAzureClient) SetMetadata(vfs.File, map[string]*string) error {
 	return a.ExpectedError
 }
 
 // Upload returns the value of ExpectedError
-func (a *MockAzureClient) Upload(file vfs.File, content io.ReadSeeker) error {
+func (a *MockAzureClient) Upload(file vfs.File, content io.ReadSeeker, contentType string) error {
+	a.UploadContentType = contentType
 	return a.ExpectedError
 }
 
@@ -66,32 +68,4 @@ func (a *MockAzureClient) DeleteAllVersions(file vfs.File) error {
 	return a.ExpectedError
 }
 
-// MockStorageError is a mock for the azblob.StorageError interface
-type MockStorageError struct {
-	azblob.ResponseError
-}
-
-// ServiceCode always returns "BlobNotFound" to simulate the not found condition
-func (mse MockStorageError) ServiceCode() azblob.ServiceCodeType {
-	return "BlobNotFound"
-}
-
-// Response returns nil
-func (mse MockStorageError) Response() *http.Response {
-	return nil
-}
-
-// Timeout returns nil
-func (mse MockStorageError) Timeout() bool {
-	return false
-}
-
-// Temporary returns nil
-func (mse MockStorageError) Temporary() bool {
-	return false
-}
-
-// Error returns empty string
-func (mse MockStorageError) Error() string {
-	return ""
-}
+var blobNotFoundErr = &azcore.ResponseError{ErrorCode: string(bloberror.BlobNotFound)}
diff --git a/backend/azure/mock_token.go b/backend/azure/mock_token.go
index ec14f11..8c7d43c 100644
--- a/backend/azure/mock_token.go
+++ b/backend/azure/mock_token.go
@@ -1,15 +1,11 @@
 package azure
 
 import (
-	"time"
-
-	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/fake"
 )
 
-// MockTokenCredentialFactory knows how to create a "do-nothing" credential used for unit testing
-type MockTokenCredentialFactory struct{}
-
-// New creates a new azblob.TokenCredential struct
-func (f *MockTokenCredentialFactory) New(tenantID, clientID, clientSecret, azureEnvName string) (azblob.TokenCredential, error) {
-	return azblob.NewTokenCredential("aaa", func(credential azblob.TokenCredential) time.Duration { return time.Second * 1 }), nil
+// MockTokenCredentialFactory creates a new azcore.TokenCredential struct
+func MockTokenCredentialFactory(_, _, _ string) (azcore.TokenCredential, error) {
+	return &fake.TokenCredential{}, nil
 }
diff --git a/backend/azure/options.go b/backend/azure/options.go
index b884453..8477287 100644
--- a/backend/azure/options.go
+++ b/backend/azure/options.go
@@ -5,7 +5,7 @@ import (
 
 	"github.com/c2fo/vfs/v6"
 
-	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
 )
 
 // Options contains options necessary for the azure vfs implementation
@@ -30,10 +30,6 @@ type Options struct {
 	// based authentication.
 	ClientSecret string
 
-	// AzureEnvName holds the name for the Azure environment.  This field is used for OAuth token
-	// based authentication.
-	AzureEnvName string
-
 	// RetryFunc holds the retry function
 	RetryFunc vfs.Retry
 
@@ -59,26 +55,25 @@ func NewOptions() *Options {
 		TenantID:               os.Getenv("VFS_AZURE_TENANT_ID"),
 		ClientID:               os.Getenv("VFS_AZURE_CLIENT_ID"),
 		ClientSecret:           os.Getenv("VFS_AZURE_CLIENT_SECRET"),
-		AzureEnvName:           os.Getenv("VFS_AZURE_ENV_NAME"),
-		tokenCredentialFactory: &DefaultTokenCredentialFactory{},
+		tokenCredentialFactory: DefaultTokenCredentialFactory,
 	}
 }
 
-// Credential returns an azblob.Credential struct based on how options are configured.  Options are checked
+// Credential returns a credential based on how options are configured.  Options are checked
 // and evaluated in the following order:
-//  1. If TenantID, ClientID, and ClientSecret are non-empty, return azblob.TokenCredential.  This form of authentication
+//  1. If TenantID, ClientID, and ClientSecret are non-empty, return azcore.TokenCredential.  This form of authentication
 //     is used with service accounts and can be used to access containers across multiple storage accounts.
 //  2. If AccountName, and AccountKey are non-empty, return azblob.SharedKeyCredential.  This form or authentication
 //     is used with storage accounts and only provides access to a single storage account.
-//  3. Returns an anonymous credential.  This allows access only to public blobs.
-func (o *Options) Credential() (azblob.Credential, error) {
+//  3. Returns a nil credential.  This allows access only to public blobs.
+func (o *Options) Credential() (any, error) {
 	if o.tokenCredentialFactory == nil {
-		o.tokenCredentialFactory = &DefaultTokenCredentialFactory{}
+		o.tokenCredentialFactory = DefaultTokenCredentialFactory
 	}
 
 	// Check to see if we have service account credentials
 	if o.TenantID != "" && o.ClientID != "" && o.ClientSecret != "" {
-		return o.tokenCredentialFactory.New(o.TenantID, o.ClientID, o.ClientSecret, o.AzureEnvName)
+		return o.tokenCredentialFactory(o.TenantID, o.ClientID, o.ClientSecret)
 	}
 
 	// Check to see if we have storage account credentials
@@ -86,6 +81,6 @@ func (o *Options) Credential() (azblob.Credential, error) {
 		return azblob.NewSharedKeyCredential(o.AccountName, o.AccountKey)
 	}
 
-	// 3. Return an anonymous credential
-	return azblob.NewAnonymousCredential(), nil
+	// 3. Return a nil credential
+	return nil, nil
 }
diff --git a/backend/azure/properties.go b/backend/azure/properties.go
index d2fe9c0..c161df3 100644
--- a/backend/azure/properties.go
+++ b/backend/azure/properties.go
@@ -3,27 +3,27 @@ package azure
 import (
 	"time"
 
-	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
 )
 
 // BlobProperties holds a subset of information returned by Blob.GetProperties(..)
 type BlobProperties struct {
 	// Size holds the size of the blob.
-	Size uint64
+	Size *int64
 
 	// LastModified holds the last modified time.Time
 	LastModified *time.Time
 
 	// Metadata holds the Azure metadata
-	Metadata map[string]string
+	Metadata map[string]*string
 }
 
 // NewBlobProperties creates a new BlobProperties from an azblob.BlobGetPropertiesResponse
-func NewBlobProperties(azureProps *azblob.BlobGetPropertiesResponse) *BlobProperties {
-	lastModified := azureProps.LastModified()
+func NewBlobProperties(azureProps blob.GetPropertiesResponse) *BlobProperties {
+	lastModified := azureProps.LastModified
 	return &BlobProperties{
-		LastModified: &lastModified,
-		Metadata:     azureProps.NewMetadata(),
-		Size:         uint64(azureProps.ContentLength()),
+		LastModified: lastModified,
+		Metadata:     azureProps.Metadata,
+		Size:         azureProps.ContentLength,
 	}
 }
diff --git a/backend/azure/token.go b/backend/azure/token.go
index 5b567e4..e692b23 100644
--- a/backend/azure/token.go
+++ b/backend/azure/token.go
@@ -1,55 +1,17 @@
 package azure
 
 import (
-	"time"
-
-	"github.com/Azure/azure-storage-blob-go/azblob"
-	"github.com/Azure/go-autorest/autorest/adal"
-	"github.com/Azure/go-autorest/autorest/azure"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
 )
 
-// TokenCredentialFactory is an interface that provides a single factory method to create azure.TokenCredentials.  This
-// interface is provided to allow for mocking in unit tests.
-type TokenCredentialFactory interface {
-	// New creates a new azblob.TokenCredential struct
-	New(tenantID, clientID, clientSecret, azureEnvName string) (azblob.TokenCredential, error)
-}
+// TokenCredentialFactory creates azure.TokenCredentials.  This function is provided to allow for mocking in unit tests.
+type TokenCredentialFactory func(tenantID, clientID, clientSecret string) (azcore.TokenCredential, error)
 
 // DefaultTokenCredentialFactory knows how to make azblob.TokenCredential structs for OAuth authentication
-type DefaultTokenCredentialFactory struct{}
-
-// New creates a new azblob.TokenCredential struct
-func (f *DefaultTokenCredentialFactory) New(tenantID, clientID, clientSecret, azureEnvName string) (azblob.TokenCredential, error) {
-	env, err := azure.EnvironmentFromName(azureEnvName)
-	if err != nil {
-		return nil, err
-	}
-
-	oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, tenantID)
-	if err != nil {
-		return nil, err
-	}
-
-	spt, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, env.ResourceIdentifiers.Storage)
-	if err != nil {
-		return nil, err
-	}
-
-	initialToken := spt.Token()
-
-	var tokenRefresher = func(credential azblob.TokenCredential) time.Duration {
-		err := spt.Refresh()
-		if err != nil {
-			// Not sure what else to do here except panic...
-			// The documentation suggests returning 0 so the token will never attempt to refresh again
-			// and then call cancel() on any context in the pipeline that have this credential object
-			panic(err)
-		}
-
-		token := spt.Token()
-		credential.SetToken(token.AccessToken)
-		return token.Expires().Sub(time.Now().Add(2 * time.Minute))
+func DefaultTokenCredentialFactory(tenantID, clientID, clientSecret string) (azcore.TokenCredential, error) {
+	if clientID != "" || clientSecret != "" {
+		return azidentity.NewClientSecretCredential(tenantID, clientID, clientSecret, nil)
 	}
-
-	return azblob.NewTokenCredential(initialToken.AccessToken, tokenRefresher), nil
+	return azidentity.NewEnvironmentCredential(nil)
 }
diff --git a/backend/ftp/file.go b/backend/ftp/file.go
index 54844db..15cd746 100644
--- a/backend/ftp/file.go
+++ b/backend/ftp/file.go
@@ -33,6 +33,7 @@ type File struct {
 	fileSystem *FileSystem
 	authority  utils.Authority
 	path       string
+	opts       []options.NewFileOption
 	offset     int64
 }
 
diff --git a/backend/ftp/fileSystem.go b/backend/ftp/fileSystem.go
index 969d640..a4f42bf 100644
--- a/backend/ftp/fileSystem.go
+++ b/backend/ftp/fileSystem.go
@@ -9,6 +9,7 @@ import (
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/backend"
 	"github.com/c2fo/vfs/v6/backend/ftp/types"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -34,7 +35,7 @@ func (fs *FileSystem) Retry() vfs.Retry {
 }
 
 // NewFile function returns the FTP implementation of vfs.File.
-func (fs *FileSystem) NewFile(authority, filePath string) (vfs.File, error) {
+func (fs *FileSystem) NewFile(authority, filePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	if fs == nil {
 		return nil, errors.New("non-nil ftp.FileSystem pointer is required")
 	}
@@ -54,6 +55,7 @@ func (fs *FileSystem) NewFile(authority, filePath string) (vfs.File, error) {
 		fileSystem: fs,
 		authority:  auth,
 		path:       path.Clean(filePath),
+		opts:       opts,
 	}, nil
 }
 
diff --git a/backend/ftp/location.go b/backend/ftp/location.go
index 572714d..ce95c1f 100644
--- a/backend/ftp/location.go
+++ b/backend/ftp/location.go
@@ -193,7 +193,7 @@ func (l *Location) ChangeDir(relativePath string) error {
 
 // NewFile uses the properties of the calling location to generate a vfs.File (backed by an ftp.File). The filePath
 // argument is expected to be a relative path to the location's current path.
-func (l *Location) NewFile(filePath string) (vfs.File, error) {
+func (l *Location) NewFile(filePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	err := utils.ValidateRelativeFilePath(filePath)
 	if err != nil {
 		return nil, err
@@ -202,6 +202,7 @@ func (l *Location) NewFile(filePath string) (vfs.File, error) {
 		fileSystem: l.fileSystem,
 		authority:  l.Authority,
 		path:       utils.EnsureLeadingSlash(path.Join(l.path, filePath)),
+		opts:       opts,
 	}
 	return newFile, nil
 }
diff --git a/backend/gs/file.go b/backend/gs/file.go
index ed8600f..211f65e 100644
--- a/backend/gs/file.go
+++ b/backend/gs/file.go
@@ -16,6 +16,7 @@ import (
 	"github.com/c2fo/vfs/v6/backend"
 	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/options/delete"
+	"github.com/c2fo/vfs/v6/options/newfile"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -28,6 +29,7 @@ type File struct {
 	fileSystem *FileSystem
 	bucket     string
 	key        string
+	opts       []options.NewFileOption
 
 	// seek-related fields
 	cursorPos  int64
@@ -106,6 +108,14 @@ func (f *File) tempToGCS() error {
 	w := handle.NewWriter(f.fileSystem.ctx)
 	defer func() { _ = w.Close() }()
 
+	for _, o := range f.opts {
+		switch o := o.(type) {
+		case *newfile.ContentType:
+			w.ContentType = *(*string)(o)
+		default:
+		}
+	}
+
 	_, err = f.tempFileWriter.Seek(0, io.SeekStart)
 	if err != nil {
 		return err
@@ -327,6 +337,14 @@ func (f *File) initWriters() error {
 				return err
 			}
 
+			for _, o := range f.opts {
+				switch o := o.(type) {
+				case *newfile.ContentType:
+					w.ContentType = *(*string)(o)
+				default:
+				}
+			}
+
 			// set gcsWriter
 			f.gcsWriter = w
 		}
@@ -403,6 +421,11 @@ func (f *File) CopyToFile(file vfs.File) (err error) {
 
 	// do native copy if same location/auth
 	if tf, ok := file.(*File); ok {
+		// If the target file has no newfile options, use the source file's options (used if not same auth)
+		if len(f.opts) == 0 {
+			tf.opts = f.opts
+		}
+
 		opts, ok := tf.Location().FileSystem().(*FileSystem).options.(Options)
 		if ok {
 			if f.isSameAuth(&opts) {
@@ -454,18 +477,18 @@ 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. If DeleteAllVersions option is provided,
+// a DeleteObject call to GCS for the file. If delete.AllVersions 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 {
 	if err := f.Close(); err != nil {
 		return err
 	}
 
-	var deleteAllVersions bool
+	var allVersions bool
 	for _, o := range opts {
 		switch o.(type) {
-		case delete.DeleteAllVersions:
-			deleteAllVersions = true
+		case delete.AllVersions, delete.DeleteAllVersions:
+			allVersions = true
 		default:
 		}
 	}
@@ -479,7 +502,7 @@ func (f *File) Delete(opts ...options.DeleteOption) error {
 		return err
 	}
 
-	if deleteAllVersions {
+	if allVersions {
 		handles, err := f.getObjectGenerationHandles()
 		if err != nil {
 			return err
@@ -589,6 +612,15 @@ func (f *File) createEmptyFile() error {
 	defer cancel()
 
 	w := handle.NewWriter(ctx)
+
+	for _, o := range f.opts {
+		switch o := o.(type) {
+		case *newfile.ContentType:
+			w.ContentType = *(*string)(o)
+		default:
+		}
+	}
+
 	defer func() { _ = w.Close() }()
 	if _, err := w.Write(make([]byte, 0)); err != nil {
 		return err
diff --git a/backend/gs/fileSystem.go b/backend/gs/fileSystem.go
index add14f3..9ccc391 100644
--- a/backend/gs/fileSystem.go
+++ b/backend/gs/fileSystem.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"
 )
 
@@ -27,14 +28,14 @@ type FileSystem struct {
 
 // Retry will return a retrier provided via options, or a no-op if none is provided.
 func (fs *FileSystem) Retry() vfs.Retry {
-	if options, _ := fs.options.(Options); options.Retry != nil {
-		return options.Retry
+	if opts, _ := fs.options.(Options); opts.Retry != nil {
+		return opts.Retry
 	}
 	return vfs.DefaultRetryer()
 }
 
 // NewFile function returns the gcs implementation of vfs.File.
-func (fs *FileSystem) NewFile(volume, name string) (vfs.File, error) {
+func (fs *FileSystem) NewFile(volume, name string, opts ...options.NewFileOption) (vfs.File, error) {
 	if fs == nil {
 		return nil, errors.New("non-nil gs.FileSystem pointer is required")
 	}
@@ -48,6 +49,7 @@ func (fs *FileSystem) NewFile(volume, name string) (vfs.File, error) {
 		fileSystem: fs,
 		bucket:     volume,
 		key:        path.Clean(name),
+		opts:       opts,
 	}, nil
 }
 
diff --git a/backend/gs/location.go b/backend/gs/location.go
index f4ad7d3..38a5a5e 100644
--- a/backend/gs/location.go
+++ b/backend/gs/location.go
@@ -157,7 +157,7 @@ func (l *Location) FileSystem() vfs.FileSystem {
 }
 
 // NewFile returns a new file instance at the given path, relative to the current location.
-func (l *Location) NewFile(filePath string) (vfs.File, error) {
+func (l *Location) NewFile(filePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	if l == nil {
 		return nil, errors.New("non-nil gs.Location pointer is required")
 	}
@@ -172,6 +172,7 @@ func (l *Location) NewFile(filePath string) (vfs.File, error) {
 		fileSystem: l.fileSystem,
 		bucket:     l.bucket,
 		key:        utils.EnsureLeadingSlash(path.Join(l.prefix, filePath)),
+		opts:       opts,
 	}
 	return newFile, nil
 }
diff --git a/backend/mem/file.go b/backend/mem/file.go
index a15b943..ebd5cd7 100644
--- a/backend/mem/file.go
+++ b/backend/mem/file.go
@@ -40,6 +40,7 @@ type File struct {
 	memFile         *memFile
 	readWriteSeeker *ReadWriteSeeker
 	name            string // the base name of the file
+	opts            []options.NewFileOption
 	cursor          int
 	writeMode       mode
 	isOpen          bool
diff --git a/backend/mem/fileSystem.go b/backend/mem/fileSystem.go
index 212216a..99f65a7 100644
--- a/backend/mem/fileSystem.go
+++ b/backend/mem/fileSystem.go
@@ -6,6 +6,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"
 )
 
@@ -38,7 +39,7 @@ func (fs *FileSystem) Retry() vfs.Retry {
 // If a file is written to before a touch call, Write() will take care of that call.  This is
 // true for other functions as well and existence only poses a problem in the context of deletion
 // or copying FROM a non-existent file.
-func (fs *FileSystem) NewFile(volume, absFilePath string) (vfs.File, error) {
+func (fs *FileSystem) NewFile(volume, absFilePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	err := utils.ValidateAbsoluteFilePath(absFilePath)
 	if err != nil {
 		return nil, err
@@ -59,6 +60,7 @@ func (fs *FileSystem) NewFile(volume, absFilePath string) (vfs.File, error) {
 						name:            obj.i.(*memFile).name,
 						memFile:         obj.i.(*memFile),
 						readWriteSeeker: NewReadWriteSeekerWithData(obj.i.(*memFile).contents),
+						opts:            opts,
 					}
 					return vfsFile, nil
 				}
@@ -68,6 +70,7 @@ func (fs *FileSystem) NewFile(volume, absFilePath string) (vfs.File, error) {
 	// validateAbsFile path will throw an error if there was a trailing slash, hence not calling path.Clean()
 	file := &File{
 		name: path.Base(absFilePath),
+		opts: opts,
 	}
 
 	memFile := newMemFile(file, location.(*Location))
diff --git a/backend/mem/location.go b/backend/mem/location.go
index c22fb14..ea9d553 100644
--- a/backend/mem/location.go
+++ b/backend/mem/location.go
@@ -147,7 +147,7 @@ func (l *Location) FileSystem() vfs.FileSystem {
 }
 
 // NewFile creates a vfs.File given its relative path and tags it onto "l's" path
-func (l *Location) NewFile(relFilePath string) (vfs.File, error) {
+func (l *Location) NewFile(relFilePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	if relFilePath == "" {
 		return nil, errors.New("cannot use empty name for file")
 	}
@@ -178,6 +178,7 @@ func (l *Location) NewFile(relFilePath string) (vfs.File, error) {
 
 	file := &File{
 		name: path.Base(nameStr),
+		opts: opts,
 	}
 	newLoc := *l
 	newLoc.name = relativeLocationPath
diff --git a/backend/os/file.go b/backend/os/file.go
index 91985b9..0e38b63 100644
--- a/backend/os/file.go
+++ b/backend/os/file.go
@@ -7,6 +7,7 @@ import (
 	"os"
 	"path"
 	"path/filepath"
+	"runtime"
 	"time"
 
 	"github.com/c2fo/vfs/v6"
@@ -22,8 +23,10 @@ type opener func(filePath string) (*os.File, error)
 // File implements vfs.File interface for os fs.
 type File struct {
 	file        *os.File
+	volume      string
 	name        string
 	filesystem  *FileSystem
+	opts        []options.NewFileOption
 	cursorPos   int64
 	tempFile    *os.File
 	useTempFile bool
@@ -34,7 +37,7 @@ type File struct {
 
 // Delete unlinks the file returning any error or nil.
 func (f *File) Delete(_ ...options.DeleteOption) error {
-	err := os.Remove(f.Path())
+	err := os.Remove(osFilePath(f))
 	if err == nil {
 		f.file = nil
 	}
@@ -43,7 +46,7 @@ func (f *File) Delete(_ ...options.DeleteOption) error {
 
 // LastModified returns the timestamp of the file's mtime or error, if any.
 func (f *File) LastModified() (*time.Time, error) {
-	stats, err := os.Stat(f.Path())
+	stats, err := os.Stat(osFilePath(f))
 	if err != nil {
 		return nil, err
 	}
@@ -66,12 +69,12 @@ func (f *File) Name() string {
 //
 //	someFile.Location().Path()
 func (f *File) Path() string {
-	return filepath.Join(f.Location().Path(), f.Name())
+	return path.Join(f.Location().Path(), f.Name())
 }
 
 // Size returns the size (in bytes) of the File or any error.
 func (f *File) Size() (uint64, error) {
-	stats, err := os.Stat(f.Path())
+	stats, err := os.Stat(osFilePath(f))
 	if err != nil {
 		return 0, err
 	}
@@ -181,7 +184,7 @@ func (f *File) Seek(offset int64, whence int) (int64, error) {
 
 // Exists true if the file exists on the file system, otherwise false, and an error, if any.
 func (f *File) Exists() (bool, error) {
-	_, err := os.Stat(f.Path())
+	_, err := os.Stat(osFilePath(f))
 	if err != nil {
 		// file does not exist
 		if os.IsNotExist(err) {
@@ -217,19 +220,22 @@ func (f *File) Write(p []byte) (n int, err error) {
 func (f *File) Location() vfs.Location {
 	return &Location{
 		fileSystem: f.filesystem,
+		volume:     f.volume,
 		name:       utils.EnsureTrailingSlash(path.Dir(f.name)),
 	}
 }
 
 // MoveToFile move a file. It accepts a target vfs.File and returns an error, if any.
 func (f *File) MoveToFile(file vfs.File) error {
-	// validate seek is at 0,0 before doing copy
-	if err := backend.ValidateCopySeekPosition(f); err != nil {
-		return err
+	if f.file != nil {
+		// validate seek is at 0,0 before doing copy
+		if err := backend.ValidateCopySeekPosition(f); err != nil {
+			return err
+		}
 	}
 	// handle native os move/rename
 	if file.Location().FileSystem().Scheme() == Scheme {
-		return safeOsRename(f.Path(), file.Path())
+		return safeOsRename(osFilePath(f), osFilePath(file))
 	}
 
 	// do copy/delete move for non-native os moves
@@ -245,7 +251,7 @@ func safeOsRename(srcName, dstName string) error {
 	err := os.Rename(srcName, dstName)
 	if err != nil {
 		e, ok := err.(*os.LinkError)
-		if ok && e.Err.Error() == osCrossDeviceLinkError {
+		if ok && (e.Err.Error() == osCrossDeviceLinkError || (runtime.GOOS == "windows" && e.Err.Error() == "Access is denied.")) {
 			// do cross-device renaming
 			if err := osCopy(srcName, dstName); err != nil {
 				return err
@@ -306,9 +312,11 @@ func (f *File) MoveToLocation(location vfs.Location) (vfs.File, error) {
 
 // CopyToFile copies the file to a new File.  It accepts a vfs.File and returns an error, if any.
 func (f *File) CopyToFile(file vfs.File) error {
-	// validate seek is at 0,0 before doing copy
-	if err := backend.ValidateCopySeekPosition(f); err != nil {
-		return err
+	if f.file != nil {
+		// validate seek is at 0,0 before doing copy
+		if err := backend.ValidateCopySeekPosition(f); err != nil {
+			return err
+		}
 	}
 	_, err := f.copyWithName(file.Name(), file.Location())
 	return err
@@ -317,9 +325,11 @@ func (f *File) CopyToFile(file vfs.File) error {
 // CopyToLocation copies existing File to new Location with the same name.
 // It accepts a vfs.Location and returns a vfs.File and error, if any.
 func (f *File) CopyToLocation(location vfs.Location) (vfs.File, error) {
-	// validate seek is at 0,0 before doing copy
-	if err := backend.ValidateCopySeekPosition(f); err != nil {
-		return nil, err
+	if f.file != nil {
+		// validate seek is at 0,0 before doing copy
+		if err := backend.ValidateCopySeekPosition(f); err != nil {
+			return nil, err
+		}
 	}
 	return f.copyWithName(f.Name(), location)
 }
@@ -351,7 +361,7 @@ func (f *File) Touch() error {
 		return f.Close()
 	}
 	now := time.Now()
-	return os.Chtimes(f.Path(), now, now)
+	return os.Chtimes(osFilePath(f), now, now)
 }
 
 func (f *File) copyWithName(name string, location vfs.Location) (vfs.File, error) {
@@ -386,7 +396,7 @@ func (f *File) openFile() (*os.File, error) {
 		openFunc = f.fileOpener
 	}
 
-	file, err := openFunc(f.Path())
+	file, err := openFunc(osFilePath(f))
 	if err != nil {
 		return nil, err
 	}
@@ -398,7 +408,7 @@ func (f *File) openFile() (*os.File, error) {
 func openOSFile(filePath string) (*os.File, error) {
 	// Ensure the path exists before opening the file, NoOp if dir already exists.
 	var fileMode os.FileMode = 0666
-	if err := os.MkdirAll(path.Dir(filePath), os.ModeDir|0750); err != nil {
+	if err := os.MkdirAll(filepath.Dir(filePath), os.ModeDir|0750); err != nil {
 		return nil, err
 	}
 
@@ -431,7 +441,7 @@ func (f *File) getInternalFile() (*os.File, error) {
 				openFunc = f.fileOpener
 			}
 
-			finalFile, err := openFunc(f.Path())
+			finalFile, err := openFunc(osFilePath(f))
 			if err != nil {
 				return nil, err
 			}
@@ -475,10 +485,11 @@ func (f *File) copyToLocalTempReader() (*os.File, error) {
 			openFunc = f.fileOpener
 		}
 
-		actualFile, err := openFunc(f.Path())
+		actualFile, err := openFunc(osFilePath(f))
 		if err != nil {
 			return nil, err
 		}
+		defer func() { _ = actualFile.Close() }()
 		if _, err := io.Copy(tmpFile, actualFile); err != nil {
 			return nil, err
 		}
@@ -493,3 +504,10 @@ func (f *File) copyToLocalTempReader() (*os.File, error) {
 
 	return tmpFile, nil
 }
+
+func osFilePath(f vfs.File) string {
+	if runtime.GOOS == "windows" {
+		return f.Location().Volume() + filepath.FromSlash(f.Path())
+	}
+	return f.Path()
+}
diff --git a/backend/os/fileSystem.go b/backend/os/fileSystem.go
index 32e819d..b11a2e6 100644
--- a/backend/os/fileSystem.go
+++ b/backend/os/fileSystem.go
@@ -2,9 +2,12 @@ package os
 
 import (
 	"path"
+	"path/filepath"
+	"runtime"
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/backend"
+	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -21,16 +24,32 @@ func (fs *FileSystem) Retry() vfs.Retry {
 }
 
 // NewFile function returns the os implementation of vfs.File.
-func (fs *FileSystem) NewFile(volume, name string) (vfs.File, error) {
+func (fs *FileSystem) NewFile(volume, name string, opts ...options.NewFileOption) (vfs.File, error) {
+	if runtime.GOOS == "windows" && filepath.IsAbs(name) {
+		if v := filepath.VolumeName(name); v != "" {
+			volume = v
+			name = name[len(v):]
+		}
+	}
+
+	name = filepath.ToSlash(name)
 	err := utils.ValidateAbsoluteFilePath(name)
 	if err != nil {
 		return nil, err
 	}
-	return &File{name: name, filesystem: fs}, nil
+	return &File{volume: volume, name: name, filesystem: fs, opts: opts}, nil
 }
 
 // NewLocation function returns the os implementation of vfs.Location.
 func (fs *FileSystem) NewLocation(volume, name string) (vfs.Location, error) {
+	if runtime.GOOS == "windows" && filepath.IsAbs(name) {
+		if v := filepath.VolumeName(name); v != "" {
+			volume = v
+			name = name[len(v):]
+		}
+	}
+
+	name = filepath.ToSlash(name)
 	err := utils.ValidateAbsoluteLocationPath(name)
 	if err != nil {
 		return nil, err
@@ -38,6 +57,7 @@ func (fs *FileSystem) NewLocation(volume, name string) (vfs.Location, error) {
 
 	return &Location{
 		fileSystem: fs,
+		volume:     volume,
 		name:       utils.EnsureTrailingSlash(path.Clean(name)),
 	}, nil
 }
diff --git a/backend/os/location.go b/backend/os/location.go
index 764392c..023f4bf 100644
--- a/backend/os/location.go
+++ b/backend/os/location.go
@@ -6,6 +6,7 @@ import (
 	"path"
 	"path/filepath"
 	"regexp"
+	"runtime"
 	"strings"
 
 	"github.com/c2fo/vfs/v6"
@@ -15,25 +16,27 @@ import (
 
 // Location implements the vfs.Location interface specific to OS fs.
 type Location struct {
+	volume     string
 	name       string
 	fileSystem vfs.FileSystem
 }
 
 // NewFile uses the properties of the calling location to generate a vfs.File (backed by an os.File). A string
 // argument is expected to be a relative path to the location's current path.
-func (l *Location) NewFile(fileName string) (vfs.File, error) {
+func (l *Location) NewFile(fileName string, opts ...options.NewFileOption) (vfs.File, error) {
 	if l == nil {
 		return nil, errors.New("non-nil os.Location pointer is required")
 	}
 	if fileName == "" {
 		return nil, errors.New("non-empty string filePath is required")
 	}
+	fileName = filepath.ToSlash(fileName)
 	err := utils.ValidateRelativeFilePath(fileName)
 	if err != nil {
 		return nil, err
 	}
 	fileName = utils.EnsureLeadingSlash(path.Clean(path.Join(l.name, fileName)))
-	return l.fileSystem.NewFile(l.Volume(), fileName)
+	return l.fileSystem.NewFile(l.Volume(), fileName, opts...)
 }
 
 // DeleteFile deletes the file of the given name at the location. This is meant to be a short cut for instantiating a
@@ -112,7 +115,7 @@ func (l *Location) fileList(testEval fileTest) ([]string, error) {
 
 // Volume returns the volume, if any, of the location. Given "C:\foo\bar" it returns "C:" on Windows. On other platforms it returns "".
 func (l *Location) Volume() string {
-	return filepath.VolumeName(l.name)
+	return l.volume
 }
 
 // Path returns the location path.
@@ -124,7 +127,7 @@ func (l *Location) Path() string {
 // permissions. Will receive false without an error if the location simply doesn't exist. Otherwise could receive
 // false and any errors passed back from the OS.
 func (l *Location) Exists() (bool, error) {
-	_, err := os.Stat(l.Path())
+	_, err := os.Stat(osLocationPath(l))
 	if err != nil {
 		if os.IsNotExist(err) {
 			return false, nil
@@ -155,6 +158,7 @@ func (l *Location) NewLocation(relativePath string) (vfs.Location, error) {
 	// make a copy of the original location first, then ChangeDir, leaving the original location as-is
 	newLocation := &Location{}
 	*newLocation = *l
+	relativePath = filepath.ToSlash(relativePath)
 	err := newLocation.ChangeDir(relativePath)
 	if err != nil {
 		return nil, err
@@ -186,3 +190,10 @@ func (l *Location) ChangeDir(relativePath string) error {
 func (l *Location) FileSystem() vfs.FileSystem {
 	return l.fileSystem
 }
+
+func osLocationPath(l vfs.Location) string {
+	if runtime.GOOS == "windows" {
+		return l.Volume() + filepath.FromSlash(l.Path())
+	}
+	return l.Path()
+}
diff --git a/backend/s3/file.go b/backend/s3/file.go
index f065b88..4592aeb 100644
--- a/backend/s3/file.go
+++ b/backend/s3/file.go
@@ -21,6 +21,7 @@ import (
 	"github.com/c2fo/vfs/v6/mocks"
 	"github.com/c2fo/vfs/v6/options"
 	"github.com/c2fo/vfs/v6/options/delete"
+	"github.com/c2fo/vfs/v6/options/newfile"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -31,6 +32,7 @@ type File struct {
 	fileSystem *FileSystem
 	bucket     string
 	key        string
+	opts       []options.NewFileOption
 
 	// seek-related fields
 	cursorPos  int64
@@ -202,7 +204,7 @@ 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. If DeleteAllVersions option is provided,
+// a DeleteObject call to s3 for the file. If delete.AllVersions 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 {
 	if err := f.Close(); err != nil {
@@ -214,11 +216,11 @@ func (f *File) Delete(opts ...options.DeleteOption) error {
 		return err
 	}
 
-	var deleteAllVersions bool
+	var allVersions bool
 	for _, o := range opts {
 		switch o.(type) {
-		case delete.DeleteAllVersions:
-			deleteAllVersions = true
+		case delete.AllVersions, delete.DeleteAllVersions:
+			allVersions = true
 		default:
 		}
 	}
@@ -231,7 +233,7 @@ func (f *File) Delete(opts ...options.DeleteOption) error {
 		return err
 	}
 
-	if deleteAllVersions {
+	if allVersions {
 		objectVersions, err := f.getAllObjectVersions(client)
 		if err != nil {
 			return err
@@ -569,6 +571,18 @@ func (f *File) getCopyObjectInput(targetFile *File) (*s3.CopyObjectInput, error)
 	isSameAccount := false
 	var ACL string
 
+	// get content type from source
+	var contentType string
+	if targetFile.opts == nil && f.opts != nil {
+		for _, o := range f.opts {
+			switch o := o.(type) {
+			case *newfile.ContentType:
+				contentType = string(*o)
+			default:
+			}
+		}
+	}
+
 	fileOptions := f.Location().FileSystem().(*FileSystem).options
 	targetOptions := targetFile.Location().FileSystem().(*FileSystem).options
 
@@ -605,6 +619,11 @@ func (f *File) getCopyObjectInput(targetFile *File) (*s3.CopyObjectInput, error)
 			SetBucket(targetFile.bucket).
 			SetCopySource(copySourceKey)
 
+		// set content type if it exists
+		if contentType != "" {
+			copyInput.SetContentType(contentType)
+		}
+
 		if f.fileSystem.options != nil && f.fileSystem.options.(Options).DisableServerSideEncryption {
 			copyInput.ServerSideEncryption = nil
 		}
@@ -659,6 +678,14 @@ func uploadInput(f *File) *s3manager.UploadInput {
 		}
 	}
 
+	for _, o := range f.opts {
+		switch o := o.(type) {
+		case *newfile.ContentType:
+			input.ContentType = (*string)(o)
+		default:
+		}
+	}
+
 	return input
 }
 
diff --git a/backend/s3/fileSystem.go b/backend/s3/fileSystem.go
index a5bfb0d..ade41f9 100644
--- a/backend/s3/fileSystem.go
+++ b/backend/s3/fileSystem.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"
 )
 
@@ -30,7 +31,7 @@ func (fs *FileSystem) Retry() vfs.Retry {
 }
 
 // NewFile function returns the s3 implementation of vfs.File.
-func (fs *FileSystem) NewFile(volume, name string) (vfs.File, error) {
+func (fs *FileSystem) NewFile(volume, name string, opts ...options.NewFileOption) (vfs.File, error) {
 	if fs == nil {
 		return nil, errors.New("non-nil s3.FileSystem pointer is required")
 	}
@@ -45,6 +46,7 @@ func (fs *FileSystem) NewFile(volume, name string) (vfs.File, error) {
 		fileSystem: fs,
 		bucket:     utils.RemoveTrailingSlash(volume),
 		key:        path.Clean(name),
+		opts:       opts,
 	}, nil
 }
 
diff --git a/backend/s3/location.go b/backend/s3/location.go
index 730eb56..cbec6f1 100644
--- a/backend/s3/location.go
+++ b/backend/s3/location.go
@@ -123,7 +123,7 @@ func (l *Location) ChangeDir(relativePath string) error {
 
 // NewFile uses the properties of the calling location to generate a vfs.File (backed by an s3.File). The filePath
 // argument is expected to be a relative path to the location's current path.
-func (l *Location) NewFile(filePath string) (vfs.File, error) {
+func (l *Location) NewFile(filePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	if l == nil {
 		return nil, errors.New("non-nil s3.Location pointer is required")
 	}
@@ -138,6 +138,7 @@ func (l *Location) NewFile(filePath string) (vfs.File, error) {
 		fileSystem: l.fileSystem,
 		bucket:     l.bucket,
 		key:        utils.EnsureLeadingSlash(path.Join(l.prefix, filePath)),
+		opts:       opts,
 	}
 	return newFile, nil
 }
diff --git a/backend/s3/options.go b/backend/s3/options.go
index f859a07..356b1a2 100644
--- a/backend/s3/options.go
+++ b/backend/s3/options.go
@@ -8,6 +8,7 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/credentials"
 	"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
+	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
 	"github.com/aws/aws-sdk-go/aws/defaults"
 	"github.com/aws/aws-sdk-go/aws/ec2metadata"
 	"github.com/aws/aws-sdk-go/aws/request"
@@ -22,6 +23,7 @@ type Options struct {
 	SecretAccessKey             string `json:"secretAccessKey,omitempty"`
 	SessionToken                string `json:"sessionToken,omitempty"`
 	Region                      string `json:"region,omitempty"`
+	RoleARN                     string `json:"roleARN,omitempty"`
 	Endpoint                    string `json:"endpoint,omitempty"`
 	ACL                         string `json:"acl,omitempty"`
 	ForcePathStyle              bool   `json:"forcePathStyle,omitempty"`
@@ -76,6 +78,12 @@ func getClient(opt Options) (s3iface.S3API, error) {
 		return nil, err
 	}
 
+	if opt.RoleARN != "" {
+		// Create role credentials
+		creds := stscreds.NewCredentials(s, opt.RoleARN)
+		return s3.New(s, &aws.Config{Credentials: creds}), nil
+	}
+
 	// return client instance
 	return s3.New(s), nil
 }
diff --git a/backend/sftp/file.go b/backend/sftp/file.go
index f25b329..5daedf0 100644
--- a/backend/sftp/file.go
+++ b/backend/sftp/file.go
@@ -18,6 +18,7 @@ type File struct {
 	fileSystem *FileSystem
 	Authority  utils.Authority
 	path       string
+	opts       []options.NewFileOption
 	sftpfile   ReadWriteSeekCloser
 	opener     fileOpener
 	seekCalled bool
@@ -253,7 +254,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(opts ...options.DeleteOption) error {
+func (f *File) Delete(_ ...options.DeleteOption) error {
 	client, err := f.fileSystem.Client(f.Authority)
 	if err != nil {
 		return err
diff --git a/backend/sftp/fileSystem.go b/backend/sftp/fileSystem.go
index e51bf4b..2a85b78 100644
--- a/backend/sftp/fileSystem.go
+++ b/backend/sftp/fileSystem.go
@@ -14,6 +14,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"
 )
 
@@ -40,7 +41,7 @@ func (fs *FileSystem) Retry() vfs.Retry {
 }
 
 // NewFile function returns the SFTP implementation of vfs.File.
-func (fs *FileSystem) NewFile(authority, filePath string) (vfs.File, error) {
+func (fs *FileSystem) NewFile(authority, filePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	if fs == nil {
 		return nil, errors.New("non-nil sftp.FileSystem pointer is required")
 	}
@@ -60,6 +61,7 @@ func (fs *FileSystem) NewFile(authority, filePath string) (vfs.File, error) {
 		fileSystem: fs,
 		Authority:  auth,
 		path:       path.Clean(filePath),
+		opts:       opts,
 	}, nil
 }
 
diff --git a/backend/sftp/location.go b/backend/sftp/location.go
index ebbe162..1200e12 100644
--- a/backend/sftp/location.go
+++ b/backend/sftp/location.go
@@ -173,7 +173,7 @@ func (l *Location) ChangeDir(relativePath string) error {
 
 // NewFile uses the properties of the calling location to generate a vfs.File (backed by an sftp.File). The filePath
 // argument is expected to be a relative path to the location's current path.
-func (l *Location) NewFile(filePath string) (vfs.File, error) {
+func (l *Location) NewFile(filePath string, opts ...options.NewFileOption) (vfs.File, error) {
 	if l == nil {
 		return nil, errors.New("non-nil sftp.Location pointer receiver is required")
 	}
@@ -188,6 +188,7 @@ func (l *Location) NewFile(filePath string) (vfs.File, error) {
 		fileSystem: l.fileSystem,
 		Authority:  l.Authority,
 		path:       utils.EnsureLeadingSlash(path.Join(l.path, filePath)),
+		opts:       opts,
 	}
 	return newFile, nil
 }
diff --git a/backend/sftp/options.go b/backend/sftp/options.go
index c517ad7..a42e0e2 100644
--- a/backend/sftp/options.go
+++ b/backend/sftp/options.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"path"
+	"path/filepath"
 	"runtime"
 	"strconv"
 
@@ -231,7 +231,10 @@ func findHomeSystemKnownHosts(knownHostsFiles []string) ([]string, error) {
 	if err != nil {
 		return nil, err
 	}
-	homeKnownHostsPath := utils.EnsureLeadingSlash(path.Join(home, ".ssh/known_hosts"))
+	homeKnownHostsPath := filepath.Join(home, ".ssh", "known_hosts")
+	if runtime.GOOS != "windows" {
+		homeKnownHostsPath = utils.EnsureLeadingSlash(homeKnownHostsPath)
+	}
 
 	// check file existence first to prevent auto-vivification of file
 	found, err := foundFile(homeKnownHostsPath)
diff --git a/docs/azure.md b/docs/azure.md
index a021645..00656a4 100644
--- a/docs/azure.md
+++ b/docs/azure.md
@@ -68,7 +68,7 @@ found:
        to containers from multiple storage accounts.
 1. The ENV vars `VFS_AZURE_STORAGE_ACCOUNT` and `VFS_AZURE_STORAGE_KEY`, a shared key authenticator is used.  This will
        allow access to any containers owned by the designated storage account.
-1. If none of the above are present, then an anonymous authenticator is created and only publicly accessible blobs
+1. If none of the above are present, then no credentials are used and only publicly accessible blobs
        will be available
 
 ## Usage
@@ -238,22 +238,15 @@ func (a *DefaultClient) Upload(file vfs.File, content io.ReadSeeker) error
 ```
 Upload uploads a new file to Azure Blob Storage
 
-### type DefaultTokenCredentialFactory
+### func DefaultTokenCredentialFactory
 
 ```go
-type DefaultTokenCredentialFactory struct{}
+func DefaultTokenCredentialFactory(tenantID, clientID, clientSecret string) (azblob.TokenCredential, error)
 ```
 
-DefaultTokenCredentialFactory knows how to make azblob.TokenCredential structs
+DefaultTokenCredentialFactory knows how to make azcore.TokenCredential structs
 for OAuth authentication
 
-#### func (*DefaultTokenCredentialFactory) New
-
-```go
-func (f *DefaultTokenCredentialFactory) New(tenantID, clientID, clientSecret, azureEnvName string) (azblob.TokenCredential, error)
-```
-New creates a new azblob.TokenCredential struct
-
 ### type File
 
 ```go
@@ -295,7 +288,7 @@ be done to the new file.
 ```go
 func (f *File) Delete(opts ...options.DeleteOption) error
 ```
-Deletes the file using Azure's delete blob api. If opts is of type DeleteAllVersions, after deleting the blob, each version of the blob is deleted using Azure's delete api. NOTE that if soft deletion is enabled for the blobs, each version will be marked as deleted and will get permanently deleted by Azure as per the soft deletion policy. Returns any error returned by the API.
+Deletes the file using Azure's delete blob api. If opts is of type delete.AllVersions, after deleting the blob, each version of the blob is deleted using Azure's delete api. NOTE that if soft deletion is enabled for the blobs, each version will be marked as deleted and will get permanently deleted by Azure as per the soft deletion policy. Returns any error returned by the API.
 
 #### func (*File) Exists
 
@@ -449,7 +442,7 @@ Name returns "azure"
 #### func (*FileSystem) NewFile
 
 ```go
-func (fs *FileSystem) NewFile(volume, absFilePath string) (vfs.File, error)
+func (fs *FileSystem) NewFile(volume, absFilePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile returns the azure implementation of vfs.File
 
@@ -558,7 +551,7 @@ ListByRegex returns a list of base names that match the given regular expression
 #### func (*Location) NewFile
 
 ```go
-func (l *Location) NewFile(relFilePath string) (vfs.File, error)
+func (l *Location) NewFile(relFilePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile returns a new file instance at the given path, relative to the current
 location.
@@ -664,67 +657,15 @@ func (a *MockAzureClient) Upload(file vfs.File, content io.ReadSeeker) error
 ```
 Upload returns the value of ExpectedError
 
-### type MockStorageError
-
-```go
-type MockStorageError struct {
-	azblob.ResponseError
-}
-```
-
-MockStorageError is a mock for the azblob.StorageError interface
-
-#### func (MockStorageError) Error
+### func MockTokenCredentialFactory
 
 ```go
-func (mse MockStorageError) Error() string
-```
-Error returns empty string
-
-#### func (MockStorageError) Response
-
-```go
-func (mse MockStorageError) Response() *http.Response
-```
-Response returns nil
-
-#### func (MockStorageError) ServiceCode
-
-```go
-func (mse MockStorageError) ServiceCode() azblob.ServiceCodeType
-```
-ServiceCode always returns "BlobNotFound" to simulate the not found condition
-
-#### func (MockStorageError) Temporary
-
-```go
-func (mse MockStorageError) Temporary() bool
-```
-Temporary returns nil
-
-#### func (MockStorageError) Timeout
-
-```go
-func (mse MockStorageError) Timeout() bool
-```
-Timeout returns nil
-
-### type MockTokenCredentialFactory
-
-```go
-type MockTokenCredentialFactory struct{}
+func MockTokenCredentialFactory(_, _, _ string) (azblob.TokenCredential, error)
 ```
 
 MockTokenCredentialFactory knows how to create a "do-nothing" credential used
 for unit testing
 
-#### func (*MockTokenCredentialFactory) New
-
-```go
-func (f *MockTokenCredentialFactory) New(tenantID, clientID, clientSecret, azureEnvName string) (azblob.TokenCredential, error)
-```
-New creates a new azblob.TokenCredential struct
-
 ### type Options
 
 ```go
@@ -749,10 +690,6 @@ type Options struct {
 	// based authentication.
 	ClientSecret string
 
-	// AzureEnvName holds the name for the Azure environment.  This field is used for OAuth token
-	// based authentication.
-	AzureEnvName string
-
 	// RetryFunc holds the retry function
 	RetryFunc vfs.Retry
 }
@@ -779,26 +716,22 @@ variables.
 #### func (*Options) Credential
 
 ```go
-func (o *Options) Credential() (azblob.Credential, error)
+func (o *Options) Credential() (azcore.TokenCredential, error)
 ```
-Credential returns an azblob.Credential struct based on how options are
+Credential returns an azcore.TokenCredential interface based on how options are
 configured. Options are checked and evaluated in the following order:
 
-    1. If TenantID, ClientID, and ClientSecret are non-empty, return azblob.TokenCredential.  This form of authentication
+    1. If TenantID, ClientID, and ClientSecret are non-empty, return azcore.TokenCredential.  This form of authentication
        is used with service accounts and can be used to access containers across multiple storage accounts.
     2. If AccountName, and AccountKey are non-empty, return azblob.SharedKeyCredential.  This form or authentication
        is used with storage accounts and only provides access to a single storage account.
-    3. Returns an anonymous credential.  This allows access only to public blobs.
+    3. Returns a nil credential.  This allows access only to public blobs.
 
-### type TokenCredentialFactory
+### func TokenCredentialFactory
 
 ```go
-type TokenCredentialFactory interface {
-	// New creates a new azblob.TokenCredential struct
-	New(tenantID, clientID, clientSecret, azureEnvName string) (azblob.TokenCredential, error)
-}
+type TokenCredentialFactory func(tenantID, clientID, clientSecret string) (azcore.TokenCredential, error)
 ```
 
-TokenCredentialFactory is an interface that provides a single factory method to
-create azure.TokenCredentials. This interface is provided to allow for mocking
+TokenCredentialFactory creates azure.TokenCredentials. This function is provided to allow for mocking
 in unit tests.
diff --git a/docs/delete_options.md b/docs/delete_options.md
index ec62600..5a6b99f 100644
--- a/docs/delete_options.md
+++ b/docs/delete_options.md
@@ -5,7 +5,7 @@
 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.
+Currently, we have delete.AllVersions 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
@@ -21,7 +21,7 @@ Delete file using file.delete():
     func DeleteFile() error {
         file, err := fs.NewFile(bucketName, fileName)
         ...
-        err = file.Delete(delete.WithDeleteAllVersions())
+        err = file.Delete(delete.WithAllVersions())
         ...
     }
 ```
@@ -37,7 +37,7 @@ Delete file using location.delete():
     )
 
     func DeleteFileUsingLocation() error {
-        err = location.DeleteFile("filename.txt", delete.WithDeleteAllVersions())
+        err = location.DeleteFile("filename.txt", delete.WithAllVersions())
         ...
     }
 ```
diff --git a/docs/ftp.md b/docs/ftp.md
index d339f5c..6076875 100644
--- a/docs/ftp.md
+++ b/docs/ftp.md
@@ -381,7 +381,7 @@ Name returns "Secure File Transfer Protocol"
 #### func (*FileSystem) NewFile
 
 ```go
-func (fs *FileSystem) NewFile(authority, filePath string) (vfs.File, error)
+func (fs *FileSystem) NewFile(authority, filePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile function returns the FTP implementation of vfs.File.
 
@@ -498,7 +498,7 @@ considerations of List() apply here as well.
 #### func (*Location) NewFile
 
 ```go
-func (l *Location) NewFile(filePath string) (vfs.File, error)
+func (l *Location) NewFile(filePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile uses the properties of the calling location to generate a vfs.File
 (backed by an ftp.File). The filePath argument is expected to be a relative path
diff --git a/docs/gs.md b/docs/gs.md
index 3b2d615..42e9dd2 100644
--- a/docs/gs.md
+++ b/docs/gs.md
@@ -130,7 +130,7 @@ to the new file.
 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 GCS for the file. If opts is of type DeleteAllVersions, DeleteObject call is made to
+then makes a DeleteObject call to GCS for the file. If opts is of type delete.AllVersions, DeleteObject call is made to
 GCS for each version of the file. Returns any error returned by the API.
 
 #### func (*File) Exists
@@ -269,7 +269,7 @@ Name returns "Google Cloud Storage"
 #### func (*FileSystem) NewFile
 
 ```go
-func (fs *FileSystem) NewFile(volume string, name string) (vfs.File, error)
+func (fs *FileSystem) NewFile(volume string, name string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile function returns the gcs implementation of [vfs.File](../README.md#type-file).
 
@@ -374,7 +374,7 @@ provided regular expression.
 #### func (*Location) NewFile
 
 ```go
-func (l *Location) NewFile(filePath string) (vfs.File, error)
+func (l *Location) NewFile(filePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile returns a new file instance at the given path, relative to the current
 location.
diff --git a/docs/mem.md b/docs/mem.md
index a644e82..c46ccd4 100644
--- a/docs/mem.md
+++ b/docs/mem.md
@@ -219,7 +219,7 @@ Name returns the name of the underlying FileSystem
 #### func (*FileSystem) NewFile
 
 ```go
-func (fs *FileSystem) NewFile(volume string, absFilePath string) (vfs.File, error)
+func (fs *FileSystem) NewFile(volume string, absFilePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile function returns the in-memory implementation of vfs.File. Since this is
 inside FileSystem, we assume that the caller knows that the CWD is the root. If
@@ -322,7 +322,7 @@ empty slice upon nothing found
 #### func (*Location) NewFile
 
 ```go
-func (l *Location) NewFile(relFilePath string) (vfs.File, error)
+func (l *Location) NewFile(relFilePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile creates a vfs.File given its relative path and tags it onto "l's" path
 
diff --git a/docs/options.md b/docs/options.md
index 3f7c9b7..ac6115f 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -5,7 +5,7 @@
 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.
+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 [delete.AllVersions](./delete_options.md#DeleteAllVersions) option.
 
 ## Development
 
@@ -32,8 +32,8 @@ Now, in each implementation of file.Delete(... options.DeleteOptions), implement
     func (f *File) Delete(opts ...options.DeleteOption) error {
         for _, o := range opts {
             switch o.(type) {
-            case delete.DeleteAllVersions:
-                deleteAllVersions = true
+            case delete.AllVersions:
+                allVersions = true
 			case delete.MyTakeBackupDeleteOption:
                 // do something to take backup
             default:
diff --git a/docs/os.md b/docs/os.md
index 81972ac..2cabfa6 100644
--- a/docs/os.md
+++ b/docs/os.md
@@ -205,7 +205,7 @@ Name returns "os"
 #### func (*FileSystem) NewFile
 
 ```go
-func (fs *FileSystem) NewFile(volume string, name string) (vfs.File, error)
+func (fs *FileSystem) NewFile(volume string, name string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile function returns the os implementation of [vfs.File](../README.md#type-file).
 
@@ -294,7 +294,7 @@ of the location.
 #### func (*Location) NewFile
 
 ```go
-func (l *Location) NewFile(fileName string) (vfs.File, error)
+func (l *Location) NewFile(fileName string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile uses the properties of the calling location to generate a  [vfs.File](../README.md#type-file)
 (backed by an [os.File](#type-file)). A string argument is expected to be a relative path to
diff --git a/docs/s3.md b/docs/s3.md
index f327374..8d11767 100644
--- a/docs/s3.md
+++ b/docs/s3.md
@@ -90,6 +90,9 @@ found:
 1. RemoteCredProvider - default remote endpoints such as EC2 or ECS IAM Roles
 1. EC2RoleProvider - credentials from the EC2 service, and keeps track if those credentials are expired
 
+If the 'RoleARN' option is set for the filesystem then the backend will attempt to assume the given role granting the permissions associated with it. For more information regarding role based credentials:
+https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html
+
 See the following for more auth info:
 https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html and
 https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html
@@ -147,7 +150,7 @@ the new file.
 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. If opts is of type DeleteAllVersions, DeleteObject call is made to
+then makes a DeleteObject call to s3 for the file. If opts is of type delete.AllVersions, DeleteObject call is made to
 s3 for each version of the file. Returns any error returned by
 the API.
 
@@ -296,7 +299,7 @@ Name returns "AWS S3"
 #### func (*FileSystem) NewFile
 
 ```go
-func (fs *FileSystem) NewFile(volume string, name string) (vfs.File, error)
+func (fs *FileSystem) NewFile(volume string, name string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile function returns the s3 implementation of [vfs.File](../README.md#type-file).
 
@@ -402,7 +405,7 @@ considerations of [List()](#func-location-list) apply here as well.
 #### func (*Location) NewFile
 
 ```go
-func (l *Location) NewFile(filePath string) (vfs.File, error)
+func (l *Location) NewFile(filePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile uses the properties of the calling location to generate a vfs.File
 (backed by an [s3.File](#type-file)). The filePath argument is expected to be a relative path
@@ -451,11 +454,20 @@ Volume returns the bucket the location is contained in.
 
 ```go
 type Options struct {
-	AccessKeyID     string `json:"accessKeyId,omitempty"`
-	SecretAccessKey string `json:"secretAccessKey,omitempty"`
-	SessionToken    string `json:"sessionToken,omitempty"`
-	Region          string `json:"region,omitempty"`
-	Endpoint        string `json:"endpoint,omitempty"`
+	AccessKeyID                 string `json:"accessKeyId,omitempty"`
+	SecretAccessKey             string `json:"secretAccessKey,omitempty"`
+	SessionToken                string `json:"sessionToken,omitempty"`
+	Region                      string `json:"region,omitempty"`
+	RoleARN                     string `json:"roleARN,omitempty"`
+	Endpoint                    string `json:"endpoint,omitempty"`
+	ACL                         string `json:"acl,omitempty"`
+	ForcePathStyle              bool   `json:"forcePathStyle,omitempty"`
+	DisableServerSideEncryption bool   `json:"disableServerSideEncryption,omitempty"`
+	Retry                       request.Retryer
+	MaxRetries                  int
+	FileBufferSize              int   
+	DownloadPartitionSize       int64 
+	UploadPartitionSize         int64 
 }
 ```
 
diff --git a/docs/sftp.md b/docs/sftp.md
index f9c4985..9e56ca5 100644
--- a/docs/sftp.md
+++ b/docs/sftp.md
@@ -411,7 +411,7 @@ Name returns "Secure File Transfer Protocol"
 #### func (*FileSystem) NewFile
 
 ```go
-func (fs *FileSystem) NewFile(authority, filePath string) (vfs.File, error)
+func (fs *FileSystem) NewFile(authority, filePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile function returns the SFTP implementation of vfs.File.
 
@@ -522,7 +522,7 @@ considerations of [List()](#func-location-list) apply here as well.
 #### func (*Location) NewFile
 
 ```go
-func (l *Location) NewFile(filePath string) (vfs.File, error)
+func (l *Location) NewFile(filePath string, opts ...options.NewFileOption) (vfs.File, error)
 ```
 NewFile uses the properties of the calling location to generate a [vfs.File](../README.md#type-file)
 (backed by an sftp.File). The filePath argument is expected to be a relative
diff --git a/go.mod b/go.mod
index 4da9ae3..a46c368 100644
--- a/go.mod
+++ b/go.mod
@@ -1,95 +1,93 @@
 module github.com/c2fo/vfs/v6
 
-go 1.22.7
+go 1.22.9
 
 toolchain go1.23.0
 
 require (
-	cloud.google.com/go/storage v1.48.0
-	github.com/Azure/azure-pipeline-go v0.2.3
-	github.com/Azure/azure-storage-blob-go v0.15.0
-	github.com/Azure/go-autorest/autorest v0.11.29
-	github.com/Azure/go-autorest/autorest/adal v0.9.24
+	cloud.google.com/go/storage v1.49.0
+	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
+	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
+	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0
 	github.com/aws/aws-sdk-go v1.55.5
 	github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349
 	github.com/fatih/color v1.18.0
-	github.com/fsouza/fake-gcs-server v1.50.2
+	github.com/fsouza/fake-gcs-server v1.52.0
 	github.com/jlaffaye/ftp v0.2.1-0.20240214224549-4edb16bfcd0f
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/pkg/sftp v1.13.7
 	github.com/stretchr/testify v1.10.0
-	golang.org/x/crypto v0.31.0
-	golang.org/x/net v0.32.0
-	google.golang.org/api v0.210.0
+	golang.org/x/crypto v0.32.0
+	golang.org/x/net v0.34.0
+	google.golang.org/api v0.215.0
 )
 
 require (
 	cel.dev/expr v0.19.1 // indirect
 	cloud.google.com/go v0.116.0 // indirect
-	cloud.google.com/go/auth v0.12.0 // indirect
+	cloud.google.com/go/auth v0.13.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
-	cloud.google.com/go/compute/metadata v0.5.2 // indirect
+	cloud.google.com/go/compute/metadata v0.6.0 // indirect
 	cloud.google.com/go/iam v1.3.0 // indirect
-	cloud.google.com/go/monitoring v1.22.0 // indirect
+	cloud.google.com/go/monitoring v1.22.1 // indirect
 	cloud.google.com/go/pubsub v1.45.3 // indirect
-	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
-	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
-	github.com/Azure/go-autorest/logger v0.2.1 // indirect
-	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
+	github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
+	github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect
 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect
-	github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
-	github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
+	github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
-	github.com/envoyproxy/go-control-plane v0.13.1 // indirect
+	github.com/envoyproxy/go-control-plane/envoy v1.32.2 // indirect
+	github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 // indirect
 	github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/go-errors/errors v1.5.1 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
-	github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
-	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
+	github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/google/renameio/v2 v2.0.0 // indirect
 	github.com/google/s2a-go v0.1.8 // indirect
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
-	github.com/googleapis/gax-go/v2 v2.14.0 // indirect
+	github.com/googleapis/gax-go/v2 v2.14.1 // indirect
 	github.com/gorilla/handlers v1.5.2 // indirect
 	github.com/gorilla/mux v1.8.1 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-multierror v1.1.1 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/kr/fs v0.1.0 // indirect
+	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-ieproxy v0.0.12 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
 	github.com/pkg/xattr v0.4.10 // indirect
 	github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/rogpeppe/go-internal v1.12.0 // indirect
 	github.com/stretchr/objx v0.5.2 // indirect
 	go.opencensus.io v0.24.0 // indirect
-	go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
-	go.opentelemetry.io/otel v1.32.0 // indirect
-	go.opentelemetry.io/otel/metric v1.32.0 // indirect
-	go.opentelemetry.io/otel/sdk v1.32.0 // indirect
-	go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
-	go.opentelemetry.io/otel/trace v1.32.0 // indirect
-	golang.org/x/oauth2 v0.24.0 // indirect
+	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+	go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
+	go.opentelemetry.io/otel v1.33.0 // indirect
+	go.opentelemetry.io/otel/metric v1.33.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.33.0 // indirect
+	go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect
+	go.opentelemetry.io/otel/trace v1.33.0 // indirect
+	golang.org/x/oauth2 v0.25.0 // indirect
 	golang.org/x/sync v0.10.0 // indirect
-	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/sys v0.29.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
 	golang.org/x/time v0.8.0 // indirect
-	google.golang.org/genproto v0.0.0-20241206012308-a4fef0638583 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect
+	google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect
 	google.golang.org/grpc v1.68.1 // indirect
 	google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 // indirect
-	google.golang.org/protobuf v1.35.2 // indirect
+	google.golang.org/protobuf v1.36.2 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index bb3ed3b..59d5539 100644
--- a/go.sum
+++ b/go.sum
@@ -3,49 +3,44 @@ cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
 cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
-cloud.google.com/go/auth v0.12.0 h1:ARAD8r0lkiHw2go7kEnmviF6TOYhzLM+yDGcDt9mP68=
-cloud.google.com/go/auth v0.12.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
+cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
+cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
 cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
 cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
-cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
-cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
+cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
+cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
 cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU=
 cloud.google.com/go/iam v1.3.0/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
 cloud.google.com/go/kms v1.20.1 h1:og29Wv59uf2FVaZlesaiDAqHFzHaoUyHI3HYp9VUHVg=
 cloud.google.com/go/kms v1.20.1/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc=
 cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
 cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
-cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng=
-cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
-cloud.google.com/go/monitoring v1.22.0 h1:mQ0040B7dpuRq1+4YiQD43M2vW9HgoVxY98xhqGT+YI=
-cloud.google.com/go/monitoring v1.22.0/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
+cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
+cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
+cloud.google.com/go/monitoring v1.22.1 h1:KQbnAC4IAH+5x3iWuPZT5iN9VXqKMzzOgqcYB6fqPDE=
+cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY=
 cloud.google.com/go/pubsub v1.45.3 h1:prYj8EEAAAwkp6WNoGTE4ahe0DgHoyJd5Pbop931zow=
 cloud.google.com/go/pubsub v1.45.3/go.mod h1:cGyloK/hXC4at7smAtxFnXprKEFTqmMXNNd9w+bd94Q=
-cloud.google.com/go/storage v1.48.0 h1:FhBDHACbVtdPx7S/AbcKujPWiHvfO6F8OXGgCEbB2+o=
-cloud.google.com/go/storage v1.48.0/go.mod h1:aFoDYNMAjv67lp+xcuZqjUKv/ctmplzQ3wJgodA7b+M=
+cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw=
+cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU=
 cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI=
 cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io=
-github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
-github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
-github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
-github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
-github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
-github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
-github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw=
-github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
-github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
-github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
-github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4=
-github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8=
-github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
-github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
-github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
-github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
-github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
-github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
-github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
-github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
-github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
+github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 h1:mlmW46Q0B79I+Aj4azKC6xDMFN9a9SyZWESlGWYXbFs=
+github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0/go.mod h1:PXe2h+LKcWTX9afWdZoHyODqR4fBa5boUM/8uJfZ0Jo=
+github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
+github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
@@ -58,17 +53,17 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp
 github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
 github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
-github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI=
-github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q=
+github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
 github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
 github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
@@ -89,8 +84,12 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE=
-github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw=
+github.com/envoyproxy/go-control-plane v0.13.3 h1:F2vYcSF8iRNhfvhZQRZ5Dvuyu0TpXazE9+h53TzkvA4=
+github.com/envoyproxy/go-control-plane v0.13.3/go.mod h1:uhvHSBAMSvy2Y+CuAYfByIRH19zcdir1rgmMzKUo3eA=
+github.com/envoyproxy/go-control-plane/envoy v1.32.2 h1:zidqwmijfcbyKqVxjQDFx042PgX+p9U+/fu/f9VtSk8=
+github.com/envoyproxy/go-control-plane/envoy v1.32.2/go.mod h1:eR2SOX2IedqlPvmiKjUH7Wu//S602JKI7HPC/L3SRq8=
+github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
+github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
 github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
@@ -98,9 +97,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
 github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
-github.com/fsouza/fake-gcs-server v1.50.2 h1:ulrS1pavCOCbMZfN5ZPgBRMFWclON9xDsuLBniXtQoE=
-github.com/fsouza/fake-gcs-server v1.50.2/go.mod h1:VU6Zgei4647KuT4XER8WHv5Hcj2NIySndyG8gfvwckA=
+github.com/fsouza/fake-gcs-server v1.52.0 h1:ps23VAKR0pKu+QsdMUo25mtDXp6KpKxksIRkbHNBPug=
+github.com/fsouza/fake-gcs-server v1.52.0/go.mod h1:tmfEOHhUOdkk243WkWUPC1MuHEaGdpJrslDijp5nZRs=
 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
 github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
@@ -114,19 +112,17 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
-github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
-github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
-github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
-github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
-github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
+github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
 github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
 github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
-github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -154,13 +150,12 @@ github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxeh
 github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
 github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
 github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
-github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o=
-github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk=
+github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
+github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
 github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
 github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@@ -178,35 +173,33 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
+github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
 github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
 github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
-github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
-github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
+github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
-github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=
-github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
-github.com/minio/minio-go/v7 v7.0.78 h1:LqW2zy52fxnI4gg8C2oZviTaKHcBV36scS+RzJnxUFs=
-github.com/minio/minio-go/v7 v7.0.78/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
+github.com/minio/minio-go/v7 v7.0.83 h1:W4Kokksvlz3OKf3OqIlzDNKd4MERlC2oN8YptwJ0+GA=
+github.com/minio/minio-go/v7 v7.0.83/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
 github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
 github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
 github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
@@ -216,8 +209,10 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
-github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
+github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
 github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
 github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -228,7 +223,6 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -236,34 +230,32 @@ go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw=
 go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8=
-go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 h1:qtFISDHKolvIxzSs0gIaiPUPR0Cucb0F2coHC7ZLdps=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0/go.mod h1:Y+Pop1Q6hCOnETWTW4NROK/q1hv50hM7yDaUTjG8lp8=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
-go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
-go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAmGRgoYcnXkzgQPhP4=
+go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
+go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
+go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
 go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
-go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
-go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
-go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
-go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
-go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
-go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
-go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
-go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
+go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
+go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
+go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
+go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
+go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU=
+go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q=
+go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
+go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -276,24 +268,21 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
-golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
-golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
+golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -304,12 +293,10 @@ golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -317,23 +304,22 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
+golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
+golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
@@ -352,19 +338,19 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk=
-google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs=
+google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0=
+google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20241206012308-a4fef0638583 h1:pjPnE7Rv3PAwHISLRJhA3HQTnM2uu5qcnroxTkRb5G8=
-google.golang.org/genproto v0.0.0-20241206012308-a4fef0638583/go.mod h1:dW27OyXi0Ph+N43jeCWMFC86aTT5VgdeQtOSf0Hehdw=
-google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583 h1:v+j+5gpj0FopU0KKLDGfDo9ZRRpKdi5UBrCP0f76kuY=
-google.golang.org/genproto/googleapis/api v0.0.0-20241206012308-a4fef0638583/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
+google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
+google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
+google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d h1:H8tOf8XM88HvKqLTxe755haY6r1fqqzLbEnfrmLXlSA=
+google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d/go.mod h1:2v7Z7gP2ZUOGsaFyxATQSRoBnKygqVq2Cwnvom7QiqY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@@ -383,8 +369,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
-google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
+google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/mocks/File.go b/mocks/File.go
index 59a7490..38d9b34 100644
--- a/mocks/File.go
+++ b/mocks/File.go
@@ -173,11 +173,11 @@ func (_c *File_CopyToLocation_Call) RunAndReturn(run func(vfs.Location) (vfs.Fil
 	return _c
 }
 
-// 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]
+// Delete provides a mock function with given fields: opts
+func (_m *File) Delete(opts ...options.DeleteOption) error {
+	_va := make([]interface{}, len(opts))
+	for _i := range opts {
+		_va[_i] = opts[_i]
 	}
 	var _ca []interface{}
 	_ca = append(_ca, _va...)
@@ -189,7 +189,7 @@ func (_m *File) Delete(deleteOpts ...options.DeleteOption) error {
 
 	var r0 error
 	if rf, ok := ret.Get(0).(func(...options.DeleteOption) error); ok {
-		r0 = rf(deleteOpts...)
+		r0 = rf(opts...)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -203,13 +203,13 @@ type File_Delete_Call struct {
 }
 
 // Delete is a helper method to define mock.On call
-//   - deleteOpts ...options.DeleteOption
-func (_e *File_Expecter) Delete(deleteOpts ...interface{}) *File_Delete_Call {
+//   - opts ...options.DeleteOption
+func (_e *File_Expecter) Delete(opts ...interface{}) *File_Delete_Call {
 	return &File_Delete_Call{Call: _e.mock.On("Delete",
-		append([]interface{}{}, deleteOpts...)...)}
+		append([]interface{}{}, opts...)...)}
 }
 
-func (_c *File_Delete_Call) Run(run func(deleteOpts ...options.DeleteOption)) *File_Delete_Call {
+func (_c *File_Delete_Call) Run(run func(opts ...options.DeleteOption)) *File_Delete_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		variadicArgs := make([]options.DeleteOption, len(args)-0)
 		for i, a := range args[0:] {
diff --git a/mocks/FileSystem.go b/mocks/FileSystem.go
index fee875f..c60061b 100644
--- a/mocks/FileSystem.go
+++ b/mocks/FileSystem.go
@@ -3,8 +3,10 @@
 package mocks
 
 import (
-	vfs "github.com/c2fo/vfs/v6"
+	options "github.com/c2fo/vfs/v6/options"
 	mock "github.com/stretchr/testify/mock"
+
+	vfs "github.com/c2fo/vfs/v6"
 )
 
 // FileSystem is an autogenerated mock type for the FileSystem type
@@ -65,9 +67,16 @@ func (_c *FileSystem_Name_Call) RunAndReturn(run func() string) *FileSystem_Name
 	return _c
 }
 
-// NewFile provides a mock function with given fields: volume, absFilePath
-func (_m *FileSystem) NewFile(volume string, absFilePath string) (vfs.File, error) {
-	ret := _m.Called(volume, absFilePath)
+// NewFile provides a mock function with given fields: volume, absFilePath, opts
+func (_m *FileSystem) NewFile(volume string, absFilePath string, opts ...options.NewFileOption) (vfs.File, error) {
+	_va := make([]interface{}, len(opts))
+	for _i := range opts {
+		_va[_i] = opts[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, volume, absFilePath)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
 
 	if len(ret) == 0 {
 		panic("no return value specified for NewFile")
@@ -75,19 +84,19 @@ func (_m *FileSystem) NewFile(volume string, absFilePath string) (vfs.File, erro
 
 	var r0 vfs.File
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string, string) (vfs.File, error)); ok {
-		return rf(volume, absFilePath)
+	if rf, ok := ret.Get(0).(func(string, string, ...options.NewFileOption) (vfs.File, error)); ok {
+		return rf(volume, absFilePath, opts...)
 	}
-	if rf, ok := ret.Get(0).(func(string, string) vfs.File); ok {
-		r0 = rf(volume, absFilePath)
+	if rf, ok := ret.Get(0).(func(string, string, ...options.NewFileOption) vfs.File); ok {
+		r0 = rf(volume, absFilePath, opts...)
 	} else {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).(vfs.File)
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(string, string) error); ok {
-		r1 = rf(volume, absFilePath)
+	if rf, ok := ret.Get(1).(func(string, string, ...options.NewFileOption) error); ok {
+		r1 = rf(volume, absFilePath, opts...)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -103,13 +112,21 @@ type FileSystem_NewFile_Call struct {
 // NewFile is a helper method to define mock.On call
 //   - volume string
 //   - absFilePath string
-func (_e *FileSystem_Expecter) NewFile(volume interface{}, absFilePath interface{}) *FileSystem_NewFile_Call {
-	return &FileSystem_NewFile_Call{Call: _e.mock.On("NewFile", volume, absFilePath)}
+//   - opts ...options.NewFileOption
+func (_e *FileSystem_Expecter) NewFile(volume interface{}, absFilePath interface{}, opts ...interface{}) *FileSystem_NewFile_Call {
+	return &FileSystem_NewFile_Call{Call: _e.mock.On("NewFile",
+		append([]interface{}{volume, absFilePath}, opts...)...)}
 }
 
-func (_c *FileSystem_NewFile_Call) Run(run func(volume string, absFilePath string)) *FileSystem_NewFile_Call {
+func (_c *FileSystem_NewFile_Call) Run(run func(volume string, absFilePath string, opts ...options.NewFileOption)) *FileSystem_NewFile_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(string), args[1].(string))
+		variadicArgs := make([]options.NewFileOption, len(args)-2)
+		for i, a := range args[2:] {
+			if a != nil {
+				variadicArgs[i] = a.(options.NewFileOption)
+			}
+		}
+		run(args[0].(string), args[1].(string), variadicArgs...)
 	})
 	return _c
 }
@@ -119,7 +136,7 @@ func (_c *FileSystem_NewFile_Call) Return(_a0 vfs.File, _a1 error) *FileSystem_N
 	return _c
 }
 
-func (_c *FileSystem_NewFile_Call) RunAndReturn(run func(string, string) (vfs.File, error)) *FileSystem_NewFile_Call {
+func (_c *FileSystem_NewFile_Call) RunAndReturn(run func(string, string, ...options.NewFileOption) (vfs.File, error)) *FileSystem_NewFile_Call {
 	_c.Call.Return(run)
 	return _c
 }
diff --git a/mocks/Location.go b/mocks/Location.go
index 1969193..67c9f65 100644
--- a/mocks/Location.go
+++ b/mocks/Location.go
@@ -70,11 +70,11 @@ func (_c *Location_ChangeDir_Call) RunAndReturn(run func(string) error) *Locatio
 	return _c
 }
 
-// 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]
+// DeleteFile provides a mock function with given fields: relFilePath, opts
+func (_m *Location) DeleteFile(relFilePath string, opts ...options.DeleteOption) error {
+	_va := make([]interface{}, len(opts))
+	for _i := range opts {
+		_va[_i] = opts[_i]
 	}
 	var _ca []interface{}
 	_ca = append(_ca, relFilePath)
@@ -87,7 +87,7 @@ func (_m *Location) DeleteFile(relFilePath string, deleteOpts ...options.DeleteO
 
 	var r0 error
 	if rf, ok := ret.Get(0).(func(string, ...options.DeleteOption) error); ok {
-		r0 = rf(relFilePath, deleteOpts...)
+		r0 = rf(relFilePath, opts...)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -102,13 +102,13 @@ type Location_DeleteFile_Call struct {
 
 // DeleteFile is a helper method to define mock.On call
 //   - relFilePath string
-//   - deleteOpts ...options.DeleteOption
-func (_e *Location_Expecter) DeleteFile(relFilePath interface{}, deleteOpts ...interface{}) *Location_DeleteFile_Call {
+//   - opts ...options.DeleteOption
+func (_e *Location_Expecter) DeleteFile(relFilePath interface{}, opts ...interface{}) *Location_DeleteFile_Call {
 	return &Location_DeleteFile_Call{Call: _e.mock.On("DeleteFile",
-		append([]interface{}{relFilePath}, deleteOpts...)...)}
+		append([]interface{}{relFilePath}, opts...)...)}
 }
 
-func (_c *Location_DeleteFile_Call) Run(run func(relFilePath string, deleteOpts ...options.DeleteOption)) *Location_DeleteFile_Call {
+func (_c *Location_DeleteFile_Call) Run(run func(relFilePath string, opts ...options.DeleteOption)) *Location_DeleteFile_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 		variadicArgs := make([]options.DeleteOption, len(args)-1)
 		for i, a := range args[1:] {
@@ -406,9 +406,16 @@ func (_c *Location_ListByRegex_Call) RunAndReturn(run func(*regexp.Regexp) ([]st
 	return _c
 }
 
-// NewFile provides a mock function with given fields: relFilePath
-func (_m *Location) NewFile(relFilePath string) (vfs.File, error) {
-	ret := _m.Called(relFilePath)
+// NewFile provides a mock function with given fields: relFilePath, opts
+func (_m *Location) NewFile(relFilePath string, opts ...options.NewFileOption) (vfs.File, error) {
+	_va := make([]interface{}, len(opts))
+	for _i := range opts {
+		_va[_i] = opts[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, relFilePath)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
 
 	if len(ret) == 0 {
 		panic("no return value specified for NewFile")
@@ -416,19 +423,19 @@ func (_m *Location) NewFile(relFilePath string) (vfs.File, error) {
 
 	var r0 vfs.File
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string) (vfs.File, error)); ok {
-		return rf(relFilePath)
+	if rf, ok := ret.Get(0).(func(string, ...options.NewFileOption) (vfs.File, error)); ok {
+		return rf(relFilePath, opts...)
 	}
-	if rf, ok := ret.Get(0).(func(string) vfs.File); ok {
-		r0 = rf(relFilePath)
+	if rf, ok := ret.Get(0).(func(string, ...options.NewFileOption) vfs.File); ok {
+		r0 = rf(relFilePath, opts...)
 	} else {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).(vfs.File)
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(string) error); ok {
-		r1 = rf(relFilePath)
+	if rf, ok := ret.Get(1).(func(string, ...options.NewFileOption) error); ok {
+		r1 = rf(relFilePath, opts...)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -443,13 +450,21 @@ type Location_NewFile_Call struct {
 
 // NewFile is a helper method to define mock.On call
 //   - relFilePath string
-func (_e *Location_Expecter) NewFile(relFilePath interface{}) *Location_NewFile_Call {
-	return &Location_NewFile_Call{Call: _e.mock.On("NewFile", relFilePath)}
+//   - opts ...options.NewFileOption
+func (_e *Location_Expecter) NewFile(relFilePath interface{}, opts ...interface{}) *Location_NewFile_Call {
+	return &Location_NewFile_Call{Call: _e.mock.On("NewFile",
+		append([]interface{}{relFilePath}, opts...)...)}
 }
 
-func (_c *Location_NewFile_Call) Run(run func(relFilePath string)) *Location_NewFile_Call {
+func (_c *Location_NewFile_Call) Run(run func(relFilePath string, opts ...options.NewFileOption)) *Location_NewFile_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(string))
+		variadicArgs := make([]options.NewFileOption, len(args)-1)
+		for i, a := range args[1:] {
+			if a != nil {
+				variadicArgs[i] = a.(options.NewFileOption)
+			}
+		}
+		run(args[0].(string), variadicArgs...)
 	})
 	return _c
 }
@@ -459,7 +474,7 @@ func (_c *Location_NewFile_Call) Return(_a0 vfs.File, _a1 error) *Location_NewFi
 	return _c
 }
 
-func (_c *Location_NewFile_Call) RunAndReturn(run func(string) (vfs.File, error)) *Location_NewFile_Call {
+func (_c *Location_NewFile_Call) RunAndReturn(run func(string, ...options.NewFileOption) (vfs.File, error)) *Location_NewFile_Call {
 	_c.Call.Return(run)
 	return _c
 }
diff --git a/options/delete/deleteAllVersions.go b/options/delete/allVersions.go
similarity index 51%
rename from options/delete/deleteAllVersions.go
rename to options/delete/allVersions.go
index 2808d26..5925a1f 100644
--- a/options/delete/deleteAllVersions.go
+++ b/options/delete/allVersions.go
@@ -4,13 +4,29 @@ import "github.com/c2fo/vfs/v6/options"
 
 const optionNameDeleteAllVersions = "deleteAllVersions"
 
-// WithDeleteAllVersions returns DeleteAllVersions implementation of delete.DeleteOption
+// WithAllVersions returns AllVersions implementation of DeleteOption
+func WithAllVersions() options.DeleteOption {
+	return AllVersions{}
+}
+
+// AllVersions 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 AllVersions struct{}
+
+// DeleteOptionName returns the name of AllVersions option
+func (w AllVersions) DeleteOptionName() string {
+	return optionNameDeleteAllVersions
+}
+
+// WithDeleteAllVersions returns DeleteAllVersions implementation of options.DeleteOption
+// Deprecated: use WithAllVersions instead
 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.
+// Deprecated: use AllVersions instead
 type DeleteAllVersions struct{}
 
 // DeleteOptionName returns the name of DeleteAllVersions option
diff --git a/options/newfile/contentType.go b/options/newfile/contentType.go
new file mode 100644
index 0000000..559361c
--- /dev/null
+++ b/options/newfile/contentType.go
@@ -0,0 +1,19 @@
+package newfile
+
+import "github.com/c2fo/vfs/v6/options"
+
+const optionNameNewFileContentType = "newFileContentType"
+
+// WithContentType returns ContentType implementation of NewFileOption
+func WithContentType(contentType string) options.NewFileOption {
+	ct := ContentType(contentType)
+	return &ct
+}
+
+// ContentType represents the NewFileOption that is used to explicitly specify a content type on created files.
+type ContentType string
+
+// NewFileOptionName returns the name of ContentType option
+func (ct *ContentType) NewFileOptionName() string {
+	return optionNameNewFileContentType
+}
diff --git a/options/options.go b/options/options.go
index 558d143..d613278 100644
--- a/options/options.go
+++ b/options/options.go
@@ -16,3 +16,8 @@ package options
 type DeleteOption interface {
 	DeleteOptionName() string
 }
+
+// NewFileOption interface contains function that should be implemented by any custom option to qualify as a new file option.
+type NewFileOption interface {
+	NewFileOptionName() string
+}
diff --git a/utils/utils.go b/utils/utils.go
index c547f9e..b3eb579 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -7,6 +7,7 @@ import (
 	"net/url"
 	"path/filepath"
 	"regexp"
+	"runtime"
 	"strings"
 	"time"
 
@@ -137,12 +138,20 @@ func PathToURI(p string) (string, error) {
 		return p, nil
 	}
 
-	// make absolute path (if not already)
-	absPath, err := filepath.Abs(p)
-	if err != nil {
-		return "", err
+	absPath := p
+	if p[0] != '/' {
+		// make absolute path (if not already)
+		absPath, err = filepath.Abs(p)
+		if err != nil {
+			return "", err
+		}
+		if runtime.GOOS == "windows" {
+			absPath = "/" + absPath
+		}
 	}
 
+	absPath = filepath.ToSlash(absPath)
+
 	// Abs() strips trailing slashes so add back if original path had slash
 	if p[len(p)-1:] == "/" {
 		absPath = EnsureTrailingSlash(absPath)
diff --git a/vfs.go b/vfs.go
index 9771ba4..dae32dd 100644
--- a/vfs.go
+++ b/vfs.go
@@ -22,7 +22,7 @@ type FileSystem interface {
 	//       s3://mybucket/path/to/file has a volume of "mybucket and name /path/to/file
 	//     results in /tmp/dir1/newerdir/file.txt for the final vfs.File path.
 	//   * The file may or may not already exist.
-	NewFile(volume string, absFilePath string) (File, error)
+	NewFile(volume string, absFilePath string, opts ...options.NewFileOption) (File, error)
 
 	// NewLocation initializes a Location on the specified volume with the given path.
 	//
@@ -123,7 +123,7 @@ type Location interface {
 	//       results in /tmp/dir1/newerdir/file.txt for the final vfs.File path.
 	//   * Upon success, a vfs.File, representing the file's new path (location path + file relative path), will be returned.
 	//   * The file may or may not already exist.
-	NewFile(relFilePath string) (File, error)
+	NewFile(relFilePath string, opts ...options.NewFileOption) (File, error)
 
 	// DeleteFile deletes the file of the given name at the location.
 	//
@@ -131,7 +131,7 @@ type Location interface {
 	// error handling overhead.
 	//
 	// * Accepts relative file path.
-	DeleteFile(relFilePath string, deleteOpts ...options.DeleteOption) error
+	DeleteFile(relFilePath string, opts ...options.DeleteOption) error
 
 	// URI returns the fully qualified absolute URI for the Location.  IE, s3://bucket/some/path/
 	//
@@ -197,7 +197,7 @@ type File interface {
 	MoveToFile(file File) error
 
 	// Delete unlinks the File on the file system.
-	Delete(deleteOpts ...options.DeleteOption) error
+	Delete(opts ...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/azure/client_integration_test.go b/backend/azure/client_integration_test.go
index 8e4a628..7d80062 100644
--- a/backend/azure/client_integration_test.go
+++ b/backend/azure/client_integration_test.go
@@ -6,19 +6,22 @@ package azure
 import (
 	"context"
 	"fmt"
-	"net/url"
+	"io"
 	"os"
 	"strings"
 	"testing"
 	"time"
 
-	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
 	"github.com/stretchr/testify/suite"
 )
 
 type ClientIntegrationTestSuite struct {
 	suite.Suite
-	testContainerURL azblob.ContainerURL
+	testContainerURL *container.Client
 	accountName      string
 	accountKey       string
 }
@@ -31,28 +34,27 @@ func (s *ClientIntegrationTestSuite) SetupSuite() {
 		panic(err)
 	}
 
-	p := azblob.NewPipeline(credential, azblob.PipelineOptions{})
-	baseURL, err := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net", s.accountName))
+	cli, err := container.NewClientWithSharedKeyCredential(fmt.Sprintf("https://%s.blob.core.windows.net", s.accountName), credential, nil)
 	s.NoError(err)
-	serviceURL := azblob.NewServiceURL(*baseURL, p)
-	s.testContainerURL = serviceURL.NewContainerURL("test-container")
-	_, err = s.testContainerURL.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessNone)
+	s.testContainerURL = cli
+
+	_, err = s.testContainerURL.Create(context.Background(), nil)
 	s.NoError(err)
 
 	// The create function claims to be synchronous but for some reason it does not exist for a little bit so
 	// we need to wait for it to be there.
-	_, err = s.testContainerURL.GetProperties(context.Background(), azblob.LeaseAccessConditions{})
+	_, err = s.testContainerURL.GetProperties(context.Background(), nil)
 	for {
 		time.Sleep(2 * time.Second)
-		if err == nil || err.(azblob.StorageError).ServiceCode() != "BlobNotFound" {
+		if err == nil || !bloberror.HasCode(err, bloberror.BlobNotFound) {
 			break
 		}
-		_, err = s.testContainerURL.GetProperties(context.Background(), azblob.LeaseAccessConditions{})
+		_, err = s.testContainerURL.GetProperties(context.Background(), nil)
 	}
 }
 
 func (s *ClientIntegrationTestSuite) TearDownSuite() {
-	_, err := s.testContainerURL.Delete(context.Background(), azblob.ContainerAccessConditions{})
+	_, err := s.testContainerURL.Delete(context.Background(), nil)
 	s.NoError(err)
 }
 
@@ -64,7 +66,7 @@ func (s *ClientIntegrationTestSuite) TestAllTheThings_FileWithNoPath() {
 	s.NoError(err, "Env variables (AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_ACCESS_KEY) should contain valid azure account credentials")
 
 	// Create the new file
-	err = client.Upload(f, strings.NewReader("Hello world!"))
+	err = client.Upload(f, strings.NewReader("Hello world!"), "")
 	s.NoError(err, "The file should be successfully uploaded to azure")
 
 	// make sure it exists
@@ -112,7 +114,7 @@ func (s *ClientIntegrationTestSuite) TestAllTheThings_FileWithPath() {
 	s.NoError(err, "Env variables (AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_ACCESS_KEY) should contain valid azure account credentials")
 
 	// create a new file
-	err = client.Upload(f, strings.NewReader("Hello world!"))
+	err = client.Upload(f, strings.NewReader("Hello world!"), "")
 	s.NoError(err, "The file should be successfully uploaded to azure")
 
 	// check to see if it exists
@@ -144,11 +146,11 @@ func (s *ClientIntegrationTestSuite) TestDeleteAllVersions() {
 	s.NoError(err, "Env variables (AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_ACCESS_KEY) should contain valid azure account credentials")
 
 	// Create the new file
-	err = client.Upload(f, strings.NewReader("Hello!"))
+	err = client.Upload(f, strings.NewReader("Hello!"), "")
 	s.NoError(err, "The file should be successfully uploaded to azure")
 
 	// Recreate the file
-	err = client.Upload(f, strings.NewReader("Hello world!"))
+	err = client.Upload(f, strings.NewReader("Hello world!"), "")
 	s.NoError(err, "The file should be successfully uploaded to azure")
 
 	// make sure it exists
@@ -172,7 +174,7 @@ func (s *ClientIntegrationTestSuite) TestProperties() {
 	client, err := fs.Client()
 	s.NoError(err, "Env variables (AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_ACCESS_KEY) should contain valid azure account credentials")
 
-	err = client.Upload(f, strings.NewReader("Hello world!"))
+	err = client.Upload(f, strings.NewReader("Hello world!"), "")
 	s.NoError(err, "The file should be successfully uploaded to azure so we shouldn't get an error")
 	props, err := client.Properties(f.Location().(*Location).ContainerURL(), f.Path())
 	s.NoError(err, "The file exists so we shouldn't get an error")
@@ -188,7 +190,7 @@ func (s *ClientIntegrationTestSuite) TestProperties_Location() {
 	l, _ := fs.NewLocation("test-container", "/")
 	client, _ := fs.Client()
 
-	err = client.Upload(f, strings.NewReader("Hello world!"))
+	err = client.Upload(f, strings.NewReader("Hello world!"), "")
 	s.NoError(err, "The file should be successfully uploaded to azure so we shouldn't get an error")
 
 	props, err := client.Properties(l.URI(), "")
@@ -205,7 +207,7 @@ func (s *ClientIntegrationTestSuite) TestProperties_NonExistentFile() {
 
 	_, err = client.Properties(f.Location().URI(), f.Path())
 	s.Error(err, "The file does not exist so we expect an error")
-	s.Equal(404, err.(azblob.ResponseError).Response().StatusCode)
+	s.Equal(404, err.(*azcore.ResponseError).StatusCode)
 }
 
 func (s *ClientIntegrationTestSuite) TestDelete_NonExistentFile() {
@@ -226,7 +228,7 @@ func (s *ClientIntegrationTestSuite) TestTouch_NonexistentContainer() {
 	client, err := fs.Client()
 	s.NoError(err, "Env variables (AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_ACCESS_KEY) should contain valid azure account credentials")
 
-	err = client.Upload(f, strings.NewReader(""))
+	err = client.Upload(f, strings.NewReader(""), "")
 	s.Error(err, "The container doesn't exist so we should get an error")
 }
 
@@ -237,7 +239,7 @@ func (s *ClientIntegrationTestSuite) TestTouch_FileAlreadyExists() {
 	client, err := fs.Client()
 	s.NoError(err)
 
-	err = client.Upload(f, strings.NewReader("One fish, two fish, red fish, blue fish."))
+	err = client.Upload(f, strings.NewReader("One fish, two fish, red fish, blue fish."), "")
 	s.NoError(err)
 	originalProps, err := client.Properties(f.Location().(*Location).ContainerURL(), f.Path())
 	s.NoError(err, "Should get properties back from azure with no error")
diff --git a/backend/azure/file_test.go b/backend/azure/file_test.go
index 76db4f7..c7f04d4 100644
--- a/backend/azure/file_test.go
+++ b/backend/azure/file_test.go
@@ -7,10 +7,12 @@ import (
 	"testing"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
 	"github.com/stretchr/testify/suite"
 
 	"github.com/c2fo/vfs/v6"
 	"github.com/c2fo/vfs/v6/options/delete"
+	"github.com/c2fo/vfs/v6/options/newfile"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -31,11 +33,12 @@ func (s *FileTestSuite) TestClose() {
 }
 
 func (s *FileTestSuite) TestClose_FlushTempFile() {
-	client := MockAzureClient{PropertiesError: MockStorageError{}}
+	client := MockAzureClient{PropertiesError: blobNotFoundErr}
 	fs := NewFileSystem().WithClient(&client)
 	f, _ := fs.NewFile("test-container", "/foo.txt")
 
-	_, _ = f.Write([]byte("Hello, World!"))
+	_, err := f.Write([]byte("Hello, World!"))
+	s.Require().NoError(err)
 	s.NoError(f.Close())
 }
 
@@ -104,7 +107,7 @@ func (s *FileTestSuite) TestExists() {
 }
 
 func (s *FileTestSuite) TestExists_NonExistentFile() {
-	client := MockAzureClient{PropertiesError: MockStorageError{}}
+	client := MockAzureClient{PropertiesError: blobNotFoundErr}
 	fs := NewFileSystem().WithClient(&client)
 
 	f, err := fs.NewFile("test-container", "/foo.txt")
@@ -114,6 +117,15 @@ func (s *FileTestSuite) TestExists_NonExistentFile() {
 	s.False(exists)
 }
 
+func (s *FileTestSuite) TestCloseWithContentType() {
+	client := MockAzureClient{PropertiesError: blobNotFoundErr}
+	fs := NewFileSystem().WithClient(&client)
+	f, _ := fs.NewFile("test-container", "/foo.txt", newfile.WithContentType("text/plain"))
+	_, _ = f.Write([]byte("Hello, World!"))
+	s.NoError(f.Close())
+	s.Equal("text/plain", client.UploadContentType)
+}
+
 func (s *FileTestSuite) TestLocation() {
 	fs := NewFileSystem().WithOptions(Options{AccountName: "test-account"})
 	f, _ := fs.NewFile("test-container", "/file.txt")
@@ -189,22 +201,22 @@ func (s *FileTestSuite) TestDelete() {
 	s.NoError(f.Delete(), "The delete should succeed so there should be no error")
 }
 
-func (s *FileTestSuite) TestDeleteWithDeleteAllVersionsOption() {
+func (s *FileTestSuite) TestDeleteWithAllVersionsOption() {
 	client := MockAzureClient{}
 	fs := NewFileSystem().WithClient(&client)
 
 	f, err := fs.NewFile("test-container", "/foo.txt")
 	s.NoError(err, "The path is valid so no error should be returned")
-	s.NoError(f.Delete(delete.WithDeleteAllVersions()), "The delete should succeed so there should be no error")
+	s.NoError(f.Delete(delete.WithAllVersions()), "The delete should succeed so there should be no error")
 }
 
-func (s *FileTestSuite) TestDeleteWithDeleteAllVersionsOption_Error() {
+func (s *FileTestSuite) TestDeleteWithAllVersionsOption_Error() {
 	client := MockAzureClient{ExpectedError: errors.New("i always error")}
 	fs := NewFileSystem().WithClient(&client)
 
 	f, err := fs.NewFile("test-container", "/foo.txt")
 	s.NoError(err, "The path is valid so no error should be returned")
-	err = f.Delete(delete.WithDeleteAllVersions())
+	err = f.Delete(delete.WithAllVersions())
 	s.Error(err, "If the file does not exist we get an error")
 }
 
@@ -231,7 +243,7 @@ func (s *FileTestSuite) TestLastModified() {
 }
 
 func (s *FileTestSuite) TestSize() {
-	client := MockAzureClient{PropertiesResult: &BlobProperties{Size: 5}}
+	client := MockAzureClient{PropertiesResult: &BlobProperties{Size: to.Ptr[int64](5)}}
 	fs := NewFileSystem().WithClient(&client)
 
 	f, err := fs.NewFile("test-container", "/foo.txt")
@@ -270,7 +282,7 @@ func (s *FileTestSuite) TestName() {
 }
 
 func (s *FileTestSuite) TestTouch() {
-	client := MockAzureClient{ExpectedResult: &BlobProperties{}, PropertiesError: MockStorageError{}}
+	client := MockAzureClient{PropertiesError: blobNotFoundErr}
 	fs := NewFileSystem().WithClient(&client)
 
 	f, err := fs.NewFile("test-container", "/foo.txt")
@@ -287,6 +299,16 @@ func (s *FileTestSuite) TestTouch_NonexistentContainer() {
 	s.Error(f.Touch(), "The container does not exist so creating the new file should error")
 }
 
+func (s *FileTestSuite) TestTouchWithContentType() {
+	client := MockAzureClient{ExpectedResult: &BlobProperties{}, PropertiesError: blobNotFoundErr}
+	fs := NewFileSystem().WithClient(&client)
+
+	f, err := fs.NewFile("test-container", "/foo.txt", newfile.WithContentType("text/plain"))
+	s.NoError(err, "The path is valid so no error should be returned")
+	s.NoError(f.Touch())
+	s.Equal("text/plain", client.UploadContentType)
+}
+
 func (s *FileTestSuite) TestURI() {
 	fs := NewFileSystem().WithOptions(Options{AccountName: "test-container"})
 	f, _ := fs.NewFile("temp", "/foo/bar/blah.txt")
@@ -319,7 +341,7 @@ func (s *FileTestSuite) TestCheckTempFile() {
 }
 
 func (s *FileTestSuite) TestCheckTempFile_FileDoesNotExist() {
-	client := MockAzureClient{PropertiesError: MockStorageError{}}
+	client := MockAzureClient{PropertiesError: blobNotFoundErr}
 	fs := NewFileSystem().WithClient(&client)
 
 	f, err := fs.NewFile("test-container", "/foo.txt")
diff --git a/backend/azure/options_test.go b/backend/azure/options_test.go
index d9950ba..1e53d52 100644
--- a/backend/azure/options_test.go
+++ b/backend/azure/options_test.go
@@ -5,7 +5,8 @@ import (
 	"os"
 	"testing"
 
-	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
+	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
 	"github.com/stretchr/testify/suite"
 )
 
@@ -32,44 +33,37 @@ func (s *OptionsTestSuite) TestCredentials_ServiceAccount() {
 		TenantID:               "foo",
 		ClientID:               "foo",
 		ClientSecret:           "foo",
-		tokenCredentialFactory: &MockTokenCredentialFactory{},
+		tokenCredentialFactory: MockTokenCredentialFactory,
 	}
 
 	credential, err := options.Credential()
 	s.NoError(err, "service account vars are present so no error")
 	s.NotNil(credential, "expect a non-nil credential when service account vars are set")
-	_, ok := credential.(azblob.TokenCredential)
-	s.True(ok, "credentials type should be TokenCredential")
+	s.Implements((*azcore.TokenCredential)(nil), credential, "credentials type should be TokenCredential")
 }
 
 func (s *OptionsTestSuite) TestCredentials_StorageAccount() {
 	options := Options{
 		AccountName:            "foo",
 		AccountKey:             base64.StdEncoding.EncodeToString([]byte("bar")),
-		tokenCredentialFactory: &MockTokenCredentialFactory{},
+		tokenCredentialFactory: MockTokenCredentialFactory,
 	}
 
 	credential, err := options.Credential()
 	s.NoError(err, "service account vars are present so no error")
 	s.NotNil(credential, "expect a non-nil credential when service account vars are set")
-	_, ok := credential.(*azblob.SharedKeyCredential)
-	s.True(ok, "credential type should be SharedKeyCredential")
+	s.IsType((*azblob.SharedKeyCredential)(nil), credential, "credentials type should be SharedKeyCredential")
 }
 
 func (s *OptionsTestSuite) TestCredentials_Anon() {
 	options := Options{
 		AccountName:            "foo",
-		tokenCredentialFactory: &MockTokenCredentialFactory{},
+		tokenCredentialFactory: MockTokenCredentialFactory,
 	}
 
 	credential, err := options.Credential()
 	s.NoError(err, "anon vars are present so no error")
-	s.NotNil(credential, "expect a non-nil credential when service account vars are set")
-	s.NotNil(credential, "when no env vars are set we should get a non-nil credential")
-	_, ok := credential.(azblob.TokenCredential)
-	s.False(ok)
-	_, ok = credential.(*azblob.SharedKeyCredential)
-	s.False(ok)
+	s.Nil(credential, "when no env vars are set we should get a nil credential")
 }
 
 func TestOptions(t *testing.T) {
diff --git a/backend/gs/file_test.go b/backend/gs/file_test.go
index c43ec29..75a09ed 100644
--- a/backend/gs/file_test.go
+++ b/backend/gs/file_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/stretchr/testify/suite"
 
 	"github.com/c2fo/vfs/v6/options/delete"
+	"github.com/c2fo/vfs/v6/options/newfile"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -198,7 +199,7 @@ func (ts *fileTestSuite) TestDeleteRemoveAllVersions() {
 	ts.Require().NoError(err, "Shouldn't fail getting object generation handles")
 	ts.Len(handles, 1)
 
-	err = file.Delete(delete.WithDeleteAllVersions())
+	err = file.Delete(delete.WithAllVersions())
 	ts.Require().NoError(err, "Shouldn't fail deleting the file")
 
 	bucket := client.Bucket(bucketName)
@@ -225,6 +226,56 @@ func (ts *fileTestSuite) TestWrite() {
 	ts.NoError(err, "Error should be nil when calling Write")
 }
 
+func (ts *fileTestSuite) TestWriteWithContentType() {
+	contents := "hello world!"
+	bucketName := "bucki"
+	objectName := "some/path/file.txt"
+	server := fakestorage.NewServer(Objects{})
+	defer server.Stop()
+	client := server.Client()
+	bucket := client.Bucket(bucketName)
+	ctx := context.Background()
+	err := bucket.Create(ctx, "", nil)
+	ts.Require().NoError(err)
+	fs := NewFileSystem().WithClient(client)
+
+	file, err := fs.NewFile(bucketName, "/"+objectName, newfile.WithContentType("text/plain"))
+	ts.NoError(err, "Shouldn't fail creating new file")
+
+	_, err = file.Write([]byte(contents))
+	ts.NoError(err, "Error should be nil when calling Write")
+
+	err = file.Close()
+	ts.NoError(err, "Error should be nil when calling Close")
+
+	attrs, err := bucket.Object(objectName).Attrs(ctx)
+	ts.Require().NoError(err)
+	ts.Equal("text/plain", attrs.ContentType)
+}
+
+func (ts *fileTestSuite) TestTouchWithContentType() {
+	bucketName := "bucki"
+	objectName := "some/path/file.txt"
+	server := fakestorage.NewServer(Objects{})
+	defer server.Stop()
+	client := server.Client()
+	bucket := client.Bucket(bucketName)
+	ctx := context.Background()
+	err := bucket.Create(ctx, "", nil)
+	ts.Require().NoError(err)
+	fs := NewFileSystem().WithClient(client)
+
+	file, err := fs.NewFile(bucketName, "/"+objectName, newfile.WithContentType("text/plain"))
+	ts.NoError(err, "Shouldn't fail creating new file")
+
+	err = file.Touch()
+	ts.NoError(err, "Error should be nil when calling Touch")
+
+	attrs, err := bucket.Object(objectName).Attrs(ctx)
+	ts.Require().NoError(err)
+	ts.Equal("text/plain", attrs.ContentType)
+}
+
 func (ts *fileTestSuite) TestGetLocation() {
 	server := fakestorage.NewServer(Objects{})
 	defer server.Stop()
diff --git a/backend/mem/file_test.go b/backend/mem/file_test.go
index dc3ee91..b9cfd1c 100644
--- a/backend/mem/file_test.go
+++ b/backend/mem/file_test.go
@@ -37,10 +37,10 @@ func (s *memFileTest) SetupTest() {
 	s.NoError(s.testFile.Touch(), "unexpected error touching file")
 }
 
-func (s *memFileTest) TeardownTest() {
+func (s *memFileTest) TearDownTest() {
 	err := s.testFile.Close()
 	s.NoError(err, "close error not expected")
-	s.NoError(s.testFile.Delete(), "delete failed unexpectedly")
+	_ = s.testFile.Delete()
 }
 
 // TestZBR ensures that we can always read zero bytes
@@ -382,7 +382,7 @@ func (s *memFileTest) TestCopyToLocationOS() {
 
 	s.NotNil(copiedFile)
 	s.Equal("/test_files/test.txt", s.testFile.Path())         // testFile's path should be unchanged
-	s.Equal(filepath.Join(dir, "test.txt"), copiedFile.Path()) // new path should be that
+	s.Equal(path.Join(osFile.Location().Path(), "test.txt"), copiedFile.Path()) // new path should be that
 
 	_, err = copiedFile.Read(readSlice)
 	s.NoError(err, "unexpected read error")
@@ -390,6 +390,7 @@ func (s *memFileTest) TestCopyToLocationOS() {
 	_, err = s.testFile.Read(readSlice2)
 	s.NoError(err, "unexpected read error")
 	s.Equal(readSlice2, readSlice) // both reads should be the same
+	s.Require().NoError(copiedFile.Close())
 	cleanErr := os.RemoveAll(dir)  // clean up
 	s.NoError(cleanErr, "unexpected error cleaning up osFiles")
 }
diff --git a/backend/os/file_test.go b/backend/os/file_test.go
index 2b2eb8f..d39bf9b 100644
--- a/backend/os/file_test.go
+++ b/backend/os/file_test.go
@@ -6,7 +6,9 @@ import (
 	"io"
 	"os"
 	"path"
+	"path/filepath"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/suite"
@@ -48,7 +50,7 @@ func (s *osFileTest) SetupTest() {
 	s.testFile = file
 }
 
-func (s *osFileTest) TeardownTest() {
+func (s *osFileTest) TearDownTest() {
 	err := s.testFile.Close()
 	s.NoError(err, "close error not expected")
 }
@@ -93,6 +95,8 @@ func (s *osFileTest) TestTouch() {
 	firstModTime, err := testfile.LastModified()
 	s.NoError(err)
 
+	time.Sleep(time.Millisecond)
+
 	// touch again
 	err = testfile.Touch()
 	s.NoError(err)
@@ -173,14 +177,6 @@ func (s *osFileTest) TestSeek() {
 	s.NoError(rerr, "read error not expected")
 	s.Equal(expectedText, string(data))
 	s.NoError(s.testFile.Close())
-
-	// setup file for err out of opening
-	f, err := s.tmploc.NewFile("test_files/seekFileFail.txt")
-	s.NoError(err)
-	f.(*File).useTempFile = true
-	f.(*File).fileOpener = func(filePath string) (*os.File, error) { return nil, errors.New("bad opener") }
-	_, err = f.Seek(0, 4)
-	s.Error(err)
 }
 
 func (s *osFileTest) TestCopyToLocation() {
@@ -193,7 +189,7 @@ func (s *osFileTest) TestCopyToLocation() {
 	otherFile.On("Close").Return(nil)
 	otherFs.On("NewFile", mock.Anything, mock.Anything).Return(otherFile, nil)
 
-	location := Location{"/some/path", otherFs}
+	location := Location{name: "/some/path", fileSystem: otherFs}
 
 	_, err := s.testFile.CopyToLocation(&location)
 	s.NoError(err)
@@ -208,7 +204,7 @@ func (s *osFileTest) TestCopyToFile() {
 	otherFs := &mocks.FileSystem{}
 	otherFile := new(mocks.File)
 
-	location := Location{"/some/path", otherFs}
+	location := Location{name: "/some/path", fileSystem: otherFs}
 
 	// Expected behavior
 	otherFile.On("Write", mock.Anything).Return(len(expectedText), nil)
@@ -231,7 +227,7 @@ func (s *osFileTest) TestEmptyCopyToFile() {
 	otherFs := new(mocks.FileSystem)
 	otherFile := new(mocks.File)
 
-	location := Location{"/some/path", otherFs}
+	location := Location{name: "/some/path", fileSystem: otherFs}
 
 	// Expected behavior
 	otherFile.On("Write", mock.Anything).Return(len(expectedText), nil)
@@ -263,7 +259,7 @@ func (s *osFileTest) TestCopyToLocationIgnoreExtraSeparator() {
 	otherFs.On("NewFile", mock.Anything, mock.Anything).Return(otherFile, nil)
 
 	// Add trailing slash
-	location := Location{"/some/path/", otherFs}
+	location := Location{name: "/some/path/", fileSystem: otherFs}
 
 	_, err := s.testFile.CopyToLocation(&location)
 	s.Require().NoError(err)
@@ -273,10 +269,10 @@ func (s *osFileTest) TestCopyToLocationIgnoreExtraSeparator() {
 
 func (s *osFileTest) TestMoveToLocation() {
 	expectedText := "moved file"
-	dir, terr := os.MkdirTemp(path.Join(s.tmploc.Path(), "test_files"), "example")
+	dir, terr := os.MkdirTemp(filepath.Join(osLocationPath(s.tmploc), "test_files"), "example")
 	s.NoError(terr)
 
-	origFileName := path.Join(dir, "test_files/move.txt")
+	origFileName := filepath.Join(dir, "test_files", "move.txt")
 	file, nerr := s.fileSystem.NewFile("", origFileName)
 	s.NoError(nerr)
 
@@ -296,10 +292,11 @@ func (s *osFileTest) TestMoveToLocation() {
 	s.True(found)
 
 	// setup location
-	location := Location{dir, s.fileSystem}
+	location, err := s.fileSystem.NewLocation("", utils.EnsureTrailingSlash(dir))
+	s.Require().NoError(err)
 
 	// move the file to new location
-	movedFile, err := file.MoveToLocation(&location)
+	movedFile, err := file.MoveToLocation(location)
 	s.NoError(err)
 
 	s.Equal(location.Path(), movedFile.Location().Path(), "ensure file location changed")
@@ -336,7 +333,7 @@ func (s *osFileTest) TestMoveToLocation() {
 }
 
 func (s *osFileTest) TestSafeOsRename() {
-	dir, err := os.MkdirTemp(path.Join(s.tmploc.Path(), "test_files"), "example")
+	dir, err := os.MkdirTemp(filepath.Join(osLocationPath(s.tmploc), "test_files"), "example")
 	s.NoError(err)
 	defer func() {
 		err := os.RemoveAll(dir)
@@ -377,7 +374,7 @@ func (s *osFileTest) TestSafeOsRename() {
 }
 
 func (s *osFileTest) TestOsCopy() {
-	dir, err := os.MkdirTemp(path.Join(s.tmploc.Path(), "test_files"), "example")
+	dir, err := os.MkdirTemp(filepath.Join(osLocationPath(s.tmploc), "test_files"), "example")
 	s.NoError(err)
 	defer func() {
 		err := os.RemoveAll(dir)
@@ -399,10 +396,12 @@ func (s *osFileTest) TestOsCopy() {
 	b, err := io.ReadAll(file2)
 	s.NoError(err)
 	s.Equal(testBytes, b, "contents match")
+
+	s.Require().NoError(file2.Close())
 }
 
 func (s *osFileTest) TestMoveToFile() {
-	dir, terr := os.MkdirTemp(path.Join(s.tmploc.Path(), "test_files"), "example")
+	dir, terr := os.MkdirTemp(filepath.Join(osLocationPath(s.tmploc), "test_files"), "example")
 	s.NoError(terr)
 
 	file1, err := s.fileSystem.NewFile("", path.Join(dir, "original.txt"))
@@ -509,6 +508,7 @@ func (s *osFileTest) TestWrite() {
 	data = make([]byte, 4)
 	_, err = f.Write(data)
 	s.Error(err)
+	s.Require().NoError(f.Close())
 }
 
 func (s *osFileTest) TestCursor() {
@@ -638,7 +638,7 @@ func (s *osFileTest) TestLastModified() {
 
 	lastModified, err := file.LastModified()
 	s.NoError(err)
-	osStats, err := os.Stat(path.Join(s.tmploc.Path(), "test_files/test.txt"))
+	osStats, err := os.Stat(filepath.Join(osLocationPath(s.tmploc), "test_files", "test.txt"))
 	s.NoError(err)
 	s.NotNil(lastModified)
 	s.Equal(osStats.ModTime(), *lastModified)
@@ -663,7 +663,7 @@ func (s *osFileTest) TestSize() {
 	size, err := file.Size()
 	s.NoError(err)
 
-	osStats, err := os.Stat(path.Join(s.tmploc.Path(), "test_files/test.txt"))
+	osStats, err := os.Stat(filepath.Join(osLocationPath(s.tmploc), "test_files", "test.txt"))
 	s.NoError(err)
 	s.NotNil(size)
 	s.Equal(osStats.Size(), int64(size))
@@ -684,14 +684,14 @@ func (s *osFileTest) TestPath() {
 func (s *osFileTest) TestURI() {
 	file, err := s.tmploc.NewFile("some/file/test.txt")
 	s.NoError(err)
-	expected := fmt.Sprintf("file://%s", path.Join(s.tmploc.Path(), "some/file/test.txt"))
+	expected := fmt.Sprintf("file://%s", filepath.ToSlash(filepath.Join(osLocationPath(s.tmploc), "some", "file", "test.txt")))
 	s.Equal(expected, file.URI(), "%s does not match %s", file.URI(), expected)
 }
 
 func (s *osFileTest) TestStringer() {
 	file, err := s.tmploc.NewFile("some/file/test.txt")
 	s.NoError(err)
-	s.Equal(fmt.Sprintf("file://%s", path.Join(s.tmploc.Path(), "some/file/test.txt")), file.String())
+	s.Equal(fmt.Sprintf("file://%s", filepath.ToSlash(filepath.Join(osLocationPath(s.tmploc), "some", "file", "test.txt"))), file.String())
 }
 
 func (s *osFileTest) TestLocationRightAfterChangeDir() {
diff --git a/backend/os/location_test.go b/backend/os/location_test.go
index 7993f2d..243da49 100644
--- a/backend/os/location_test.go
+++ b/backend/os/location_test.go
@@ -2,6 +2,7 @@ package os
 
 import (
 	"os"
+	"path"
 	"path/filepath"
 	"regexp"
 	"testing"
@@ -116,14 +117,14 @@ func (s *osLocationTest) TestChangeDir() {
 	cwd := fileLocation.Path()
 	err := fileLocation.ChangeDir("other/")
 	s.NoError(err, "change dir error not expected")
-	s.Equal(fileLocation.Path(), utils.EnsureTrailingSlash(filepath.Join(cwd, "other")))
+	s.Equal(fileLocation.Path(), utils.EnsureTrailingSlash(path.Join(cwd, "other")))
 }
 
 func (s *osLocationTest) TestVolume() {
 	volume := s.testFile.Location().Volume()
 
 	// For Unix, this returns an empty string. For windows, it would be something like 'C:'
-	s.Equal(filepath.VolumeName("test_files/test.txt"), volume)
+	s.Equal(filepath.VolumeName(os.TempDir()), volume)
 }
 
 func (s *osLocationTest) TestPath() {
diff --git a/backend/s3/file_test.go b/backend/s3/file_test.go
index 55f060e..9d3d8f9 100644
--- a/backend/s3/file_test.go
+++ b/backend/s3/file_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/c2fo/vfs/v6/backend/s3/mocks"
 	vfsmocks "github.com/c2fo/vfs/v6/mocks"
 	"github.com/c2fo/vfs/v6/options/delete"
+	"github.com/c2fo/vfs/v6/options/newfile"
 	"github.com/c2fo/vfs/v6/utils"
 )
 
@@ -593,7 +594,7 @@ func (ts *fileTestSuite) TestDeleteError() {
 	s3apiMock.AssertExpectations(ts.T())
 }
 
-func (ts *fileTestSuite) TestDeleteWithDeleteAllVersionsOption() {
+func (ts *fileTestSuite) TestDeleteWithAllVersionsOption() {
 	var versions []*s3.ObjectVersion
 	verIds := [...]string{"ver1", "ver2"}
 	for i := range verIds {
@@ -605,13 +606,13 @@ func (ts *fileTestSuite) TestDeleteWithDeleteAllVersionsOption() {
 	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())
+	err := testFile.Delete(delete.WithAllVersions())
 	ts.NoError(err, "Successful delete should not return an error.")
 	s3apiMock.AssertExpectations(ts.T())
 	s3apiMock.AssertNumberOfCalls(ts.T(), "DeleteObject", 3)
 }
 
-func (ts *fileTestSuite) TestDeleteWithDeleteAllVersionsOptionError() {
+func (ts *fileTestSuite) TestDeleteWithAllVersionsOptionError() {
 	var versions []*s3.ObjectVersion
 	verIds := [...]string{"ver1", "ver2"}
 	for i := range verIds {
@@ -625,7 +626,7 @@ func (ts *fileTestSuite) TestDeleteWithDeleteAllVersionsOptionError() {
 	s3apiMock.On("DeleteObject", &s3.DeleteObjectInput{Key: &testFileName, Bucket: &bucket, VersionId: &verIds[0]}).
 		Return(nil, errors.New("something went wrong"))
 
-	err := testFile.Delete(delete.WithDeleteAllVersions())
+	err := testFile.Delete(delete.WithAllVersions())
 	ts.Error(err, "Delete should return an error if s3 api had error.")
 	s3apiMock.AssertExpectations(ts.T())
 	s3apiMock.AssertNumberOfCalls(ts.T(), "DeleteObject", 2)
@@ -702,6 +703,13 @@ func (ts *fileTestSuite) TestUploadInputDisableSSE() {
 	ts.Equal("mybucket", *input.Bucket, "bucket was set")
 }
 
+func (ts *fileTestSuite) TestUploadInputContentType() {
+	fs = FileSystem{client: &mocks.S3API{}}
+	file, _ := fs.NewFile("mybucket", "/some/file/test.txt", newfile.WithContentType("text/plain"))
+	input := uploadInput(file.(*File))
+	ts.Equal("text/plain", *input.ContentType)
+}
+
 func (ts *fileTestSuite) TestNewFile() {
 	fs := &FileSystem{}
 	// fs is nil
diff --git a/backend/s3/location_test.go b/backend/s3/location_test.go
index c2b41eb..9d02610 100644
--- a/backend/s3/location_test.go
+++ b/backend/s3/location_test.go
@@ -300,7 +300,7 @@ func (lt *locationTestSuite) TestDeleteFile() {
 	lt.s3apiMock.AssertExpectations(lt.T())
 }
 
-func (lt *locationTestSuite) TestDeleteFileWithDeleteAllVersionsOption() {
+func (lt *locationTestSuite) TestDeleteFileWithAllVersionsOption() {
 	var versions []*s3.ObjectVersion
 	verIds := [...]string{"ver1", "ver2"}
 	for i := range verIds {
@@ -314,7 +314,7 @@ func (lt *locationTestSuite) TestDeleteFileWithDeleteAllVersionsOption() {
 	loc, err := lt.fs.NewLocation("bucket", "/old/")
 	lt.NoError(err)
 
-	err = loc.DeleteFile("filename.txt", delete.WithDeleteAllVersions())
+	err = loc.DeleteFile("filename.txt", delete.WithAllVersions())
 	lt.NoError(err, "Successful delete should not return an error.")
 	lt.s3apiMock.AssertExpectations(lt.T())
 	lt.s3apiMock.AssertNumberOfCalls(lt.T(), "DeleteObject", 3)
diff --git a/backend/s3/options_test.go b/backend/s3/options_test.go
index 0ae6a9f..1fdf781 100644
--- a/backend/s3/options_test.go
+++ b/backend/s3/options_test.go
@@ -44,6 +44,19 @@ func (o *optionsTestSuite) TestGetClient() {
 	o.NoError(err)
 	o.NotNil(client, "client is set")
 	o.Equal("set-by-envvar", *client.(*s3.S3).Config.Region, "region is set by env var")
+
+	// role ARN set
+	opts = Options{
+		AccessKeyID:     "mykey",
+		SecretAccessKey: "mysecret",
+		Region:          "some-region",
+		RoleARN:         "arn:aws:iam::123456789012:role/my-role",
+	}
+	client, err = getClient(opts)
+	o.NoError(err)
+	o.NotNil(client, "client is set")
+	o.Equal("some-region", *client.(*s3.S3).Config.Region, "region is set")
+	o.NotNil(client.(*s3.S3).Config.Credentials, "credentials are set")
 }
 
 func TestOptions(t *testing.T) {
diff --git a/backend/sftp/options_test.go b/backend/sftp/options_test.go
index 72790c9..e9f4def 100644
--- a/backend/sftp/options_test.go
+++ b/backend/sftp/options_test.go
@@ -103,6 +103,7 @@ type getFileTest struct {
 	keyfile    string
 	passphrase string
 	hasError   bool
+	err        error
 	errMessage string
 	message    string
 }
@@ -127,7 +128,7 @@ func (o *optionsSuite) TestGetKeyFile() {
 			keyfile:    "nonexistent.key",
 			passphrase: "",
 			hasError:   true,
-			errMessage: "open nonexistent.key: no such file or directory",
+			err:        os.ErrNotExist,
 			message:    "file not found",
 		},
 		{
@@ -153,7 +154,11 @@ func (o *optionsSuite) TestGetKeyFile() {
 		o.Run(t.message, func() {
 			_, err := getKeyFile(t.keyfile, t.passphrase)
 			if t.hasError {
-				o.EqualError(err, t.errMessage, t.message)
+				if t.err != nil {
+					o.ErrorIs(err, t.err, t.message)
+				} else {
+					o.EqualError(err, t.errMessage, t.message)
+				}
 			} else {
 				o.NoError(err, t.message)
 			}
@@ -269,6 +274,7 @@ type authTest struct {
 	returnCount int
 	hasError    bool
 	errMessage  string
+	err         error
 	message     string
 }
 
@@ -374,7 +380,7 @@ func (o *optionsSuite) TestGetAuthMethods() {
 			},
 			returnCount: 1,
 			hasError:    true,
-			errMessage:  "open nonexistent.key: no such file or directory",
+			err:         os.ErrNotExist,
 			message:     "env var keyfile returns error for file not found",
 		},
 	}
@@ -391,7 +397,11 @@ func (o *optionsSuite) TestGetAuthMethods() {
 			// apply test
 			auth, err := getAuthMethods(t.options)
 			if t.hasError {
-				o.EqualError(err, t.errMessage, t.message)
+				if t.err != nil {
+					o.ErrorIs(err, t.err, t.message)
+				} else {
+					o.EqualError(err, t.errMessage, t.message)
+				}
 			} else {
 				o.NoError(err, t.message)
 				o.Len(auth, t.returnCount, "auth count")
@@ -409,6 +419,7 @@ type getClientTest struct {
 	options   Options
 	authority utils.Authority
 	hasError  bool
+	err       error
 	errRegex  string
 	message   string
 }
@@ -435,7 +446,7 @@ func (o *optionsSuite) TestGetClient() {
 				KnownHostsCallback: ssh.FixedHostKey(o.publicKey),
 			},
 			hasError: true,
-			errRegex: "open nonexistent.key: no such file or directory",
+			err:      os.ErrNotExist,
 			message:  "getclient - bad auth key",
 		},
 		{
@@ -455,8 +466,12 @@ func (o *optionsSuite) TestGetClient() {
 			_, _, err := getClient(t.authority, t.options)
 			if t.hasError {
 				if o.Error(err, "error found") {
-					re := regexp.MustCompile(t.errRegex)
-					o.Regexp(re, err.Error(), "error matches")
+					if t.err != nil {
+						o.ErrorIs(err, t.err, t.message)
+					} else {
+						re := regexp.MustCompile(t.errRegex)
+						o.Regexp(re, err.Error(), "error matches")
+					}
 				}
 			} else {
 				o.NoError(err, t.message)
diff --git a/backend/testsuite/io_integration_test.go b/backend/testsuite/io_integration_test.go
index ffda5fb..3f1fb92 100644
--- a/backend/testsuite/io_integration_test.go
+++ b/backend/testsuite/io_integration_test.go
@@ -120,7 +120,7 @@ type ReadWriteSeekCloseURINamer interface {
 	io.Closer
 	Name() string
 	URI() string
-	Delete(deleteOpts ...options.DeleteOption) error
+	Delete(opts ...options.DeleteOption) error
 }
 
 type ioTestSuite struct {
diff --git a/utils/utils_test.go b/utils/utils_test.go
index af38f8a..0d2223b 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -715,6 +715,8 @@ func (s *utilsSuite) TestTouchCopy() {
 	s.NoError(err, "file should exist, so no error")
 	s.NotZero(fi, "file should have a non-zero byte size")
 
+	s.NoError(reader.Close())
+
 	// TouchCopy should fail on a reader.Size() error
 	nonexistentFile := path.Join(writer.Path(), "nonexistent.file")
 	noFile, err := osfs.NewFile("", nonexistentFile)
@@ -790,6 +792,8 @@ func (s *utilsSuite) TestTouchCopyBufferedDefaultBufferSize() {
 	s.NoError(err, "file should exist, so no error")
 	s.NotZero(fi, "file should have a non-zero byte size")
 
+	s.NoError(reader.Close())
+
 	// TouchCopyBuffered should fail on a reader.Size() error
 	nonexistentFile := path.Join(writer.Path(), "nonexistent.file")
 	noFile, err := osfs.NewFile("", nonexistentFile)
@@ -865,6 +869,8 @@ func (s *utilsSuite) TestTouchCopyBufferedNonDefaultBufferSize() {
 	s.NoError(err, "file should exist, so no error")
 	s.NotZero(fi, "file should have a non-zero byte size")
 
+	s.NoError(reader.Close())
+
 	// TouchCopyBuffered should fail on a reader.Size() error
 	nonexistentFile := path.Join(writer.Path(), "nonexistent.file")
 	noFile, err := osfs.NewFile("", nonexistentFile)