Copy an object (Go SDK V2)

Updated at:
Copy as MD

Use CopyObject to copy an object smaller than 1 GB within a versioning-enabled bucket. For objects larger than 1 GB, use UploadPartCopy to perform a multipart copy.

Prerequisites

Before you begin, ensure that you have:

Usage notes

  • The sample code uses the China (Hangzhou) region (cn-hangzhou) and a public endpoint. To access OSS from another Alibaba Cloud service in the same region, use an internal endpoint instead. For supported regions and endpoints, see OSS regions and endpoints.

  • Appendable objects cannot be copied to a bucket with versioning enabled or suspended.

Copy an object

CopyObject copies objects up to 1 GB from a source bucket to a destination bucket within the same region. By default, it copies the current version of the source object. To copy a specific version, set SourceVersionId in the request.

Versioning behavior:

ScenarioBehavior
Current version is a delete marker, no SourceVersionId specifiedOSS returns 404 Not Found, which indicates that the object does not exist
SourceVersionId points to a delete markerDelete markers cannot be copied
Versioning enabled on destination bucketOSS generates a unique version ID for the copied object and returns it in the x-oss-version-id response header
Versioning disabled or suspended on destination bucketOSS assigns a null version ID; this overwrites any existing null-versioned object
Restoring a previous versionCopy it to the same bucket; the copied object becomes the new current version

The following example copies a specific version of an object and sets a new storage class and tags on the destination object.

package main

import (
	"context"
	"flag"
	"log"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

var (
	region         string
	srcBucketName  string
	srcObjectName  string
	destBucketName string
	destObjectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&srcBucketName, "src-bucket", "", "The name of the source bucket.")
	flag.StringVar(&srcObjectName, "src-object", "", "The name of the source object.")
	flag.StringVar(&destBucketName, "dest-bucket", "", "The name of the destination bucket.")
	flag.StringVar(&destObjectName, "dest-object", "", "The name of the destination object.")
}

func main() {
	flag.Parse()

	if len(srcBucketName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, source bucket name required")
	}

	if len(region) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, region required")
	}

	// Default to the source bucket if no destination bucket is specified.
	if len(destBucketName) == 0 {
		destBucketName = srcBucketName
	}

	if len(srcObjectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, source object name required")
	}

	if len(destObjectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, destination object name required")
	}

	// Load default config with credentials from environment variables.
	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	client := oss.NewClient(cfg)

	// Copy a specific version of the source object to the destination.
	// StorageClass and Tagging override the source object's values.
	copyRequest := &oss.CopyObjectRequest{
		Bucket:           oss.Ptr(destBucketName),
		Key:              oss.Ptr(destObjectName),
		SourceKey:        oss.Ptr(srcObjectName),
		SourceBucket:     oss.Ptr(srcBucketName),
		SourceVersionId:  oss.Ptr("yourVersionId"),           // Replace with the actual version ID.
		StorageClass:     oss.StorageClassStandard,
		TaggingDirective: oss.Ptr("Replace"),                 // Do not inherit tags from the source object.
		Tagging:          oss.Ptr("tag1=value1&tag2=value2"), // Tags to set on the destination object.
	}

	copyResult, err := client.CopyObject(context.TODO(), copyRequest)
	if err != nil {
		log.Fatalf("failed to copy object: %v", err)
	}

	// VersionId is the version ID assigned to the newly created object in the destination bucket.
	log.Printf("copy object result versionId: %#v\n", *copyResult.VersionId)
}

Multipart copy

For objects larger than 1 GB, use UploadPartCopy to copy the object in parts. The process has three steps: initialize a multipart upload, copy parts concurrently, then complete the upload.

By default, UploadPartCopy copies from the current version of the source object. To copy from a specific version, set SourceVersionId in the UploadPartCopyRequest.

Version ID behavior with delete markers:

ScenarioBehavior
No SourceVersionId specified and current version is a delete markerOSS returns 404 Not Found
SourceVersionId points to a delete markerOSS returns 400 Bad Request

The following example copies 3 parts concurrently using goroutines.

package main

import (
	"context"
	"flag"
	"log"
	"sync"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

var (
	region         string
	srcBucketName  string
	srcObjectName  string
	destBucketName string
	destObjectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&srcBucketName, "src-bucket", "", "The name of the source bucket.")
	flag.StringVar(&srcObjectName, "src-object", "", "The name of the source object.")
	flag.StringVar(&destBucketName, "dest-bucket", "", "The name of the destination bucket.")
	flag.StringVar(&destObjectName, "dest-object", "", "The name of the destination object.")
}

func main() {
	flag.Parse()

	var uploadId string

	if len(srcBucketName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, source bucket name required")
	}

	if len(region) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, region required")
	}

	if len(destBucketName) == 0 {
		destBucketName = srcBucketName
	}

	if len(srcObjectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, source object name required")
	}

	if len(destObjectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, destination object name required")
	}

	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	client := oss.NewClient(cfg)

	// Step 1: Initialize the multipart upload to get an upload ID.
	initRequest := &oss.InitiateMultipartUploadRequest{
		Bucket: oss.Ptr(destBucketName),
		Key:    oss.Ptr(destObjectName),
	}
	initResult, err := client.InitiateMultipartUpload(context.TODO(), initRequest)
	if err != nil {
		log.Fatalf("failed to initiate multipart upload: %v", err)
	}

	log.Printf("upload ID: %#v\n", *initResult.UploadId)
	uploadId = *initResult.UploadId

	// Step 2: Copy parts concurrently. Each goroutine handles one part.
	var wg sync.WaitGroup
	var parts []oss.UploadPart
	var mu sync.Mutex
	count := 3

	for i := 0; i < count; i++ {
		wg.Add(1)
		go func(partNumber int) {
			defer wg.Done()

			partRequest := &oss.UploadPartCopyRequest{
				Bucket:          oss.Ptr(destBucketName),
				Key:             oss.Ptr(destObjectName),
				SourceBucket:    oss.Ptr(srcBucketName),
				SourceKey:       oss.Ptr(srcObjectName),
				SourceVersionId: oss.Ptr("yourVersionId"), // Replace with the actual version ID.
				PartNumber:      int32(partNumber),
				UploadId:        oss.Ptr(uploadId),
			}

			partResult, err := client.UploadPartCopy(context.TODO(), partRequest)
			if err != nil {
				log.Fatalf("failed to upload part copy %d: %v", partNumber, err)
			}

			mu.Lock()
			parts = append(parts, oss.UploadPart{
				PartNumber: partRequest.PartNumber,
				ETag:       partResult.ETag,
			})
			mu.Unlock()
		}(i + 1)
	}

	wg.Wait()

	// Step 3: Complete the multipart upload. OSS assembles the parts in order.
	request := &oss.CompleteMultipartUploadRequest{
		Bucket:   oss.Ptr(destBucketName),
		Key:      oss.Ptr(destObjectName),
		UploadId: oss.Ptr(uploadId),
		CompleteMultipartUpload: &oss.CompleteMultipartUpload{
			Parts: parts,
		},
	}
	result, err := client.CompleteMultipartUpload(context.TODO(), request)
	if err != nil {
		log.Fatalf("failed to complete multipart upload: %v", err)
	}

	// VersionId is the version ID assigned to the assembled object.
	log.Printf("complete multipart upload result versionId: %#v\n", *result.VersionId)
}

API reference