Upload objects (Go SDK V2)

Updated at:
Copy as MD

Upload objects to a versioning-enabled bucket using OSS Go SDK V2. This topic covers three upload methods — simple upload, append upload, and multipart upload — and explains how versioning affects each.

Prerequisites

Before you begin, make sure you have:

Usage notes

  • All examples use the China (Hangzhou) region (cn-hangzhou) with a public endpoint. To access OSS from another Alibaba Cloud service in the same region, use an internal endpoint. For details, see Regions and endpoints.

  • Credentials are read from environment variables in all examples.

How versioning affects uploads

The bucket's versioning state determines what version ID OSS assigns to each uploaded object:

Upload methodVersioning-enabled bucketVersioning-suspended bucket
Simple upload (PutObject)OSS generates a unique version ID and returns it in the x-oss-version-id response headerOSS assigns a null version ID; uploading an object with the same name overwrites the existing object. As a result, each object has only a single version with a null version ID
Append upload (AppendObject)Only the current version of an appendable object can be appended to; previous versions cannotNot applicable (AppendObject versioning-suspended behavior is not documented)
Multipart upload (CompleteMultipartUpload)OSS generates a unique version ID and returns it in the x-oss-version-id response headerNot applicable (multipart upload versioning-suspended behavior is not documented)

Simple upload

Use PutObject to upload an object in a single request. OSS returns the assigned version ID in the response.

package main

import (
	"context"
	"flag"
	"log"
	"strings"

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

var (
	region     string
	bucketName string
	objectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	flag.Parse()

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

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

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

	content := "hi oss"

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

	client := oss.NewClient(cfg)

	request := &oss.PutObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectName),
		Body:   strings.NewReader(content),
	}

	result, err := client.PutObject(context.TODO(), request)
	if err != nil {
		log.Fatalf("failed to put object %v", err)
	}

	// Print the version ID assigned to the uploaded object.
	log.Printf("put object result versionId:%#v\n", *result.VersionId)
}

Append upload

Use AppendObject to append data to an existing appendable object. Each call returns the position for the next append operation.

In a versioning-enabled bucket, AppendObject can only be performed on the current version of an appendable object:

  • Appending to the current version does not create a previous version of the object.

  • Calling PutObject or DeleteObject on the current version of an appendable object saves it as a non-appendable previous version.

  • AppendObject cannot be called on the current version of a non-appendable object, such as a normal object or a delete marker.

package main

import (
	"context"
	"flag"
	"log"
	"strings"

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

var (
	region     string
	bucketName string
	objectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	flag.Parse()

	var position = int64(0)

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

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

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

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

	client := oss.NewClient(cfg)

	content := "hi append object"

	// First append: position is 0. The response contains the position for the next append.
	request := &oss.AppendObjectRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		Position: oss.Ptr(position),
		Body:     strings.NewReader(content),
		Tagging:  oss.Ptr("tag1=value1&tag2=value2"),
	}

	result, err := client.AppendObject(context.TODO(), request)
	if err != nil {
		log.Fatalf("failed to append object %v", err)
	}

	// Second append: use NextPosition from the previous response.
	request = &oss.AppendObjectRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		Position: oss.Ptr(result.NextPosition),
		Body:     strings.NewReader("hi append object"),
	}

	result, err = client.AppendObject(context.TODO(), request)
	if err != nil {
		log.Fatalf("failed to append object %v", err)
	}

	// Print the version ID of the appendable object.
	log.Printf("append object result:%#v\n", *result.VersionId)
}

Multipart upload

Use multipart upload for large objects. The process has three steps: initiate, upload parts in parallel, and complete. When CompleteMultipartUpload succeeds in a versioning-enabled bucket, OSS generates a unique version ID for the assembled object.

package main

import (
	"bufio"
	"bytes"
	"context"
	"flag"
	"io"
	"log"
	"os"
	"sync"

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

var (
	region     string
	bucketName string
	objectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	flag.Parse()

	var uploadId string

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

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

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

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

	client := oss.NewClient(cfg)

	// Step 1: Initiate the multipart upload and get an upload ID.
	initRequest := &oss.InitiateMultipartUploadRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectName),
	}
	initResult, err := client.InitiateMultipartUpload(context.TODO(), initRequest)
	if err != nil {
		log.Fatalf("failed to initiate multipart upload %v", err)
	}
	log.Printf("initiate multipart upload result:%#v\n", *initResult.UploadId)
	uploadId = *initResult.UploadId

	// Step 2: Upload parts concurrently using goroutines.
	// Replace yourLocalFile with the actual path to your local file.
	file, err := os.Open("yourLocalFile")
	if err != nil {
		log.Fatalf("failed to open local file %v", err)
	}
	defer file.Close()

	bufReader := bufio.NewReader(file)
	content, err := io.ReadAll(bufReader)
	if err != nil {
		log.Fatalf("failed to read local file %v", err)
	}
	log.Printf("file size: %d\n", len(content))

	var wg sync.WaitGroup
	var parts []oss.UploadPart
	var mu sync.Mutex
	count := 3
	chunkSize := len(content) / count
	if chunkSize == 0 {
		chunkSize = 1
	}

	for i := 0; i < count; i++ {
		start := i * chunkSize
		end := start + chunkSize
		if i == count-1 {
			end = len(content)
		}

		wg.Add(1)
		go func(partNumber int, start, end int) {
			defer wg.Done()

			partRequest := &oss.UploadPartRequest{
				Bucket:     oss.Ptr(bucketName),
				Key:        oss.Ptr(objectName),
				PartNumber: int32(partNumber),
				UploadId:   oss.Ptr(uploadId),
				Body:       bytes.NewReader(content[start:end]),
			}

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

			part := oss.UploadPart{
				PartNumber: partRequest.PartNumber,
				ETag:       partResult.ETag,
			}

			mu.Lock()
			parts = append(parts, part)
			mu.Unlock()
		}(i+1, start, end)
	}

	wg.Wait()

	// Step 3: Complete the multipart upload. OSS returns a version ID for the assembled object.
	request := &oss.CompleteMultipartUploadRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		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)
	}

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

API reference