Skip to content

Commit

Permalink
chore(storage): add proto converters for Object metadata (#4583)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahdietz committed Aug 25, 2021
1 parent d21fc8e commit 83b3656
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 5 deletions.
49 changes: 49 additions & 0 deletions storage/acl.go
Expand Up @@ -22,6 +22,7 @@ import (
"cloud.google.com/go/internal/trace"
"google.golang.org/api/googleapi"
raw "google.golang.org/api/storage/v1"
storagepb "google.golang.org/genproto/googleapis/storage/v2"
)

// ACLRole is the level of access to grant.
Expand Down Expand Up @@ -244,6 +245,14 @@ func toObjectACLRules(items []*raw.ObjectAccessControl) []ACLRule {
return rs
}

func fromProtoToObjectACLRules(items []*storagepb.ObjectAccessControl) []ACLRule {
var rs []ACLRule
for _, item := range items {
rs = append(rs, fromProtoToObjectACLRule(item))
}
return rs
}

func toBucketACLRules(items []*raw.BucketAccessControl) []ACLRule {
var rs []ACLRule
for _, item := range items {
Expand All @@ -263,6 +272,17 @@ func toObjectACLRule(a *raw.ObjectAccessControl) ACLRule {
}
}

func fromProtoToObjectACLRule(a *storagepb.ObjectAccessControl) ACLRule {
return ACLRule{
Entity: ACLEntity(a.GetEntity()),
EntityID: a.GetEntityId(),
Role: ACLRole(a.GetRole()),
Domain: a.GetDomain(),
Email: a.GetEmail(),
ProjectTeam: fromProtoToObjectProjectTeam(a.GetProjectTeam()),
}
}

func toBucketACLRule(a *raw.BucketAccessControl) ACLRule {
return ACLRule{
Entity: ACLEntity(a.Entity),
Expand All @@ -285,6 +305,17 @@ func toRawObjectACL(rules []ACLRule) []*raw.ObjectAccessControl {
return r
}

func toProtoObjectACL(rules []ACLRule) []*storagepb.ObjectAccessControl {
if len(rules) == 0 {
return nil
}
r := make([]*storagepb.ObjectAccessControl, 0, len(rules))
for _, rule := range rules {
r = append(r, rule.toProtoObjectAccessControl("")) // bucket name unnecessary
}
return r
}

func toRawBucketACL(rules []ACLRule) []*raw.BucketAccessControl {
if len(rules) == 0 {
return nil
Expand Down Expand Up @@ -314,6 +345,14 @@ func (r ACLRule) toRawObjectAccessControl(bucket string) *raw.ObjectAccessContro
}
}

func (r ACLRule) toProtoObjectAccessControl(bucket string) *storagepb.ObjectAccessControl {
return &storagepb.ObjectAccessControl{
Entity: string(r.Entity),
Role: string(r.Role),
// The other fields are not settable.
}
}

func toBucketProjectTeam(p *raw.BucketAccessControlProjectTeam) *ProjectTeam {
if p == nil {
return nil
Expand All @@ -333,3 +372,13 @@ func toObjectProjectTeam(p *raw.ObjectAccessControlProjectTeam) *ProjectTeam {
Team: p.Team,
}
}

func fromProtoToObjectProjectTeam(p *storagepb.ProjectTeam) *ProjectTeam {
if p == nil {
return nil
}
return &ProjectTeam{
ProjectNumber: p.GetProjectNumber(),
Team: p.GetTeam(),
}
}
49 changes: 49 additions & 0 deletions storage/acl_test.go
Expand Up @@ -20,6 +20,7 @@ import (
"testing"

"cloud.google.com/go/internal/testutil"
storagepb "google.golang.org/genproto/googleapis/storage/v2"
)

func TestSetACL(t *testing.T) {
Expand Down Expand Up @@ -63,3 +64,51 @@ func TestSetACL(t *testing.T) {
}
}
}

func TestToProtoObjectACL(t *testing.T) {
for i, tst := range []struct {
rules []ACLRule
want []*storagepb.ObjectAccessControl
}{
{nil, nil},
{
rules: []ACLRule{
{Entity: "foo", Role: "bar", Domain: "do not copy me!", Email: "donotcopy@"},
{Entity: "bar", Role: "foo", ProjectTeam: &ProjectTeam{ProjectNumber: "1234", Team: "donotcopy"}},
},
want: []*storagepb.ObjectAccessControl{
{Entity: "foo", Role: "bar"},
{Entity: "bar", Role: "foo"},
},
},
} {
got := toProtoObjectACL(tst.rules)
if diff := testutil.Diff(got, tst.want); diff != "" {
t.Errorf("#%d: got(-),want(+):\n%s", i, diff)
}
}
}

func TestFromProtoToObjectACLRules(t *testing.T) {
for i, tst := range []struct {
want []ACLRule
acls []*storagepb.ObjectAccessControl
}{
{nil, nil},
{
want: []ACLRule{
{Entity: "foo", Role: "bar", ProjectTeam: &ProjectTeam{ProjectNumber: "1234", Team: "foo"}},
{Entity: "bar", Role: "foo", EntityID: "baz", Domain: "domain"},
},
acls: []*storagepb.ObjectAccessControl{
{Entity: "foo", Role: "bar", ProjectTeam: &storagepb.ProjectTeam{ProjectNumber: "1234", Team: "foo"}},
{Entity: "bar", Role: "foo", EntityId: "baz", Domain: "domain"},
},
},
} {
got := fromProtoToObjectACLRules(tst.acls)
if diff := testutil.Diff(got, tst.want); diff != "" {
t.Errorf("#%d: got(-),want(+):\n%s", i, diff)
}
}
}
2 changes: 1 addition & 1 deletion storage/reader.go
Expand Up @@ -446,7 +446,7 @@ func (o *ObjectHandle) newRangeReaderWithGRPC(ctx context.Context, offset, lengt

// For now, there are only globally unique buckets, and "_" is the alias
// project ID for such buckets.
b := bucket("_", o.bucket)
b := bucketResourceName("_", o.bucket)
req := &storagepb.ReadObjectRequest{
Bucket: b,
Object: o.object,
Expand Down
103 changes: 99 additions & 4 deletions storage/storage.go
Expand Up @@ -46,6 +46,9 @@ import (
"google.golang.org/api/option/internaloption"
raw "google.golang.org/api/storage/v1"
htransport "google.golang.org/api/transport/http"
storagepb "google.golang.org/genproto/googleapis/storage/v2"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)

// Methods which can be used in signed URLs.
Expand Down Expand Up @@ -1132,6 +1135,42 @@ func (o *ObjectAttrs) toRawObject(bucket string) *raw.Object {
}
}

// toProtoObject copies the editable attributes from o to the proto library's Object type.
func (o *ObjectAttrs) toProtoObject(b string) *storagepb.Object {
checksums := &storagepb.ObjectChecksums{Md5Hash: o.MD5}
if o.CRC32C > 0 {
checksums.Crc32C = proto.Uint32(o.CRC32C)
}

// For now, there are only globally unique buckets, and "_" is the alias
// project ID for such buckets.
b = bucketResourceName("_", b)

return &storagepb.Object{
Bucket: b,
Name: o.Name,
EventBasedHold: proto.Bool(o.EventBasedHold),
TemporaryHold: o.TemporaryHold,
ContentType: o.ContentType,
ContentEncoding: o.ContentEncoding,
ContentLanguage: o.ContentLanguage,
CacheControl: o.CacheControl,
ContentDisposition: o.ContentDisposition,
StorageClass: o.StorageClass,
Acl: toProtoObjectACL(o.ACL),
Metadata: o.Metadata,
CreateTime: toProtoTimestamp(o.Created),
CustomTime: toProtoTimestamp(o.CustomTime),
DeleteTime: toProtoTimestamp(o.Deleted),
RetentionExpireTime: toProtoTimestamp(o.RetentionExpirationTime),
UpdateTime: toProtoTimestamp(o.Updated),
KmsKey: o.KMSKeyName,
Generation: o.Generation,
Size: o.Size,
Checksums: checksums,
}
}

// ObjectAttrs represents the metadata for a Google Cloud Storage (GCS) object.
type ObjectAttrs struct {
// Bucket is the name of the bucket containing this GCS object.
Expand Down Expand Up @@ -1288,6 +1327,22 @@ func convertTime(t string) time.Time {
return r
}

func convertProtoTime(t *timestamppb.Timestamp) time.Time {
var r time.Time
if t != nil {
r = t.AsTime()
}
return r
}

func toProtoTimestamp(t time.Time) *timestamppb.Timestamp {
if t.IsZero() {
return nil
}

return timestamppb.New(t)
}

func newObject(o *raw.Object) *ObjectAttrs {
if o == nil {
return nil
Expand Down Expand Up @@ -1333,6 +1388,40 @@ func newObject(o *raw.Object) *ObjectAttrs {
}
}

func newObjectFromProto(r *storagepb.WriteObjectResponse) *ObjectAttrs {
o := r.GetResource()
if r == nil || o == nil {
return nil
}
return &ObjectAttrs{
Bucket: parseBucketName(o.Bucket),
Name: o.Name,
ContentType: o.ContentType,
ContentLanguage: o.ContentLanguage,
CacheControl: o.CacheControl,
EventBasedHold: o.GetEventBasedHold(),
TemporaryHold: o.TemporaryHold,
RetentionExpirationTime: convertProtoTime(o.GetRetentionExpireTime()),
ACL: fromProtoToObjectACLRules(o.GetAcl()),
Owner: o.GetOwner().GetEntity(),
ContentEncoding: o.ContentEncoding,
ContentDisposition: o.ContentDisposition,
Size: int64(o.Size),
MD5: o.GetChecksums().GetMd5Hash(),
CRC32C: o.GetChecksums().GetCrc32C(),
Metadata: o.Metadata,
Generation: o.Generation,
Metageneration: o.Metageneration,
StorageClass: o.StorageClass,
CustomerKeySHA256: o.GetCustomerEncryption().GetKeySha256(),
KMSKeyName: o.GetKmsKey(),
Created: convertProtoTime(o.GetCreateTime()),
Deleted: convertProtoTime(o.GetDeleteTime()),
Updated: convertProtoTime(o.GetUpdateTime()),
CustomTime: convertProtoTime(o.GetCustomTime()),
}
}

// Decode a uint32 encoded in Base64 in big-endian byte order.
func decodeUint32(b64 string) (uint32, error) {
d, err := base64.StdEncoding.DecodeString(b64)
Expand Down Expand Up @@ -1687,9 +1776,15 @@ func (c *Client) ServiceAccount(ctx context.Context, projectID string) (string,
return res.EmailAddress, nil
}

// bucket formats the given project ID and bucket ID into a Bucket resource
// name. This is the format necessary for the gRPC API as it conforms to the
// Resource-oriented design practices in https://google.aip.dev/121.
func bucket(p, b string) string {
// bucketResourceName formats the given project ID and bucketResourceName ID
// into a Bucket resource name. This is the format necessary for the gRPC API as
// it conforms to the Resource-oriented design practices in https://google.aip.dev/121.
func bucketResourceName(p, b string) string {
return fmt.Sprintf("projects/%s/buckets/%s", p, b)
}

// parseBucketName strips the leading resource path segment and returns the
// bucket ID, which is the simple Bucket name typical of the v1 API.
func parseBucketName(b string) string {
return strings.TrimPrefix(b, "projects/_/buckets/")
}

0 comments on commit 83b3656

Please sign in to comment.