feat(azure): server-side copy with download+reupload fallback

* feat(azure): implement server-side copy with fallback

Add server-side object copy for the Azure backend using StartCopyFromURL, with a
fallback to download+reupload when server-side copy is unavailable. The copy
logic lives in backend/azure/copy.go and handles metadata, tagging and object
lock configurations.

The copy-source SAS service version is configurable via the --copy-sas-version
flag (AZ_COPY_SAS_VERSION) and defaults to the SDK version, so production is
unchanged. Endpoints that lag the SDK's SAS version (e.g. Azurite) cannot verify
a SAS signed with the newer version and can set an older one. On a metadata-COPY,
the internal website-redirect key is dropped from the destination to match the
download+reupload fallback.

Testing:
- Add CopyObject_cross_bucket_server_side_copy, which copies an object with data,
  user metadata, content-type and tags across two buckets and verifies all are
  preserved and an ETag is returned.
- Configure the Azurite functional-test gateway with AZ_COPY_SAS_VERSION and let
  Azurite trust its self-signed test certificate (NODE_EXTRA_CA_CERTS) so it can
  fetch the copy source from its own HTTPS endpoint, ensuring CI exercises the
  real server-side copy path instead of always falling back.

Signed-off-by: Nils Leger <nils.leger@getflip.com>

* docs: update copyright year

Signed-off-by: Nils Leger <nils.leger@getflip.com>

* fix: always fallback to download+upload whenever there is an error building the server-side copy URL

Signed-off-by: Nils Leger <nils.leger@getflip.com>

---------

Signed-off-by: Nils Leger <nils.leger@getflip.com>
This commit is contained in:
Nils Leger
2026-08-27 16:37:28 -07:00
committed by GitHub
parent 29bf076abc
commit a780f3473a
7 changed files with 674 additions and 4 deletions
+8 -2
View File
@@ -22,7 +22,7 @@ import (
)
var (
azAccount, azKey, azServiceURL, azSASToken string
azAccount, azKey, azServiceURL, azSASToken, azCopySASVersion string
)
// AzureCommand returns the "azure" subcommand, common to all versitygw
@@ -62,12 +62,18 @@ func AzureCommand() *cli.Command {
Aliases: []string{"u"},
Destination: &azServiceURL,
},
&cli.StringFlag{
Name: "copy-sas-version",
Usage: "service version used to sign the copy-source SAS for server-side copy (defaults to the SDK version; set this for endpoints that lag the SDK, e.g. Azurite)",
EnvVars: []string{"AZ_COPY_SAS_VERSION"},
Destination: &azCopySASVersion,
},
},
}
}
func runAzure(ctx *cli.Context) error {
be, err := azure.New(azAccount, azKey, azServiceURL, azSASToken, CopyObjectThreshold)
be, err := azure.New(azAccount, azKey, azServiceURL, azSASToken, CopyObjectThreshold, azCopySASVersion)
if err != nil {
return fmt.Errorf("init azure: %w", err)
}