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

chore(storage): add proto converters for Object metadata #4583

Merged
merged 11 commits into from
Aug 25, 2021
49 changes: 49 additions & 0 deletions storage/acl.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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"},
noahdietz marked this conversation as resolved.
Show resolved Hide resolved
{Entity: "bar", Role: "foo"},
},
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
Original file line number Diff line number Diff line change
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
104 changes: 100 additions & 4 deletions storage/storage.go
Original file line number Diff line number Diff line change
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,43 @@ 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 {
var checksums *storagepb.ObjectChecksums
if len(o.MD5) > 0 {
noahdietz marked this conversation as resolved.
Show resolved Hide resolved
checksums = &storagepb.ObjectChecksums{Md5Hash: o.MD5}
} else if o.CRC32C > 0 {
checksums = &storagepb.ObjectChecksums{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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it necessary to add this field here and not in the apiary object?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk if it's necessary or not, but it's in the proto so I included it here. Should I remove it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, looks like it might be an oversight in the current implementation which is concerning!

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 +1328,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 +1389,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()),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed ETag is missing, is that no longer provided via gRPC?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't an etag field in the protos so I guess not.

}

// 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 +1777,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/")
}