package s3 import ( "context" "io" "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" ) // Client wraps the AWS S3 client for vaultik operations type Client struct { s3Client *s3.Client bucket string prefix string } // Config contains S3 client configuration type Config struct { Endpoint string Bucket string Prefix string AccessKeyID string SecretAccessKey string Region string } // NewClient creates a new S3 client func NewClient(ctx context.Context, cfg Config) (*Client, error) { // Create AWS config awsCfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(cfg.Region), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider( cfg.AccessKeyID, cfg.SecretAccessKey, "", )), ) if err != nil { return nil, err } // Configure custom endpoint if provided s3Opts := func(o *s3.Options) { if cfg.Endpoint != "" { o.BaseEndpoint = aws.String(cfg.Endpoint) o.UsePathStyle = true } } s3Client := s3.NewFromConfig(awsCfg, s3Opts) return &Client{ s3Client: s3Client, bucket: cfg.Bucket, prefix: cfg.Prefix, }, nil } // PutObject uploads an object to S3 func (c *Client) PutObject(ctx context.Context, key string, data io.Reader) error { fullKey := c.prefix + key _, err := c.s3Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: aws.String(c.bucket), Key: aws.String(fullKey), Body: data, }) return err } // GetObject downloads an object from S3 func (c *Client) GetObject(ctx context.Context, key string) (io.ReadCloser, error) { fullKey := c.prefix + key result, err := c.s3Client.GetObject(ctx, &s3.GetObjectInput{ Bucket: aws.String(c.bucket), Key: aws.String(fullKey), }) if err != nil { return nil, err } return result.Body, nil } // DeleteObject removes an object from S3 func (c *Client) DeleteObject(ctx context.Context, key string) error { fullKey := c.prefix + key _, err := c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: aws.String(c.bucket), Key: aws.String(fullKey), }) return err } // ListObjects lists objects with the given prefix func (c *Client) ListObjects(ctx context.Context, prefix string) ([]string, error) { fullPrefix := c.prefix + prefix var keys []string paginator := s3.NewListObjectsV2Paginator(c.s3Client, &s3.ListObjectsV2Input{ Bucket: aws.String(c.bucket), Prefix: aws.String(fullPrefix), }) for paginator.HasMorePages() { page, err := paginator.NextPage(ctx) if err != nil { return nil, err } for _, obj := range page.Contents { if obj.Key != nil { // Remove the base prefix from the key key := *obj.Key if len(key) > len(c.prefix) { key = key[len(c.prefix):] } keys = append(keys, key) } } } return keys, nil } // HeadObject checks if an object exists func (c *Client) HeadObject(ctx context.Context, key string) (bool, error) { fullKey := c.prefix + key _, err := c.s3Client.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: aws.String(c.bucket), Key: aws.String(fullKey), }) if err != nil { // Check if it's a not found error // TODO: Add proper error type checking return false, nil } return true, nil }