# AWS Go SDK

<!-- -->

This guide assumes that you have followed the steps in the [Getting Started](/docs/get-started/.md) guide, and have the access keys available.

You may continue to use the AWS Go SDK as you normally would, but with the endpoint set to Tigris at <https://t3.storage.dev>.

This example uses the [AWS Go SDK v2](https://github.com/aws/aws-sdk-go-v2) and reads the default credentials file or the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.

When you create your client, make sure to enable the following settings:

* `BaseEndpoint` should be set to `https://t3.storage.dev`.
* `Region` should be set to `auto`.
* `UsePathStyle` should be set to `false`.

```
svc := s3.NewFromConfig(sdkConfig, func(o *s3.Options) {

  o.BaseEndpoint = aws.String("https://t3.storage.dev")

  o.Region = "auto"

  o.UsePathStyle = false

})
```

## Getting started[​](#getting-started "Direct link to Getting started")

This shows off a few basic operations with the AWS Go SDK such as PutObject, ListObjectsV2, and GetObject.

<!-- -->

```
package main



import (

	"context"

	"flag"

	"fmt"

	"log"

	"os"

	"time"



	"github.com/aws/aws-sdk-go-v2/aws"

	"github.com/aws/aws-sdk-go-v2/config"

	"github.com/aws/aws-sdk-go-v2/service/s3"

)



func main() {

	flag.Parse()

	if flag.NArg() != 1 {

		log.Fatalf("usage: %s <bucket>", flag.Arg(0))

	}



	bucketName := flag.Arg(0)



	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

	defer cancel()



	sdkConfig, err := config.LoadDefaultConfig(ctx)

	if err != nil {

		log.Printf("Couldn't load default configuration. Here's why: %v", err)

		return

	}



	// Create S3 service client

	svc := s3.NewFromConfig(sdkConfig, func(o *s3.Options) {

		o.BaseEndpoint = aws.String("https://t3.storage.dev")

		o.Region = "auto"

		o.UsePathStyle = false

		o.DisableLogOutputChecksumValidationSkipped = true

	})



	// List buckets

	result, err := svc.ListBuckets(ctx, &s3.ListBucketsInput{})

	if err != nil {

		log.Printf("Unable to list buckets. Here's why: %v", err)

		return

	}



	fmt.Println("Buckets:")



	for _, b := range result.Buckets {

		fmt.Printf("* %s created on %s\n",

			aws.ToString(b.Name), aws.ToTime(b.CreationDate))

	}



	// Upload file

	fmt.Println("Upload file:")



	os.WriteFile("bar.txt", []byte("Hello, World!"), 0644)



	file, err := os.Open("bar.txt")

	if err != nil {

		log.Printf("Couldn't open file to upload. Here's why: %v\n", err)

		return

	}



	defer file.Close()

	_, err = svc.PutObject(ctx, &s3.PutObjectInput{

		Bucket: aws.String(bucketName),

		Key:    aws.String("bar.txt"),

		Body:   file,

	})

	if err != nil {

		log.Printf("Couldn't upload file. Here's why: %v\n", err)

		return

	}



	fmt.Println("File uploaded.")



	// List objects

	fmt.Println("List objects:")



	resp, err := svc.ListObjectsV2(ctx, &s3.ListObjectsV2Input{

		Bucket: aws.String(bucketName),

	})

	if err != nil {

		log.Printf("Unable to list objects. Here's why: %v", err)

		return

	}



	if len(resp.Contents) == 0 {

		log.Printf("No objects found in bucket: %s", bucketName)

		return

	}



	fmt.Println("Objects:")

	for _, obj := range resp.Contents {

		fmt.Printf("* %s\n", aws.ToString(obj.Key))

	}



	// Download file

	fmt.Println("Download file:")



	file, err = os.Create("bar.txt")

	if err != nil {

		log.Printf("Couldn't create file to download. Here's why: %v", err)

		return

	}

	defer file.Close()



	_, err = svc.GetObject(ctx, &s3.GetObjectInput{

		Bucket: aws.String(bucketName),

		Key:    aws.String("bar.txt"),

	})

	if err != nil {

		log.Printf("Couldn't download file. Here's why: %v", err)

		return

	}

	fmt.Println("File downloaded.")

}
```

Run it with:

```
go run main.go bucket-name
```

## Conditional operations[​](#conditional-operations "Direct link to Conditional operations")

Below is an example of how to use the AWS Go SDK to [perform conditional operations](/docs/objects/conditionals/.md) on objects in Tigris. The example reads an object, modifies it, and then writes it back to the bucket. The write operation is conditional on the object not being modified since it was read.

<!-- -->

```
package main



import (

	"bytes"

	"context"

	"flag"

	"io"

	"log"



	"github.com/aws/aws-sdk-go-v2/aws"

	"github.com/aws/aws-sdk-go-v2/config"

	"github.com/aws/aws-sdk-go-v2/service/s3"

	"github.com/aws/smithy-go/transport/http"

)



func WithHeader(key, value string) func(*s3.Options) {

	return func(options *s3.Options) {

		options.APIOptions = append(options.APIOptions, http.AddHeaderValue(key, value))

	}

}



func IfMatch(value string) func(*s3.Options) {

	return WithHeader("If-Match", value)

}



func main() {

	flag.Parse()



	if flag.NArg() != 1 {

		log.Fatalf("usage: %s <bucket>", flag.Arg(0))

	}

	bucketName := flag.Arg(0)

	keyName := "mykey"



	ctx, cancel := context.WithCancel(context.Background())

	defer cancel()



	cfg, err := config.LoadDefaultConfig(ctx)

	if err != nil {

		log.Printf("Couldn't load default configuration. Here's why: %v\n", err)

		return

	}



	// Create S3 service client

	client := s3.NewFromConfig(cfg, func(o *s3.Options) {

		o.BaseEndpoint = aws.String("https://t3.storage.dev")

		o.Region = "auto"

		o.UsePathStyle = false

	})



	// put an object into the bucket as a starting point

	_, err = client.PutObject(ctx,

		&s3.PutObjectInput{

			Bucket: aws.String(bucketName),

			Key:    aws.String(keyName),

			Body:   bytes.NewReader([]byte("hello world")),

		},

	)

	if err != nil {

		log.Fatalf("unable to put object: %v", err)

	}



	// read the object atomically

	out, err := client.GetObject(ctx,

		&s3.GetObjectInput{

			Bucket: aws.String(bucketName),

			Key:    aws.String(keyName),

		},

	)

	if err != nil {

		log.Fatalf("unable to read object: %v", err)

	}



	body, err := io.ReadAll(out.Body)

	if err != nil {

		log.Fatalf("unable to read object body: %v", err)

	}



	// write the object only if the etag matches the one we read

	out1, err := client.PutObject(ctx,

		&s3.PutObjectInput{

			Bucket: aws.String(bucketName),

			Key:    aws.String(keyName),

			Body:   bytes.NewBuffer(body),

		},

		WithHeader("If-Match", *out.ETag),

	)

	if err != nil {

		log.Fatalf("unable to put object, %v", err)

	}

	log.Printf("mykey etag is %s", *out1.ETag)

}
```

Run it with:

```
go run main.go bucket-name
```

## Using presigned URLs[​](#using-presigned-urls "Direct link to Using presigned URLs")

Presigned URLs can be used with the AWS Go SDK as follows:

<!-- -->

```
package main



import (

	"context"

	"flag"

	"fmt"

	"log"

	"os"

	"strings"

	"time"



	"github.com/aws/aws-sdk-go-v2/aws"

	v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"

	"github.com/aws/aws-sdk-go-v2/config"

	"github.com/aws/aws-sdk-go-v2/service/s3"

)



// Client encapsulates the S3 SDK presign client and provides methods to presign requests.

type Client struct {

	PresignClient *s3.PresignClient

}



// GetObject makes a presigned request that can be used to get an object from a bucket.

func (p *Client) GetObject(

	ctx context.Context,

	bucket string,

	key string,

	expiry time.Duration,

) (*v4.PresignedHTTPRequest, error) {

	request, err := p.PresignClient.PresignGetObject(ctx, &s3.GetObjectInput{

		Bucket: aws.String(bucket),

		Key:    aws.String(key),

	}, func(opts *s3.PresignOptions) {

		opts.Expires = expiry

	})

	if err != nil {

		log.Printf("Couldn't get a presigned request to get %v:%v. Here's why: %v\n",

			bucket, key, err)

	}

	return request, err

}



// PutObject makes a presigned request that can be used to put an object in a bucket.

func (p *Client) PutObject(

	ctx context.Context,

	bucket string,

	object string,

	expiry time.Duration,

) (*v4.PresignedHTTPRequest, error) {

	request, err := p.PresignClient.PresignPutObject(ctx, &s3.PutObjectInput{

		Bucket: aws.String(bucket),

		Key:    aws.String(object),

	}, func(opts *s3.PresignOptions) {

		opts.Expires = expiry

	})

	if err != nil {

		log.Printf("Couldn't get a presigned request to put %v:%v. Here's why: %v\n",

			bucket, object, err)

	}

	return request, err

}



// DeleteObject makes a presigned request that can be used to delete an object from a bucket.

func (p *Client) DeleteObject(ctx context.Context, bucket string, object string, expiry time.Duration) (*v4.PresignedHTTPRequest, error) {

	request, err := p.PresignClient.PresignDeleteObject(ctx, &s3.DeleteObjectInput{

		Bucket: aws.String(bucket),

		Key:    aws.String(object),

	}, func(opts *s3.PresignOptions) {

		opts.Expires = expiry

	})

	if err != nil {

		log.Printf("Couldn't get a presigned request to delete object %v. Here's why: %v\n", object, err)

	}

	return request, err

}



func main() {

	flag.Parse()

	if flag.NArg() != 1 {

		log.Fatalf("usage: %s <bucket>", os.Args[0])

	}



	bucketName := flag.Arg(0)



	ctx, cancel := context.WithCancel(context.Background())

	defer cancel()



	sdkConfig, err := config.LoadDefaultConfig(ctx)

	if err != nil {

		log.Printf("Couldn't load default configuration. Here's why: %v\n", err)

		return

	}



	// Create S3 service client

	svc := s3.NewFromConfig(sdkConfig, func(o *s3.Options) {

		o.BaseEndpoint = aws.String("https://t3.storage.dev")

		o.Region = "auto"

		o.UsePathStyle = false

	})



	// Presigning a request

	ps := s3.NewPresignClient(svc)

	presigner := &Client{PresignClient: ps}



	// Presigned URL to upload an object to the bucket

	presignedPutReq, err := presigner.PutObject(ctx, bucketName, "bar.txt", 60*time.Minute)

	if err != nil {

		log.Printf("Couldn't get a presigned request to put bar.txt. Here's why: %v\n", err)

	} else {

		fmt.Printf("Presigned URL for PUT: %s\n", presignedPutReq.URL)

	}



	// Presigned URL to download an object from the bucket

	presignedGetReq, err := presigner.GetObject(ctx, bucketName, "bar.txt", 60*time.Minute)

	if err != nil {

		log.Printf("Couldn't get a presigned request to get bar.txt. Here's why: %v\n", err)

		return

	}



	fmt.Printf("Presigned URL for GET: %s\n", presignedGetReq.URL)



	// Replace the bucket host with your custom domain before sharing

	brandedURL := strings.ReplaceAll(presignedGetReq.URL, bucketName+".t3.storage.dev", "your-domain.example.com")

	fmt.Printf("Presigned URL for GET (custom domain): %s\n", brandedURL)



	// Presigned URL to delete an object from the bucket

	presignedDeleteReq, err := presigner.DeleteObject(ctx, bucketName, "bar.txt", 60*time.Minute)

	if err != nil {

		log.Printf("Couldn't get a presigned request to delete bar.txt. Here's why: %v\n", err)

	} else {

		fmt.Printf("Presigned URL for DELETE: %s\n", presignedDeleteReq.URL)

	}

}
```

You can now use the URL returned by the `presignedPutReq.URL` and `presignedGetReq.URL` to upload or download objects.

Run it with:

```
go run main.go bucket-name
```

note

When you use presigned URLs, you must disable path-style requests in your S3 SDK configuration.

This will generate a virtual-hosted-style URL that is compatible with Tigris.

### Presigned URLs with custom domains[​](#presigned-urls-with-custom-domains "Direct link to Presigned URLs with custom domains")

You can also use a [presigned URL with a custom domain](/docs/objects/presigned/.md#presigned-url-with-custom-domain) by replacing the bucket host with your custom domain:

```
brandedURL := strings.ReplaceAll(

    presignedGetReq.URL, bucketName+".t3.storage.dev", "your-domain.example.com")

fmt.Printf("Presigned URL for GET (custom domain): %s\n", brandedURL)
```

## Metadata Querying[​](#metadata-querying "Direct link to Metadata Querying")

Below is an example of how to use [metadata querying](/docs/objects/query-metadata/.md) with the AWS Go SDK.

<!-- -->

```
package main



import (

	"bytes"

	"context"

	"flag"

	"fmt"

	"log"

	"time"



	"github.com/aws/aws-sdk-go-v2/aws"

	"github.com/aws/aws-sdk-go-v2/config"

	"github.com/aws/aws-sdk-go-v2/service/s3"

	"github.com/aws/smithy-go/transport/http"

)



func WithHeader(key, value string) func(*s3.Options) {

	return func(options *s3.Options) {

		options.APIOptions = append(options.APIOptions, http.AddHeaderValue(key, value))

	}

}



func main() {

	flag.Parse()

	if flag.NArg() != 1 {

		log.Fatalf("usage: %s <bucket>", flag.Arg(0))

	}



	bucketName := flag.Arg(0)

	keyName := "examplefile.js"



	ctx, cancel := context.WithCancel(context.Background())

	defer cancel()



	cfg, err := config.LoadDefaultConfig(ctx)

	if err != nil {

		log.Printf("Couldn't load default configuration. Here's why: %v\n", err)

		return

	}



	// Create S3 service client

	client := s3.NewFromConfig(cfg, func(o *s3.Options) {

		o.BaseEndpoint = aws.String("https://t3.storage.dev")

		o.Region = "auto"

		o.UsePathStyle = false

	})



	contentType := "text/javascript"



	fmt.Println("Putting object to the bucket:", keyName)



	// put a javascript file in the bucket

	_, err = client.PutObject(ctx, &s3.PutObjectInput{

		Bucket:      aws.String(bucketName),

		Key:         aws.String(keyName),

		Body:        bytes.NewBuffer([]byte("console.log('Hello, World!')")),

		ContentType: aws.String(contentType),

	})

	if err != nil {

		log.Fatalf("Unable to put object. Here's why: %v", err)

	}



	fmt.Println("Listing objects with Content-Type:", contentType)



	resp, err := client.ListObjectsV2(ctx,

		&s3.ListObjectsV2Input{

			Bucket: aws.String(bucketName),

		},

		WithHeader("X-Tigris-Query", fmt.Sprintf("`Last-Modified` < %q AND `Content-Type` = %q", time.Now().Format(time.RFC3339), contentType)),

	)

	if err != nil {

		log.Fatalf("unable to list objects: %v", err)

	}



	if len(resp.Contents) == 0 {

		log.Printf("No objects found with Content-Type: %s", contentType)

		return

	}



	for _, obj := range resp.Contents {

		fmt.Println("*", *obj.Key)

	}

}
```

## Object Notifications[​](#object-notifications "Direct link to Object Notifications")

If you want to be notified when an object is created or deleted in a bucket, use [object notifications](/docs/buckets/object-notifications/.md). This example shows you how to implement a server that listens for and parses object notifications so you can take action when an object is created or deleted.

<!-- -->

```
package main



import (

	"crypto/subtle"

	"encoding/json"

	"fmt"

	"io"

	"log"

	"net/http"

	"strings"

)



// ObjectNotificationReq is the parent object for object notification events.

type ObjectNotificationReq struct {

	Events []*ObjectNotificationEvent `json:"events"`

}



// ObjectNotificationEvent contains information about an object being created or deleted.

type ObjectNotificationEvent struct {

	EventVersion string       `json:"eventVersion"`

	EventSource  string       `json:"eventSource"`

	EventName    string       `json:"eventName"`

	EventTime    string       `json:"eventTime"`

	Bucket       string       `json:"bucket"`

	Object       *EventObject `json:"object"`

}



// EventObject contains the most important information about an object.

type EventObject struct {

	Key  string `json:"key"`

	Size int32  `json:"size"`

	ETag string `json:"eTag"`

}



// eventReciever is a simple http handler that receives object notification events.

//

// This does not do any validation or authentication on any incoming requests.

func eventReceiver(w http.ResponseWriter, r *http.Request) {

	body, err := io.ReadAll(r.Body)

	if err != nil {

		http.Error(w, "Error reading request body", http.StatusInternalServerError)

		return

	}

	defer r.Body.Close()



	var req ObjectNotificationReq

	err = json.Unmarshal(body, &req)

	if err != nil {

		http.Error(w, "Error unmarshalling request body", http.StatusInternalServerError)

		return

	}



	fmt.Println("Events:")

	for _, event := range req.Events {

		fmt.Printf("time: %v, event: %v, bucket: %v, key: %v\n", event.EventTime, event.EventName, event.Bucket, event.Object.Key)

	}



	fmt.Fprint(w, "ok")

}



// basicAuth is a HTTP middleware that checks for basic auth credentials in incoming requests against a static username and password.

//

// Usage:

//

//	http.HandleFunc("/basic-auth", basicAuth("user", "pass", eventReceiver))

func basicAuth(username, password string, next http.HandlerFunc) http.Handler {

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		user, pass, ok := r.BasicAuth()

		if !ok {

			w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)

			http.Error(w, "Unauthorized", http.StatusUnauthorized)

			return

		}



		if subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {

			http.Error(w, "Unauthorized", http.StatusUnauthorized)

			return

		}



		next.ServeHTTP(w, r)

	})

}



// tokenAuth is an HTTP middleware that checks if incoming requests have an API token matching a statically defined value.

//

// Usage:

//

//	http.HandleFunc("/token-auth", tokenAuth("secret-token-pass", eventReceiver))

func tokenAuth(token string, next http.HandlerFunc) http.HandlerFunc {

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		auth := r.Header.Get("Authorization")

		if auth == "" || !strings.HasPrefix(auth, "Bearer ") || subtle.ConstantTimeCompare([]byte(strings.TrimPrefix(auth, "Bearer ")), []byte(token)) != 1 {

			http.Error(w, "Unauthorized", http.StatusUnauthorized)

			return

		}

		fmt.Println("token auth successful")



		next.ServeHTTP(w, r)

	})

}



func main() {

	http.HandleFunc("/no-auth", eventReceiver)

	http.Handle("/basic-auth", basicAuth("user", "pass", eventReceiver))

	http.Handle("/token-auth", tokenAuth("secret-token-pass", eventReceiver))



	log.Println("Server running on http://localhost:8080")

	log.Fatal(http.ListenAndServe(":8080", nil))

}
```

## Renaming Objects[​](#renaming-objects "Direct link to Renaming Objects")

To rename an object using the AWS Go SDK, attach the `X-Tigris-Rename: true` header to a CopyObject request.

<!-- -->

```
package main



import (

	"bytes"

	"context"

	"flag"

	"fmt"

	"log"

	"time"



	"github.com/aws/aws-sdk-go-v2/aws"

	"github.com/aws/aws-sdk-go-v2/config"

	"github.com/aws/aws-sdk-go-v2/service/s3"

	"github.com/aws/smithy-go/transport/http"

)



func WithHeader(key, value string) func(*s3.Options) {

	return func(options *s3.Options) {

		options.APIOptions = append(options.APIOptions, http.AddHeaderValue(key, value))

	}

}



func WithRename() func(*s3.Options) {

	return func(options *s3.Options) {

		options.APIOptions = append(options.APIOptions, http.AddHeaderValue("X-Tigris-Rename", "true"))

	}

}



func main() {

	flag.Parse()

	if flag.NArg() != 1 {

		log.Fatalf("usage: %s <bucket>", flag.Arg(0))

	}



	bucketName := flag.Arg(0)



	// Use a timestamp suffix on both keys so re-runs (and concurrent CI

	// jobs sharing the bucket) cannot collide with leftover state from a

	// previous run that died between the rename and the cleanup delete.

	// Tigris rename is atomic and does NOT silently overwrite — a 409

	// KeyAlreadyExists from a stale destination key is the API working

	// as designed, not a bug to paper over.

	suffix := time.Now().UTC().Format("20060102-150405.000000000")

	keyName := fmt.Sprintf("examplefile-go-%s.js", suffix)

	targetName := fmt.Sprintf("examplefile-go-rename-%s.js", suffix)



	ctx, cancel := context.WithCancel(context.Background())

	defer cancel()



	cfg, err := config.LoadDefaultConfig(ctx)

	if err != nil {

		log.Printf("Couldn't load default configuration. Here's why: %v\n", err)

		return

	}



	// Create S3 service client

	client := s3.NewFromConfig(cfg, func(o *s3.Options) {

		o.BaseEndpoint = aws.String("https://t3.storage.dev")

		o.Region = "auto"

		o.UsePathStyle = false

	})



	contentType := "text/javascript"



	fmt.Println("Putting object to the bucket:", keyName)



	// put a javascript file in the bucket

	_, err = client.PutObject(ctx, &s3.PutObjectInput{

		Bucket:      aws.String(bucketName),

		Key:         aws.String(keyName),

		Body:        bytes.NewBuffer([]byte("console.log('Hello, World!')")),

		ContentType: aws.String(contentType),

	})

	if err != nil {

		log.Fatalf("Unable to put object. Here's why: %v", err)

	}



	fmt.Println("Renaming object in the bucket:", keyName)



	// rename the object in the bucket

	_, err = client.CopyObject(ctx, &s3.CopyObjectInput{

		Bucket:     aws.String(bucketName),

		CopySource: aws.String(bucketName + "/" + keyName),

		Key:        aws.String(targetName),

	}, WithRename())

	if err != nil {

		log.Fatalf("Unable to rename object. Here's why: %v", err)

	}



	fmt.Println("Object renamed successfully to:", targetName)



	fmt.Println("Deleting object from the bucket:", targetName)



	// delete the object from the bucket

	_, err = client.DeleteObject(ctx, &s3.DeleteObjectInput{

		Bucket: aws.String(bucketName),

		Key:    aws.String(targetName),

	})

	if err != nil {

		log.Fatalf("Unable to delete object. Here's why: %v", err)

	}



	fmt.Println("Object deleted successfully:", targetName)

}
```
