From 7bf3645c1006f60c40775c385a81befdf5b7cb04 Mon Sep 17 00:00:00 2001 From: Will Norris Date: Fri, 2 Feb 2018 09:56:25 +0000 Subject: [PATCH] add custom cache for google cloud storage This is a bit cleaner than the gcs cache that was vendored in, is properly licensed, and uses Google's application default credentials, which just magically works when run from AppEngine and GCE. --- internal/gcscache/gcscache.go | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 internal/gcscache/gcscache.go diff --git a/internal/gcscache/gcscache.go b/internal/gcscache/gcscache.go new file mode 100644 index 0000000..ebe5c2f --- /dev/null +++ b/internal/gcscache/gcscache.go @@ -0,0 +1,84 @@ +// Package s3cache provides an httpcache.Cache implementation that stores +// cached values on Google Cloud Storage. +package gcscache + +import ( + "context" + "crypto/md5" + "encoding/hex" + "io" + "io/ioutil" + "log" + "path" + + "cloud.google.com/go/storage" +) + +var ctx = context.Background() + +type cache struct { + bucket *storage.BucketHandle + prefix string +} + +func (c *cache) Get(key string) ([]byte, bool) { + r, err := c.object(key).NewReader(ctx) + if err != nil { + if err != storage.ErrObjectNotExist { + log.Printf("error reading from gcs: %v", err) + } + return nil, false + } + defer r.Close() + + value, err := ioutil.ReadAll(r) + if err != nil { + log.Printf("error reading from gcs: %v", err) + return nil, false + } + + return value, true +} + +func (c *cache) Set(key string, value []byte) { + w := c.object(key).NewWriter(ctx) + if _, err := w.Write(value); err != nil { + log.Printf("error writing to gcs: %v", err) + } + if err := w.Close(); err != nil { + log.Printf("error closing gcs object writer: %v", err) + } +} + +func (c *cache) Delete(key string) { + if err := c.object(key).Delete(ctx); err != nil { + log.Printf("error deleting gcs object: %v", err) + } +} + +func (c *cache) object(key string) *storage.ObjectHandle { + name := path.Join(c.prefix, keyToFilename(key)) + return c.bucket.Object(name) +} + +func keyToFilename(key string) string { + h := md5.New() + io.WriteString(h, key) + return hex.EncodeToString(h.Sum(nil)) +} + +// New constructs a Cache storing files in the specified GCS bucket. If prefix +// is not empty, objects will be prefixed with that path. Credentials should +// be specified using one of the mechanisms supported for Application Default +// Credentials (see https://cloud.google.com/docs/authentication/production) +func New(bucket, prefix string) (*cache, error) { + client, err := storage.NewClient(ctx) + if err != nil { + return nil, err + } + + return &cache{ + prefix: prefix, + bucket: client.Bucket(bucket), + }, nil +}