Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Google Cloud Storage object metadata handling #232

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 68 additions & 18 deletions google/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import (
storage "google.golang.org/api/storage/v1"
)

const (
metaContentType = "Content-Type"
metaContentLanguage = "Content-Language"
metaContentEncoding = "Content-Encoding"
metaContentDisposition = "Content-Disposition"
metaCacheControl = "Cache-Control"
metaACL = "ACL"
)

type Container struct {
// Name is needed to retrieve items.
name string
Expand Down Expand Up @@ -49,7 +58,7 @@ func (c *Container) Item(id string) (stow.Item, error) {
return nil, err
}

mdParsed, err := parseMetadata(res.Metadata)
mdParsed, err := parseMetadata(res)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -101,7 +110,7 @@ func (c *Container) Items(prefix string, cursor string, count int) ([]stow.Item,
return nil, "", err
}

mdParsed, err := parseMetadata(o.Metadata)
mdParsed, err := parseMetadata(o)
if err != nil {
return nil, "", err
}
Expand Down Expand Up @@ -131,16 +140,13 @@ func (c *Container) RemoveItem(id string) error {
// received are the name of the item, a reader representing the
// content, and the size of the file.
func (c *Container) Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (stow.Item, error) {
mdPrepped, err := prepMetadata(metadata)
object := &storage.Object{Name: name}

err := prepMetadata(object, metadata)
if err != nil {
return nil, err
}

object := &storage.Object{
Name: name,
Metadata: mdPrepped,
}

res, err := c.client.Objects.Insert(c.name, object).Media(r).Do()
if err != nil {
return nil, err
Expand All @@ -156,7 +162,7 @@ func (c *Container) Put(name string, r io.Reader, size int64, metadata map[strin
return nil, err
}

mdParsed, err := parseMetadata(res.Metadata)
mdParsed, err := parseMetadata(res)
if err != nil {
return nil, err
}
Expand All @@ -176,22 +182,66 @@ func (c *Container) Put(name string, r io.Reader, size int64, metadata map[strin
return newItem, nil
}

func parseMetadata(metadataParsed map[string]string) (map[string]interface{}, error) {
metadataParsedMap := make(map[string]interface{}, len(metadataParsed))
for key, value := range metadataParsed {
func parseMetadata(o *storage.Object) (map[string]interface{}, error) {
metadataParsedMap := make(map[string]interface{})

if o.ContentType != "" {
metadataParsedMap[metaContentType] = o.ContentType
}
if o.ContentLanguage != "" {
metadataParsedMap[metaContentLanguage] = o.ContentLanguage
}
if o.ContentEncoding != "" {
metadataParsedMap[metaContentEncoding] = o.ContentEncoding
}
if o.ContentDisposition != "" {
metadataParsedMap[metaContentDisposition] = o.ContentDisposition
}
if o.CacheControl != "" {
metadataParsedMap[metaCacheControl] = o.CacheControl
}

if len(o.Acl) > 0 {
metadataParsedMap[metaACL] = o.Acl
}

for key, value := range o.Metadata {
metadataParsedMap[key] = value
}
return metadataParsedMap, nil
}

func prepMetadata(metadataParsed map[string]interface{}) (map[string]string, error) {
returnMap := make(map[string]string, len(metadataParsed))
for key, value := range metadataParsed {
func prepMetadata(o *storage.Object, itemMetadata map[string]interface{}) error {
o.Metadata = make(map[string]string)
for key, value := range itemMetadata {
if key == metaACL {
acls, ok := value.([]*storage.ObjectAccessControl)
if !ok {
return errors.Errorf(`value of key '%s' in metadata must be of type []*storage.ObjectAccessControl`, key)
}
o.Acl = acls
continue
}

str, ok := value.(string)
if !ok {
return nil, errors.Errorf(`value of key '%s' in metadata must be of type string`, key)
return errors.Errorf(`value of key '%s' in metadata must be of type string`, key)
}

switch key {
case metaContentType:
o.ContentType = str
case metaContentLanguage:
o.ContentLanguage = str
case metaContentEncoding:
o.ContentEncoding = str
case metaContentDisposition:
o.ContentDisposition = str
case metaCacheControl:
o.CacheControl = str
default:
o.Metadata[key] = str
}
returnMap[key] = str
}
return returnMap, nil
return nil
}
3 changes: 1 addition & 2 deletions google/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ func (i *Item) LastMod() (time.Time, error) {
return i.lastModified, nil
}

// Metadata returns a nil map and no error.
// TODO: Implement this.
// Metadata returns a map with object metadata.
func (i *Item) Metadata() (map[string]interface{}, error) {
return i.metadata, nil
}
Expand Down
90 changes: 72 additions & 18 deletions google/stow_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package google

import (
"github.com/cheekybits/is"
"github.com/graymeta/stow"
"github.com/graymeta/stow/test"
"google.golang.org/api/storage/v1"
"io/ioutil"
"os"
"reflect"
"testing"

"github.com/cheekybits/is"
"github.com/graymeta/stow"
"github.com/graymeta/stow/test"
)

func TestStow(t *testing.T) {
Expand All @@ -32,35 +32,89 @@ func TestStow(t *testing.T) {
test.All(t, "google", config)
}

func TestPrepMetadataSuccess(t *testing.T) {
func TestParseMetadataSuccess(t *testing.T) {
is := is.New(t)

m := make(map[string]string)
m["one"] = "two"
m["3"] = "4"
m["ninety-nine"] = "100"
aclItem := &storage.ObjectAccessControl{}

o := &storage.Object{
Name: "myobject",
ContentType: "text/html",
ContentEncoding: "gzip",
ContentDisposition: "form-data",
ContentLanguage: "et",
CacheControl: "no-cache",
Metadata: map[string]string{
"myCustomKey": "myCustomvalue",
},
Acl: []*storage.ObjectAccessControl{
aclItem,
},
}

m2 := make(map[string]interface{})
for key, value := range m {
m2[key] = value
expected := map[string]interface{}{
metaContentType: "text/html",
metaContentEncoding: "gzip",
metaContentDisposition: "form-data",
metaContentLanguage: "et",
metaCacheControl: "no-cache",
metaACL: []*storage.ObjectAccessControl{
aclItem,
},
"myCustomKey": "myCustomvalue",
}

//returns map[string]interface
returnedMap, err := prepMetadata(m2)
itemMeta, err := parseMetadata(o)
is.NoErr(err)
if !reflect.DeepEqual(itemMeta, expected) {
t.Errorf("Expected map (%+v) and returned map (%+v) are not equal.", expected, itemMeta)
}
}

if !reflect.DeepEqual(returnedMap, m) {
t.Errorf("Expected map (%+v) and returned map (%+v) are not equal.", m, returnedMap)
func TestPrepMetadataSuccess(t *testing.T) {
is := is.New(t)

o := &storage.Object{Name: "myobject"}

m := map[string]interface{}{
metaContentType: "text/html",
metaContentEncoding: "gzip",
metaContentDisposition: "form-data",
metaContentLanguage: "et",
metaCacheControl: "no-cache",
"myCustomKey": "myCustomvalue",
}

err := prepMetadata(o, m)
if err != nil {
t.Error("failed to prep metadata", err)
}

is.Equal(o.ContentType, "text/html")
is.Equal(o.ContentEncoding, "gzip")
is.Equal(o.ContentDisposition, "form-data")
is.Equal(o.ContentLanguage, "et")
is.Equal(o.CacheControl, "no-cache")

is.Equal(len(o.Metadata), 1)
customValue, ok := o.Metadata["myCustomKey"]
is.OK(ok)
is.Equal(customValue, "myCustomvalue")
}

func TestPrepMetadataFailureWithNonStringValues(t *testing.T) {
func TestPrepMetadataFailureWithInvalidValues(t *testing.T) {
is := is.New(t)

o := &storage.Object{Name: "megaobject"}

m := make(map[string]interface{})
m["float"] = 8.9
m["number"] = 9

_, err := prepMetadata(m)
err := prepMetadata(o, m)
is.Err(err)

m = make(map[string]interface{})
m[metaACL] = 8.9
is.Err(err)
}