Skip to content

Commit

Permalink
tigris: helper functions for dealing with Tigris
Browse files Browse the repository at this point in the history
Signed-off-by: Xe Iaso <[email protected]>
  • Loading branch information
Xe committed May 25, 2024
1 parent 3610461 commit 8c290e7
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 50 deletions.
109 changes: 95 additions & 14 deletions cmd/uploud/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package main

import (
"context"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"flag"
"fmt"
"image"
Expand All @@ -20,6 +23,7 @@ import (
"github.com/disintegration/imaging"
"within.website/x/internal"
"within.website/x/internal/avif"
"within.website/x/tigris"
)

var (
Expand Down Expand Up @@ -226,7 +230,7 @@ func main() {
log.Fatal(err)
}

s3c, err := internal.TigrisClient(context.Background())
s3c, err := tigris.Client(context.Background())
if err != nil {
log.Fatal(err)
}
Expand All @@ -239,24 +243,101 @@ func main() {
}
defer fin.Close()

_, err = s3c.PutObject(ctx, &s3.PutObjectInput{
Body: fin,
Bucket: b2Bucket,
Key: aws.String(flag.Arg(1) + "/" + finfo.Name()),
ContentType: aws.String(mimeTypes[filepath.Ext(finfo.Name())]),
})
st, err := fin.Stat()
if err != nil {
log.Fatal(err)
}

shaSum, err := hashFileSha256(fin)
if err != nil {
log.Fatal(err)
}

md5Sum, err := hashFileMD5(fin)
if err != nil {
log.Fatal(err)
}

_, err = s3c.PutObject(ctx,
&s3.PutObjectInput{
Body: fin,
Bucket: b2Bucket,
Key: aws.String(flag.Arg(1) + "/" + finfo.Name()),
ContentType: aws.String(mimeTypes[filepath.Ext(finfo.Name())]),
ContentLength: aws.Int64(st.Size()),
ChecksumSHA256: aws.String(shaSum),
ContentMD5: aws.String(md5Sum),
},
tigris.WithCreateObjectIfNotExists(),
)
if err != nil {
log.Fatal(err)
}
}
}

var mimeTypes = map[string]string{
".avif": "image/avif",
".webp": "image/webp",
".jpg": "image/jpeg",
".png": "image/png",
".svg": "image/svg+xml",
".wasm": "application/wasm",
".css": "text/css",
".avif": "image/avif",
".webp": "image/webp",
".jpg": "image/jpeg",
".png": "image/png",
".svg": "image/svg+xml",
".wasm": "application/wasm",
".css": "text/css",
".ts": "video/mp2t",
".js": "application/javascript",
".html": "text/html",
".json": "application/json",
".txt": "text/plain",
".md": "text/markdown",
".xml": "application/xml",
".zip": "application/zip",
".gz": "application/gzip",
".tar": "application/x-tar",
".pdf": "application/pdf",
".mp4": "video/mp4",
".webm": "video/webm",
".ogg": "audio/ogg",
".mp3": "audio/mpeg",
".wav": "audio/wav",
".flac": "audio/flac",
".aac": "audio/aac",
".m4a": "audio/mp4",
".opus": "audio/opus",
".ico": "image/x-icon",
".otf": "font/otf",
".ttf": "font/ttf",
".woff": "font/woff",
".woff2": "font/woff2",
".eot": "application/vnd.ms-fontobject",
}

// hashFileSha256 hashes a file with Sha256 and returns the hash as a base64 encoded string.
func hashFileSha256(fin *os.File) (string, error) {
h := sha256.New()
if _, err := io.Copy(h, fin); err != nil {
return "", err
}

// rewind the file
if _, err := fin.Seek(0, io.SeekStart); err != nil {
return "", err
}

return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}

// hashFileMD5 hashes a file with MD5 and returns the hash as a base64 encoded string.
func hashFileMD5(fin *os.File) (string, error) {
h := md5.New()
if _, err := io.Copy(h, fin); err != nil {
return "", err
}

// rewind the file
if _, err := fin.Seek(0, io.SeekStart); err != nil {
return "", err
}

return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
17 changes: 2 additions & 15 deletions cmd/xedn/uplodr/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"time"

"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"
"github.com/chai2010/webp"
Expand All @@ -27,6 +26,7 @@ import (
"within.website/x/cmd/xedn/uplodr/pb"
"within.website/x/internal"
"within.website/x/internal/avif"
"within.website/x/tigris"
)

var (
Expand Down Expand Up @@ -89,7 +89,7 @@ type Server struct {
}

func New(ctx context.Context) (*Server, error) {
tc, err := mkTigrisClient(ctx)
tc, err := tigris.Client(ctx)
if err != nil {
return nil, fmt.Errorf("failed to create Tigris client: %w", err)
}
Expand Down Expand Up @@ -303,19 +303,6 @@ var mimeTypes = map[string]string{
".css": "text/css",
}

func mkTigrisClient(ctx context.Context) (*s3.Client, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load Tigris config: %w", err)
}
cfg.Region = "auto"

return s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String("https://fly.storage.tigris.dev")
o.Region = "auto"
}), nil
}

func mkB2Client() *s3.Client {
s3Config := aws.Config{
Credentials: credentials.NewStaticCredentialsProvider(*b2KeyID, *b2KeySecret, ""),
Expand Down
21 changes: 0 additions & 21 deletions internal/tigris.go

This file was deleted.

125 changes: 125 additions & 0 deletions tigris/tigris.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Package tigris contains a Tigris client and helpers for interacting with Tigris.
//
// Tigris is a cloud storage service that provides a simple, scalable, and secure object storage solution. It is based on the S3 API, but has additional features that need these helpers.
package tigris

import (
"context"
"fmt"
"strings"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
awsConfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/smithy-go/transport/http"
)

// WithHeader sets an arbitrary HTTP header on the request.
func WithHeader(key, value string) func(*s3.Options) {
return func(options *s3.Options) {
options.APIOptions = append(options.APIOptions, http.AddHeaderValue(key, value))
}
}

// Region is a Tigris region from the documentation.
//
// https://www.tigrisdata.com/docs/concepts/regions/
type Region string

// Possible Tigris regions.
const (
FRA Region = "fra" // Frankfurt, Germany
GRU Region = "gru" // São Paulo, Brazil
HKG Region = "hkg" // Hong Kong, China
IAD Region = "iad" // Ashburn, Virginia, USA
JNB Region = "jnb" // Johannesburg, South Africa
LHR Region = "lhr" // London, UK
MAD Region = "mad" // Madrid, Spain
NRT Region = "nrt" // Tokyo (Narita), Japan
ORD Region = "ord" // Chicago, Illinois, USA
SIN Region = "sin" // Singapore
SJC Region = "sjc" // San Jose, California, USA
SYD Region = "syd" // Sydney, Australia
)

// WithStaticReplicationRegions sets the regions where the object will be replicated.
//
// Note that this will cause you to be charged multiple times for the same object, once per region.
func WithStaticReplicationRegions(regions []Region) func(*s3.Options) {
regionsString := make([]string, 0, len(regions))
for _, r := range regions {
regionsString = append(regionsString, string(r))
}

return WithHeader("X-Tigris-Regions", strings.Join(regionsString, ","))
}

// WithQuery lets you filter objects in a ListObjectsV2 request.
//
// This functions like the WHERE clause in SQL, but for S3 objects. For more information, see the Tigris documentation[1].
//
// [1]: https://www.tigrisdata.com/docs/objects/query-metadata/
func WithQuery(query string) func(*s3.Options) {
return WithHeader("X-Tigris-Query", query)
}

// WithCreateIfNotExists will create the object if it doesn't exist.
//
// See the Tigris documentation[1] for more information.
//
// [1]: https://www.tigrisdata.com/docs/objects/conditionals/
func WithCreateObjectIfNotExists() func(*s3.Options) {
return WithHeader("If-Match", `""`)
}

// WithIfEtagMatches sets the ETag that the object must match.
//
// See the Tigris documentation[1] for more information.
//
// [1]: https://www.tigrisdata.com/docs/objects/conditionals/
func WithIfEtagMatches(etag string) func(*s3.Options) {
return WithHeader("If-Match", etag)
}

// WithModifiedSince lets you proceed with operation if object was modified after provided date (RFC1123).
//
// See the Tigris documentation[1] for more information.
//
// [1]: https://www.tigrisdata.com/docs/objects/conditionals/
func WithModifiedSince(modifiedSince time.Time) func(*s3.Options) {
return WithHeader("If-Modified-Since", modifiedSince.Format(time.RFC1123))
}

// WithUnmodifiedSince lets you proceed with operation if object was not modified after provided date (RFC1123).
//
// See the Tigris documentation[1] for more information.
//
// [1]: https://www.tigrisdata.com/docs/objects/conditionals/
func WithUnmodifiedSince(unmodifiedSince time.Time) func(*s3.Options) {
return WithHeader("If-Unmodified-Since", unmodifiedSince.Format(time.RFC1123))
}

// WithCompareAndSwap tells Tigris to skip the cache and read the object from its designated region.
//
// This is only used on GET requests.
//
// See the Tigris documentation[1] for more information.
//
// [1]: https://www.tigrisdata.com/docs/objects/conditionals/
func WithCompareAndSwap() func(*s3.Options) {
return WithHeader("X-Tigris-CAS", "true")
}

// Client returns a new S3 client wired up for Tigris.
func Client(ctx context.Context) (*s3.Client, error) {
cfg, err := awsConfig.LoadDefaultConfig(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load Tigris config: %w", err)
}

return s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String("https://fly.storage.tigris.dev")
o.Region = "auto"
}), nil
}

0 comments on commit 8c290e7

Please sign in to comment.