go

AWS SDK for Go v2

The Go client, with the transfer manager for large objects

Install

shell
go get github.com/aws/aws-sdk-go-v2/config \
       github.com/aws/aws-sdk-go-v2/credentials \
       github.com/aws/aws-sdk-go-v2/service/s3 \
       github.com/aws/aws-sdk-go-v2/feature/s3/manager

Configure

Configure the endpoint, region, credential and bucket in this tool’s vocabulary. Path-style addressing is recommended for dotted bucket names and clients without nested wildcard TLS, but virtual-hosted addressing is also supported.

In AWS SDK for Go v2Set it to
o.BaseEndpointhttps://s3.canada.popcloud.ca
config.WithRegioncanada
credentials.NewStaticCredentialsProviderPCAK00EXAMPLEKEYID00
credentials.NewStaticCredentialsProvider<your secret access key>
o.UsePathStyletrue
Environment
example values
export AWS_ACCESS_KEY_ID=PCAK00EXAMPLEKEYID00
export AWS_SECRET_ACCESS_KEY=<your secret access key>
export AWS_ENDPOINT_URL=https://s3.canada.popcloud.ca
export AWS_REGION=canada
export POPCLOUD_BUCKET=pc-your-org-media

These are example values. Sign in and every snippet on this site fills in with your own endpoint, key and bucket.

Go
func newClient(ctx context.Context) (*s3.Client, error) {
	cfg, err := config.LoadDefaultConfig(ctx,
        config.WithRegion(os.Getenv("AWS_REGION")), // canada
		config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
			os.Getenv("AWS_ACCESS_KEY_ID"),
			os.Getenv("AWS_SECRET_ACCESS_KEY"),
			"", // no session token: PopCloud credentials are long-lived
		)),
	)
	if err != nil {
		return nil, err
	}

	return s3.NewFromConfig(cfg, func(o *s3.Options) {
		o.BaseEndpoint = aws.String(os.Getenv("AWS_ENDPOINT_URL")) // https://s3.<region>.popcloud.ca
		// Recommended for predictable custom-endpoint behavior. Virtual-hosted
		// requests are supported too for ordinary single-label bucket names.
		o.UsePathStyle = true
	}), nil
}

Use it

Upload an object

Go
func upload(ctx context.Context, client *s3.Client, bucket, key string, body []byte) error {
	_, err := client.PutObject(ctx, &s3.PutObjectInput{
		Bucket:      aws.String(bucket),
		Key:         aws.String(key),
		Body:        bytes.NewReader(body),
		ContentType: aws.String("text/plain"),
	})
	return err
}

Upload a large object

Go
// The uploader handles multipart, concurrency and retries — use it whenever you
// do not control the size.
func uploadLarge(ctx context.Context, client *s3.Client, bucket, key string, r io.Reader) error {
	uploader := manager.NewUploader(client, func(u *manager.Uploader) {
		u.PartSize = 8 * 1024 * 1024
		u.Concurrency = 4
	})
	_, err := uploader.Upload(ctx, &s3.PutObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
		Body:   r,
	})
	return err
}

Download an object

Go
func download(ctx context.Context, client *s3.Client, bucket, key string) ([]byte, error) {
	out, err := client.GetObject(ctx, &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	})
	if err != nil {
		return nil, err
	}
	defer out.Body.Close()
	return io.ReadAll(out.Body)
}

List a prefix

Go
func listPrefix(ctx context.Context, client *s3.Client, bucket, prefix string) ([]string, error) {
	var keys []string
	pages := s3.NewListObjectsV2Paginator(client, &s3.ListObjectsV2Input{
		Bucket: aws.String(bucket),
		Prefix: aws.String(prefix),
	})
	for pages.HasMorePages() {
		page, err := pages.NextPage(ctx)
		if err != nil {
			return nil, err
		}
		for _, obj := range page.Contents {
			keys = append(keys, aws.ToString(obj.Key))
		}
	}
	return keys, nil
}

Presign a URL

Go
func presign(ctx context.Context, client *s3.Client, bucket, key string) (string, error) {
	signer := s3.NewPresignClient(client)
	req, err := signer.PresignGetObject(ctx, &s3.GetObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	}, s3.WithPresignExpires(time.Hour)) // 7 days is the hard maximum
	if err != nil {
		return "", err
	}
	// Anyone holding req.URL can read the object until it expires, with no
	// credentials at all.
	return req.URL, nil
}

Delete an object

Go
func remove(ctx context.Context, client *s3.Client, bucket, key string) error {
	_, err := client.DeleteObject(ctx, &s3.DeleteObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
	})
	return err
}

What to watch for

These apply to AWS SDK for Go v2 specifically. The full list covers the platform.

You’ll see
405 MethodNotAllowed from CreateBucket or DeleteBucket — `aws s3 mb`, `mc mb`, or a tool provisioning its own bucket on first run.
Why
Bucket lifecycle belongs to the control plane, which also allocates the storage account, the data-plane key and the CORS rules that come with it. Letting the edge create buckets would create half of one.
Instead
Create buckets in the dashboard, or with `POST /v1/buckets` on the control-plane API. Everything object-level then works normally against that bucket.

How this page is kept true

Every snippet above was extracted from examples/go/aws-sdk-go-v2, a program that uploads, downloads, compares bytes, lists, presigns and cleans up after itself. It runs in CI against a live sandbox organisation via make docs-verify-go. If it stops passing, this page is wrong and we treat that as a bug in the product.

A few snippets on this page are marked not run in CI — they need a browser, a second provider, or a configuration change the sandbox cannot make. Those are reviewed by hand.

Not yet run against the live sandbox — the runner is wired, the first verified run stamps this line.