
## Installation

To use Rabata.io with Go, you'll need to install the AWS SDK for Go.

### Install AWS SDK

```bash
$ go get github.com/aws/aws-sdk-go-v2/aws
$ go get github.com/aws/aws-sdk-go-v2/config
$ go get github.com/aws/aws-sdk-go-v2/service/s3
```

For a new project, you can set up a basic structure:

```bash
$ mkdir my-rabata-project
$ cd my-rabata-project
$ go mod init example.com/my-rabata-project
$ go get github.com/aws/aws-sdk-go-v2/aws
$ go get github.com/aws/aws-sdk-go-v2/config
$ go get github.com/aws/aws-sdk-go-v2/service/s3
```

## Configuration

There are several ways to configure the AWS SDK for Go to work with Rabata.io.

### Method 1: Using AWS Credentials File

If you've already configured the AWS CLI as shown in the [AWS CLI Quickstart](/docs/quickstart/aws-cli/), the SDK will automatically use those credentials.

### Method 2: Explicit Configuration in Code

You can explicitly configure the S3 client in your code:

```go
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go-v2/aws"
	"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"
)

func main() {
	// Create a custom resolver that uses the Rabata.io endpoint
	customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
		return aws.Endpoint{
			URL:               "https://s3.us-east-1.rabata.io",
			SigningRegion:     "us-east-1",
			HostnameImmutable: true,
		}, nil
	})

	// Create a configuration with custom credentials and endpoint resolver
	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithRegion("us-east-1"),
		config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
			"YOUR_ACCESS_KEY",
			"YOUR_SECRET_KEY",
			"",
		)),
		config.WithEndpointResolverWithOptions(customResolver),
	)
	if err != nil {
		log.Fatalf("unable to load SDK config, %v", err)
	}

	// Create an S3 client
	client := s3.NewFromConfig(cfg)

	// Now you can use the client to interact with Rabata.io
	// ...
}
```

### Method 3: Using Environment Variables

You can set environment variables to configure the SDK:

```bash
# Set these environment variables before running your Go program
export AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
export AWS_REGION=us-east-1
```

Then in your code:

```go
package main

import (
	"context"
	"fmt"
	"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"
)

func main() {
	// Create a custom resolver that uses the Rabata.io endpoint
	customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
		return aws.Endpoint{
			URL:               "https://s3.us-east-1.rabata.io",
			SigningRegion:     "us-east-1",
			HostnameImmutable: true,
		}, nil
	})

	// Load configuration from environment variables
	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithEndpointResolverWithOptions(customResolver),
	)
	if err != nil {
		log.Fatalf("unable to load SDK config, %v", err)
	}

	// Create an S3 client
	client := s3.NewFromConfig(cfg)

	// Now you can use the client to interact with Rabata.io
	// ...
}
```

> **Security Note:** Never hardcode your credentials in your source code, especially if it's stored in a version control system. Use environment variables, AWS credentials file, or a secure secrets management system.

## Basic Operations

Here are some common operations you can perform with the AWS SDK for Go and Rabata.io.

### Bucket Operations

#### List All Buckets

```go
package main

import (
	"context"
	"fmt"
	"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"
)

func main() {
	// Create a custom resolver that uses the Rabata.io endpoint
	customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
		return aws.Endpoint{
			URL:               "https://s3.us-east-1.rabata.io",
			SigningRegion:     "us-east-1",
			HostnameImmutable: true,
		}, nil
	})

	// Load configuration
	cfg, err := config.LoadDefaultConfig(context.TODO(),
		config.WithRegion("us-east-1"),
		config.WithEndpointResolverWithOptions(customResolver),
	)
	if err != nil {
		log.Fatalf("unable to load SDK config, %v", err)
	}

	// Create an S3 client
	client := s3.NewFromConfig(cfg)

	// List buckets
	result, err := client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
	if err != nil {
		log.Fatalf("unable to list buckets, %v", err)
	}

	fmt.Println("Buckets:")
	for _, bucket := range result.Buckets {
		fmt.Printf("  %s\n", *bucket.Name)
	}
}
```

### Note on Older Go Libraries

If you're using older or alternative Go S3 libraries like goamz (not the official aws-sdk-go), you may need to explicitly configure signature version 4 in your client configuration. Rabata.io requires signature v4 for authentication. The modern AWS SDK for Go v2 automatically uses signature v4 for custom S3-compatible endpoints.

#### Create a Bucket

```go
// Create a bucket
bucketName := "my-bucket-name"
_, err = client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
	Bucket: aws.String(bucketName),
})
if err != nil {
	log.Fatalf("unable to create bucket, %v", err)
}

fmt.Printf("Bucket created: %s\n", bucketName)
```

#### Delete a Bucket

```go
// Delete a bucket
bucketName := "my-bucket-name"
_, err = client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{
	Bucket: aws.String(bucketName),
})
if err != nil {
	log.Fatalf("unable to delete bucket, %v", err)
}

fmt.Printf("Bucket deleted: %s\n", bucketName)
```

Note: The bucket must be empty before it can be deleted.

### Object Operations

#### List Objects in a Bucket

```go
// List objects in a bucket
bucketName := "my-bucket-name"
result, err := client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
	Bucket: aws.String(bucketName),
})
if err != nil {
	log.Fatalf("unable to list objects, %v", err)
}

fmt.Printf("Objects in bucket %s:\n", bucketName)
for _, object := range result.Contents {
	fmt.Printf("  %s (%d bytes)\n", *object.Key, object.Size)
}
```

#### Upload a File

```go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"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() {
	// ... configuration code ...

	// Create an S3 client
	client := s3.NewFromConfig(cfg)

	// Upload a file
	bucketName := "my-bucket-name"
	key := "remote-file.txt"
	localFile := "local-file.txt"

	file, err := os.Open(localFile)
	if err != nil {
		log.Fatalf("unable to open file %s, %v", localFile, err)
	}
	defer file.Close()

	_, err = client.PutObject(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(key),
		Body:   file,
	})
	if err != nil {
		log.Fatalf("unable to upload file, %v", err)
	}

	fmt.Printf("File uploaded: %s to %s/%s\n", localFile, bucketName, key)
}
```

#### Download a File

```go
// Download a file
bucketName := "my-bucket-name"
key := "remote-file.txt"
localFile := "local-file.txt"

result, err := client.GetObject(context.TODO(), &s3.GetObjectInput{
	Bucket: aws.String(bucketName),
	Key:    aws.String(key),
})
if err != nil {
	log.Fatalf("unable to download file, %v", err)
}
defer result.Body.Close()

file, err := os.Create(localFile)
if err != nil {
	log.Fatalf("unable to create file %s, %v", localFile, err)
}
defer file.Close()

_, err = io.Copy(file, result.Body)
if err != nil {
	log.Fatalf("unable to write file, %v", err)
}

fmt.Printf("File downloaded: %s/%s to %s\n", bucketName, key, localFile)
```

#### Delete a File

```go
// Delete a file
bucketName := "my-bucket-name"
key := "file-to-delete.txt"

_, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{
	Bucket: aws.String(bucketName),
	Key:    aws.String(key),
})
if err != nil {
	log.Fatalf("unable to delete file, %v", err)
}

fmt.Printf("File deleted: %s/%s\n", bucketName, key)
```

## Advanced Operations

Here are some more advanced operations you can perform with the AWS SDK for Go and Rabata.io.

### Working with Object Metadata

```go
// Upload a file with metadata
bucketName := "my-bucket-name"
key := "file-with-metadata.txt"
content := []byte("Hello, World!")

_, err = client.PutObject(context.TODO(), &s3.PutObjectInput{
	Bucket: aws.String(bucketName),
	Key:    aws.String(key),
	Body:   bytes.NewReader(content),
	Metadata: map[string]string{
		"custom-key":   "custom-value",
		"content-type": "text/plain",
	},
})
if err != nil {
	log.Fatalf("unable to upload file with metadata, %v", err)
}

fmt.Printf("File uploaded with metadata: %s/%s\n", bucketName, key)
```
<!--
### Setting Object ACLs

```go
// Upload a public file
bucketName := "my-bucket-name"
key := "public-file.txt"
content := []byte("This is a public file")

_, err = client.PutObject(context.TODO(), &s3.PutObjectInput{
	Bucket: aws.String(bucketName),
	Key:    aws.String(key),
	Body:   bytes.NewReader(content),
	ACL:    types.ObjectCannedACLPublicRead,
})
if err != nil {
	log.Fatalf("unable to upload public file, %v", err)
}

fmt.Printf("Public file uploaded: %s/%s\n", bucketName, key)
``` -->

### Using Presigned URLs

Generate a presigned URL to allow temporary access to an object:

```go
package main

import (
	"context"
	"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/aws-sdk-go-v2/service/s3/presign"
)

func main() {
	// ... configuration code ...

	// Create an S3 client
	client := s3.NewFromConfig(cfg)

	// Create a presigner
	presigner := presign.NewPresigner(client)

	// Generate a presigned URL
	bucketName := "my-bucket-name"
	key := "private-file.txt"

	request, err := presigner.PresignGetObject(context.TODO(), &s3.GetObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(key),
	}, func(opts *presign.GetObjectOptions) {
		opts.Expires = time.Hour // URL expires in 1 hour
	})
	if err != nil {
		log.Fatalf("unable to generate presigned URL, %v", err)
	}

	fmt.Printf("Presigned URL: %s\n", request.URL)
}
```

### Multipart Uploads

For large files, you can use multipart uploads:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"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/aws-sdk-go-v2/service/s3/types"
)

func main() {
	// ... configuration code ...

	// Create an S3 client
	client := s3.NewFromConfig(cfg)

	// Start a multipart upload
	bucketName := "my-bucket-name"
	key := "large-file.txt"

	createResp, err := client.CreateMultipartUpload(context.TODO(), &s3.CreateMultipartUploadInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(key),
	})
	if err != nil {
		log.Fatalf("unable to create multipart upload, %v", err)
	}

	uploadID := createResp.UploadId

	// Upload parts
	// In a real application, you would split your file into parts and upload each part
	// Here's a simplified example:

	file, err := os.Open("large-file.txt")
	if err != nil {
		log.Fatalf("unable to open file, %v", err)
	}
	defer file.Close()

	// Get file info
	fileInfo, err := file.Stat()
	if err != nil {
		log.Fatalf("unable to get file info, %v", err)
	}

	// Calculate part size (5MB minimum)
	partSize := int64(5 * 1024 * 1024) // 5MB
	numParts := fileInfo.Size() / partSize
	if fileInfo.Size()%partSize != 0 {
		numParts++
	}

	// Upload each part
	var completedParts []types.CompletedPart
	for i := int64(1); i <= numParts; i++ {
		// Read part data
		partBuffer := make([]byte, partSize)
		n, err := file.Read(partBuffer)
		if err != nil && err != io.EOF {
			log.Fatalf("unable to read file part, %v", err)
		}
		if n == 0 {
			break
		}

		// Upload part
		uploadResp, err := client.UploadPart(context.TODO(), &s3.UploadPartInput{
			Bucket:     aws.String(bucketName),
			Key:        aws.String(key),
			UploadId:   uploadID,
			PartNumber: aws.Int32(int32(i)),
			Body:       bytes.NewReader(partBuffer[:n]),
		})
		if err != nil {
			// Abort the upload on error
			_, abortErr := client.AbortMultipartUpload(context.TODO(), &s3.AbortMultipartUploadInput{
				Bucket:   aws.String(bucketName),
				Key:      aws.String(key),
				UploadId: uploadID,
			})
			if abortErr != nil {
				log.Printf("unable to abort multipart upload, %v", abortErr)
			}
			log.Fatalf("unable to upload part, %v", err)
		}

		// Add the completed part to our list
		completedParts = append(completedParts, types.CompletedPart{
			ETag:       uploadResp.ETag,
			PartNumber: aws.Int32(int32(i)),
		})
	}

	// Complete the multipart upload
	_, err = client.CompleteMultipartUpload(context.TODO(), &s3.CompleteMultipartUploadInput{
		Bucket:   aws.String(bucketName),
		Key:      aws.String(key),
		UploadId: uploadID,
		MultipartUpload: &types.CompletedMultipartUpload{
			Parts: completedParts,
		},
	})
	if err != nil {
		log.Fatalf("unable to complete multipart upload, %v", err)
	}

	fmt.Printf("Large file uploaded: %s/%s\n", bucketName, key)
}
```

## Error Handling

It's important to handle errors properly when working with S3:

```go
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"

	"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"
)

func main() {
	// ... configuration code ...

	// Create an S3 client
	client := s3.NewFromConfig(cfg)

	// Try to get an object
	bucketName := "my-bucket-name"
	key := "my-object.txt"
	localFile := "local-file.txt"

	result, err := client.GetObject(context.TODO(), &s3.GetObjectInput{
		Bucket: aws.String(bucketName),
		Key:    aws.String(key),
	})
	if err != nil {
		// Check for specific error types
		var apiErr smithy.APIError
		if errors.As(err, &apiErr) {
			switch apiErr.ErrorCode() {
			case "NoSuchKey":
				fmt.Printf("The object %s does not exist.\n", key)
			case "NoSuchBucket":
				fmt.Printf("The bucket %s does not exist.\n", bucketName)
			default:
				fmt.Printf("API error: %s\n", apiErr.ErrorMessage())
			}
		} else {
			fmt.Printf("An error occurred: %v\n", err)
		}
		return
	}
	defer result.Body.Close()

	// Create a local file
	file, err := os.Create(localFile)
	if err != nil {
		log.Fatalf("unable to create file %s, %v", localFile, err)
	}
	defer file.Close()

	// Copy the object content to the local file
	_, err = io.Copy(file, result.Body)
	if err != nil {
		log.Fatalf("unable to write file, %v", err)
	}

	fmt.Printf("File downloaded: %s/%s to %s\n", bucketName, key, localFile)
}
```
