vendor: update all dependencies to latest versions

This commit is contained in:
Nick Craig-Wood
2017-09-30 15:27:27 +01:00
parent 911d121bb9
commit b017fcfe9a
3048 changed files with 537055 additions and 189679 deletions
+53 -42
View File
@@ -16,8 +16,11 @@ package storage
import (
"fmt"
"net/http"
"reflect"
"golang.org/x/net/context"
"google.golang.org/api/googleapi"
raw "google.golang.org/api/storage/v1"
)
@@ -53,10 +56,11 @@ type ACLRule struct {
// ACLHandle provides operations on an access control list for a Google Cloud Storage bucket or object.
type ACLHandle struct {
c *Client
bucket string
object string
isDefault bool
c *Client
bucket string
object string
isDefault bool
userProject string // for requester-pays buckets
}
// Delete permanently deletes the ACL entry for the given entity.
@@ -73,10 +77,10 @@ func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) error {
// Set sets the permission level for the given entity.
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) error {
if a.object != "" {
return a.objectSet(ctx, entity, role)
return a.objectSet(ctx, entity, role, false)
}
if a.isDefault {
return a.bucketDefaultSet(ctx, entity, role)
return a.objectSet(ctx, entity, role, true)
}
return a.bucketSet(ctx, entity, role)
}
@@ -96,8 +100,8 @@ func (a *ACLHandle) bucketDefaultList(ctx context.Context) ([]ACLRule, error) {
var acls *raw.ObjectAccessControls
var err error
err = runWithRetry(ctx, func() error {
req := a.c.raw.DefaultObjectAccessControls.List(a.bucket).Context(ctx)
setClientHeader(req.Header())
req := a.c.raw.DefaultObjectAccessControls.List(a.bucket)
a.configureCall(req, ctx)
acls, err = req.Do()
return err
})
@@ -107,28 +111,10 @@ func (a *ACLHandle) bucketDefaultList(ctx context.Context) ([]ACLRule, error) {
return toACLRules(acls.Items), nil
}
func (a *ACLHandle) bucketDefaultSet(ctx context.Context, entity ACLEntity, role ACLRole) error {
acl := &raw.ObjectAccessControl{
Bucket: a.bucket,
Entity: string(entity),
Role: string(role),
}
err := runWithRetry(ctx, func() error {
req := a.c.raw.DefaultObjectAccessControls.Update(a.bucket, string(entity), acl).Context(ctx)
setClientHeader(req.Header())
_, err := req.Do()
return err
})
if err != nil {
return fmt.Errorf("storage: error updating default ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
}
return nil
}
func (a *ACLHandle) bucketDefaultDelete(ctx context.Context, entity ACLEntity) error {
err := runWithRetry(ctx, func() error {
req := a.c.raw.DefaultObjectAccessControls.Delete(a.bucket, string(entity)).Context(ctx)
setClientHeader(req.Header())
req := a.c.raw.DefaultObjectAccessControls.Delete(a.bucket, string(entity))
a.configureCall(req, ctx)
return req.Do()
})
if err != nil {
@@ -141,8 +127,8 @@ func (a *ACLHandle) bucketList(ctx context.Context) ([]ACLRule, error) {
var acls *raw.BucketAccessControls
var err error
err = runWithRetry(ctx, func() error {
req := a.c.raw.BucketAccessControls.List(a.bucket).Context(ctx)
setClientHeader(req.Header())
req := a.c.raw.BucketAccessControls.List(a.bucket)
a.configureCall(req, ctx)
acls, err = req.Do()
return err
})
@@ -164,8 +150,8 @@ func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRol
Role: string(role),
}
err := runWithRetry(ctx, func() error {
req := a.c.raw.BucketAccessControls.Update(a.bucket, string(entity), acl).Context(ctx)
setClientHeader(req.Header())
req := a.c.raw.BucketAccessControls.Update(a.bucket, string(entity), acl)
a.configureCall(req, ctx)
_, err := req.Do()
return err
})
@@ -177,8 +163,8 @@ func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRol
func (a *ACLHandle) bucketDelete(ctx context.Context, entity ACLEntity) error {
err := runWithRetry(ctx, func() error {
req := a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity)).Context(ctx)
setClientHeader(req.Header())
req := a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity))
a.configureCall(req, ctx)
return req.Do()
})
if err != nil {
@@ -191,8 +177,8 @@ func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
var acls *raw.ObjectAccessControls
var err error
err = runWithRetry(ctx, func() error {
req := a.c.raw.ObjectAccessControls.List(a.bucket, a.object).Context(ctx)
setClientHeader(req.Header())
req := a.c.raw.ObjectAccessControls.List(a.bucket, a.object)
a.configureCall(req, ctx)
acls, err = req.Do()
return err
})
@@ -202,28 +188,42 @@ func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
return toACLRules(acls.Items), nil
}
func (a *ACLHandle) objectSet(ctx context.Context, entity ACLEntity, role ACLRole) error {
func (a *ACLHandle) objectSet(ctx context.Context, entity ACLEntity, role ACLRole, isBucketDefault bool) error {
type setRequest interface {
Do(opts ...googleapi.CallOption) (*raw.ObjectAccessControl, error)
Header() http.Header
}
acl := &raw.ObjectAccessControl{
Bucket: a.bucket,
Entity: string(entity),
Role: string(role),
}
var req setRequest
if isBucketDefault {
req = a.c.raw.DefaultObjectAccessControls.Update(a.bucket, string(entity), acl)
} else {
req = a.c.raw.ObjectAccessControls.Update(a.bucket, a.object, string(entity), acl)
}
a.configureCall(req, ctx)
err := runWithRetry(ctx, func() error {
req := a.c.raw.ObjectAccessControls.Update(a.bucket, a.object, string(entity), acl).Context(ctx)
setClientHeader(req.Header())
_, err := req.Do()
return err
})
if err != nil {
return fmt.Errorf("storage: error updating object ACL entry for bucket %q, file %q, entity %q: %v", a.bucket, a.object, entity, err)
if isBucketDefault {
return fmt.Errorf("storage: error updating default ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
} else {
return fmt.Errorf("storage: error updating object ACL entry for bucket %q, object %q, entity %q: %v", a.bucket, a.object, entity, err)
}
}
return nil
}
func (a *ACLHandle) objectDelete(ctx context.Context, entity ACLEntity) error {
err := runWithRetry(ctx, func() error {
req := a.c.raw.ObjectAccessControls.Delete(a.bucket, a.object, string(entity)).Context(ctx)
setClientHeader(req.Header())
req := a.c.raw.ObjectAccessControls.Delete(a.bucket, a.object, string(entity))
a.configureCall(req, ctx)
return req.Do()
})
if err != nil {
@@ -232,6 +232,17 @@ func (a *ACLHandle) objectDelete(ctx context.Context, entity ACLEntity) error {
return nil
}
func (a *ACLHandle) configureCall(call interface {
Header() http.Header
}, ctx context.Context) {
vc := reflect.ValueOf(call)
vc.MethodByName("Context").Call([]reflect.Value{reflect.ValueOf(ctx)})
if a.userProject != "" {
vc.MethodByName("UserProject").Call([]reflect.Value{reflect.ValueOf(a.userProject)})
}
setClientHeader(call.Header())
}
func toACLRules(items []*raw.ObjectAccessControl) []ACLRule {
r := make([]ACLRule, 0, len(items))
for _, item := range items {
+221 -6
View File
@@ -1,4 +1,4 @@
// Copyright 2014 Google Inc. All Rights Reserved.
// Copyright 2014 Google Inc. LiveAndArchived Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ type BucketHandle struct {
acl ACLHandle
defaultObjectACL ACLHandle
conds *BucketConditions
userProject string // project for requester-pays buckets
}
// Bucket returns a BucketHandle, which provides operations on the named bucket.
@@ -70,6 +71,11 @@ func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *Buck
bkt = &raw.Bucket{}
}
bkt.Name = b.name
// If there is lifecycle information but no location, explicitly set
// the location. This is a GCS quirk/bug.
if bkt.Location == "" && bkt.Lifecycle != nil {
bkt.Location = "US"
}
req := b.c.raw.Buckets.Insert(projectID, bkt)
setClientHeader(req.Header())
return runWithRetry(ctx, func() error { _, err := req.Context(ctx).Do(); return err })
@@ -90,6 +96,9 @@ func (b *BucketHandle) newDeleteCall() (*raw.BucketsDeleteCall, error) {
if err := applyBucketConds("BucketHandle.Delete", b.conds, req); err != nil {
return nil, err
}
if b.userProject != "" {
req.UserProject(b.userProject)
}
return req, nil
}
@@ -119,11 +128,13 @@ func (b *BucketHandle) Object(name string) *ObjectHandle {
bucket: b.name,
object: name,
acl: ACLHandle{
c: b.c,
bucket: b.name,
object: name,
c: b.c,
bucket: b.name,
object: name,
userProject: b.userProject,
},
gen: -1,
gen: -1,
userProject: b.userProject,
}
}
@@ -153,6 +164,9 @@ func (b *BucketHandle) newGetCall() (*raw.BucketsGetCall, error) {
if err := applyBucketConds("BucketHandle.Attrs", b.conds, req); err != nil {
return nil, err
}
if b.userProject != "" {
req.UserProject(b.userProject)
}
return req, nil
}
@@ -176,12 +190,17 @@ func (b *BucketHandle) newPatchCall(uattrs *BucketAttrsToUpdate) (*raw.BucketsPa
if err := applyBucketConds("BucketHandle.Update", b.conds, req); err != nil {
return nil, err
}
if b.userProject != "" {
req.UserProject(b.userProject)
}
return req, nil
}
// BucketAttrs represents the metadata for a Google Cloud Storage bucket.
// Read-only fields are ignored by BucketHandle.Create.
type BucketAttrs struct {
// Name is the name of the bucket.
// This field is read-only.
Name string
// ACL is the list of access control rules on the bucket.
@@ -195,6 +214,7 @@ type BucketAttrs struct {
Location string
// MetaGeneration is the metadata generation of the bucket.
// This field is read-only.
MetaGeneration int64
// StorageClass is the default storage class of the bucket. This defines
@@ -207,14 +227,109 @@ type BucketAttrs struct {
StorageClass string
// Created is the creation time of the bucket.
// This field is read-only.
Created time.Time
// VersioningEnabled reports whether this bucket has versioning enabled.
// This field is read-only.
VersioningEnabled bool
// Labels are the bucket's labels.
Labels map[string]string
// RequesterPays reports whether the bucket is a Requester Pays bucket.
RequesterPays bool
// Lifecycle is the lifecycle configuration for objects in the bucket.
Lifecycle Lifecycle
}
// Lifecycle is the lifecycle configuration for objects in the bucket.
type Lifecycle struct {
Rules []LifecycleRule
}
const (
// RFC3339 date with only the date segment, used for CreatedBefore in LifecycleRule.
rfc3339Date = "2006-01-02"
// DeleteAction is a lifecycle action that deletes a live and/or archived
// objects. Takes precendence over SetStorageClass actions.
DeleteAction = "Delete"
// SetStorageClassAction changes the storage class of live and/or archived
// objects.
SetStorageClassAction = "SetStorageClass"
)
// LifecycleRule is a lifecycle configuration rule.
//
// When all the configured conditions are met by an object in the bucket, the
// configured action will automatically be taken on that object.
type LifecycleRule struct {
// Action is the action to take when all of the associated conditions are
// met.
Action LifecycleAction
// Condition is the set of conditions that must be met for the associated
// action to be taken.
Condition LifecycleCondition
}
// LifecycleAction is a lifecycle configuration action.
type LifecycleAction struct {
// Type is the type of action to take on matching objects.
//
// Acceptable values are "Delete" to delete matching objects and
// "SetStorageClass" to set the storage class defined in StorageClass on
// matching objects.
Type string
// StorageClass is the storage class to set on matching objects if the Action
// is "SetStorageClass".
StorageClass string
}
// Liveness specifies whether the object is live or not.
type Liveness int
const (
// LiveAndArchived includes both live and archived objects.
LiveAndArchived Liveness = iota
// Live specifies that the object is still live.
Live
// Archived specifies that the object is archived.
Archived
)
// LifecycleCondition is a set of conditions used to match objects and take an
// action automatically.
//
// All configured conditions must be met for the associated action to be taken.
type LifecycleCondition struct {
// AgeInDays is the age of the object in days.
AgeInDays int64
// CreatedBefore is the time the object was created.
//
// This condition is satisfied when an object is created before midnight of
// the specified date in UTC.
CreatedBefore time.Time
// Liveness specifies the object's liveness. Relevant only for versioned objects
Liveness Liveness
// MatchesStorageClasses is the condition matching the object's storage
// class.
//
// Values include "MULTI_REGIONAL", "REGIONAL", "NEARLINE", "COLDLINE",
// "STANDARD", and "DURABLE_REDUCED_AVAILABILITY".
MatchesStorageClasses []string
// NumNewerVersions is the condition matching objects with a number of newer versions.
//
// If the value is N, this condition is satisfied when there are at least N
// versions (including the live version) newer than this version of the
// object.
NumNewerVersions int64
}
func newBucket(b *raw.Bucket) *BucketAttrs {
@@ -229,6 +344,8 @@ func newBucket(b *raw.Bucket) *BucketAttrs {
Created: convertTime(b.TimeCreated),
VersioningEnabled: b.Versioning != nil && b.Versioning.Enabled,
Labels: b.Labels,
RequesterPays: b.Billing != nil && b.Billing.RequesterPays,
Lifecycle: toLifecycle(b.Lifecycle),
}
acl := make([]ACLRule, len(b.Acl))
for i, rule := range b.Acl {
@@ -277,6 +394,10 @@ func (b *BucketAttrs) toRawBucket() *raw.Bucket {
if b.VersioningEnabled {
v = &raw.BucketVersioning{Enabled: true}
}
var bb *raw.BucketBilling
if b.RequesterPays {
bb = &raw.BucketBilling{RequesterPays: true}
}
return &raw.Bucket{
Name: b.Name,
DefaultObjectAcl: dACL,
@@ -285,6 +406,8 @@ func (b *BucketAttrs) toRawBucket() *raw.Bucket {
Acl: acl,
Versioning: v,
Labels: labels,
Billing: bb,
Lifecycle: toRawLifecycle(b.Lifecycle),
}
}
@@ -292,6 +415,9 @@ type BucketAttrsToUpdate struct {
// VersioningEnabled, if set, updates whether the bucket uses versioning.
VersioningEnabled optional.Bool
// RequesterPays, if set, updates whether the bucket is a Requester Pays bucket.
RequesterPays optional.Bool
setLabels map[string]string
deleteLabels map[string]bool
}
@@ -322,6 +448,12 @@ func (ua *BucketAttrsToUpdate) toRawBucket() *raw.Bucket {
ForceSendFields: []string{"Enabled"},
}
}
if ua.RequesterPays != nil {
rb.Billing = &raw.BucketBilling{
RequesterPays: optional.ToBool(ua.RequesterPays),
ForceSendFields: []string{"RequesterPays"},
}
}
if ua.setLabels != nil || ua.deleteLabels != nil {
rb.Labels = map[string]string{}
for k, v := range ua.setLabels {
@@ -373,6 +505,17 @@ func (c *BucketConditions) validate(method string) error {
return nil
}
// UserProject returns a new BucketHandle that passes the project ID as the user
// project for all subsequent calls. A user project is required for all operations
// on requester-pays buckets.
func (b *BucketHandle) UserProject(projectID string) *BucketHandle {
b2 := *b
b2.userProject = projectID
b2.acl.userProject = projectID
b2.defaultObjectACL.userProject = projectID
return &b2
}
// applyBucketConds modifies the provided call using the conditions in conds.
// call is something that quacks like a *raw.WhateverCall.
func applyBucketConds(method string, conds *BucketConditions, call interface{}) error {
@@ -396,6 +539,75 @@ func applyBucketConds(method string, conds *BucketConditions, call interface{})
return nil
}
func toRawLifecycle(l Lifecycle) *raw.BucketLifecycle {
var rl raw.BucketLifecycle
if len(l.Rules) == 0 {
return nil
}
for _, r := range l.Rules {
rr := &raw.BucketLifecycleRule{
Action: &raw.BucketLifecycleRuleAction{
Type: r.Action.Type,
StorageClass: r.Action.StorageClass,
},
Condition: &raw.BucketLifecycleRuleCondition{
Age: r.Condition.AgeInDays,
MatchesStorageClass: r.Condition.MatchesStorageClasses,
NumNewerVersions: r.Condition.NumNewerVersions,
},
}
switch r.Condition.Liveness {
case LiveAndArchived:
rr.Condition.IsLive = nil
case Live:
rr.Condition.IsLive = googleapi.Bool(true)
case Archived:
rr.Condition.IsLive = googleapi.Bool(false)
}
if !r.Condition.CreatedBefore.IsZero() {
rr.Condition.CreatedBefore = r.Condition.CreatedBefore.Format(rfc3339Date)
}
rl.Rule = append(rl.Rule, rr)
}
return &rl
}
func toLifecycle(rl *raw.BucketLifecycle) Lifecycle {
var l Lifecycle
if rl == nil {
return l
}
for _, rr := range rl.Rule {
r := LifecycleRule{
Action: LifecycleAction{
Type: rr.Action.Type,
StorageClass: rr.Action.StorageClass,
},
Condition: LifecycleCondition{
AgeInDays: rr.Condition.Age,
MatchesStorageClasses: rr.Condition.MatchesStorageClass,
NumNewerVersions: rr.Condition.NumNewerVersions,
},
}
switch {
case rr.Condition.IsLive == nil:
r.Condition.Liveness = LiveAndArchived
case *rr.Condition.IsLive == true:
r.Condition.Liveness = Live
case *rr.Condition.IsLive == false:
r.Condition.Liveness = Archived
}
if rr.Condition.CreatedBefore != "" {
r.Condition.CreatedBefore, _ = time.Parse(rfc3339Date, rr.Condition.CreatedBefore)
}
}
return l
}
// Objects returns an iterator over the objects in the bucket that match the Query q.
// If q is nil, no filtering is done.
func (b *BucketHandle) Objects(ctx context.Context, q *Query) *ObjectIterator {
@@ -450,6 +662,9 @@ func (it *ObjectIterator) fetch(pageSize int, pageToken string) (string, error)
req.Prefix(it.query.Prefix)
req.Versions(it.query.Versions)
req.PageToken(pageToken)
if it.bucket.userProject != "" {
req.UserProject(it.bucket.userProject)
}
if pageSize > 0 {
req.MaxResults(int64(pageSize))
}
+33 -9
View File
@@ -20,7 +20,10 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"cloud.google.com/go/internal/pretty"
"cloud.google.com/go/internal/testutil"
raw "google.golang.org/api/storage/v1"
)
@@ -61,8 +64,10 @@ func TestBucketAttrsToRawBucket(t *testing.T) {
}
attrs.VersioningEnabled = true
attrs.RequesterPays = true
got = attrs.toRawBucket()
want.Versioning = &raw.BucketVersioning{Enabled: true}
want.Billing = &raw.BucketBilling{RequesterPays: true}
msg, ok, err = pretty.Diff(want, got)
if err != nil {
t.Fatal(err)
@@ -74,7 +79,10 @@ func TestBucketAttrsToRawBucket(t *testing.T) {
func TestBucketAttrsToUpdateToRawBucket(t *testing.T) {
t.Parallel()
au := &BucketAttrsToUpdate{VersioningEnabled: false}
au := &BucketAttrsToUpdate{
VersioningEnabled: false,
RequesterPays: false,
}
au.SetLabel("a", "foo")
au.DeleteLabel("b")
au.SetLabel("c", "")
@@ -88,6 +96,10 @@ func TestBucketAttrsToUpdateToRawBucket(t *testing.T) {
"a": "foo",
"c": "",
},
Billing: &raw.BucketBilling{
RequesterPays: false,
ForceSendFields: []string{"RequesterPays"},
},
NullFields: []string{"Labels.b"},
}
msg, ok, err := pretty.Diff(want, got)
@@ -125,7 +137,20 @@ func TestCallBuilders(t *testing.T) {
const metagen = 17
b := c.Bucket("name")
bm := b.If(BucketConditions{MetagenerationMatch: metagen})
bm := b.If(BucketConditions{MetagenerationMatch: metagen}).UserProject("p")
equal := func(x, y interface{}) bool {
return testutil.Equal(x, y,
cmp.AllowUnexported(
raw.BucketsGetCall{},
raw.BucketsDeleteCall{},
raw.BucketsPatchCall{},
),
cmp.FilterPath(func(p cmp.Path) bool {
return p[len(p)-1].Type() == reflect.TypeOf(&raw.Service{})
}, cmp.Ignore()),
)
}
for i, test := range []struct {
callFunc func(*BucketHandle) (interface{}, error)
@@ -137,12 +162,12 @@ func TestCallBuilders(t *testing.T) {
{
func(b *BucketHandle) (interface{}, error) { return b.newGetCall() },
rc.Buckets.Get("name").Projection("full"),
func(req interface{}) { req.(*raw.BucketsGetCall).IfMetagenerationMatch(metagen) },
func(req interface{}) { req.(*raw.BucketsGetCall).IfMetagenerationMatch(metagen).UserProject("p") },
},
{
func(b *BucketHandle) (interface{}, error) { return b.newDeleteCall() },
rc.Buckets.Delete("name"),
func(req interface{}) { req.(*raw.BucketsDeleteCall).IfMetagenerationMatch(metagen) },
func(req interface{}) { req.(*raw.BucketsDeleteCall).IfMetagenerationMatch(metagen).UserProject("p") },
},
{
func(b *BucketHandle) (interface{}, error) {
@@ -151,7 +176,7 @@ func TestCallBuilders(t *testing.T) {
rc.Buckets.Patch("name", &raw.Bucket{
Versioning: &raw.BucketVersioning{Enabled: false, ForceSendFields: []string{"Enabled"}},
}).Projection("full"),
func(req interface{}) { req.(*raw.BucketsPatchCall).IfMetagenerationMatch(metagen) },
func(req interface{}) { req.(*raw.BucketsPatchCall).IfMetagenerationMatch(metagen).UserProject("p") },
},
} {
got, err := test.callFunc(b)
@@ -159,17 +184,16 @@ func TestCallBuilders(t *testing.T) {
t.Fatal(err)
}
setClientHeader(test.want.Header())
if !reflect.DeepEqual(got, test.want) {
if !equal(got, test.want) {
t.Errorf("#%d: got %#v, want %#v", i, got, test.want)
}
got, err = test.callFunc(bm)
if err != nil {
t.Fatal(err)
}
test.metagenFunc(test.want)
if !reflect.DeepEqual(got, test.want) {
t.Errorf("#%d: got %#v, want %#v", i, got, test.want)
if !equal(got, test.want) {
t.Errorf("#%d:\ngot %#v\nwant %#v", i, got, test.want)
}
}
+16 -3
View File
@@ -25,6 +25,9 @@ import (
// CopierFrom creates a Copier that can copy src to dst.
// You can immediately call Run on the returned Copier, or
// you can configure it first.
//
// For Requester Pays buckets, the user project of dst is billed, unless it is empty,
// in which case the user project of src is billed.
func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier {
return &Copier{dst: dst, src: src}
}
@@ -73,7 +76,7 @@ func (c *Copier) Run(ctx context.Context) (*ObjectAttrs, error) {
// does not cause any problems.
rawObject := c.ObjectAttrs.toRawObject("")
for {
res, err := c.callRewrite(ctx, c.src, rawObject)
res, err := c.callRewrite(ctx, rawObject)
if err != nil {
return nil, err
}
@@ -86,8 +89,8 @@ func (c *Copier) Run(ctx context.Context) (*ObjectAttrs, error) {
}
}
func (c *Copier) callRewrite(ctx context.Context, src *ObjectHandle, rawObj *raw.Object) (*raw.RewriteResponse, error) {
call := c.dst.c.raw.Objects.Rewrite(src.bucket, src.object, c.dst.bucket, c.dst.object, rawObj)
func (c *Copier) callRewrite(ctx context.Context, rawObj *raw.Object) (*raw.RewriteResponse, error) {
call := c.dst.c.raw.Objects.Rewrite(c.src.bucket, c.src.object, c.dst.bucket, c.dst.object, rawObj)
call.Context(ctx).Projection("full")
if c.RewriteToken != "" {
@@ -96,6 +99,11 @@ func (c *Copier) callRewrite(ctx context.Context, src *ObjectHandle, rawObj *raw
if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
return nil, err
}
if c.dst.userProject != "" {
call.UserProject(c.dst.userProject)
} else if c.src.userProject != "" {
call.UserProject(c.src.userProject)
}
if err := applySourceConds(c.src.gen, c.src.conds, call); err != nil {
return nil, err
}
@@ -128,6 +136,8 @@ func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer {
}
// A Composer composes source objects into a destination object.
//
// For Requester Pays buckets, the user project of dst is billed.
type Composer struct {
// ObjectAttrs are optional attributes to set on the destination object.
// Any attributes must be initialized before any calls on the Composer. Nil
@@ -174,6 +184,9 @@ func (c *Composer) Run(ctx context.Context) (*ObjectAttrs, error) {
if err := applyConds("ComposeFrom destination", c.dst.gen, c.dst.conds, call); err != nil {
return nil, err
}
if c.dst.userProject != "" {
call.UserProject(c.dst.userProject)
}
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
return nil, err
}
+26
View File
@@ -16,6 +16,7 @@ package storage_test
import (
"fmt"
"hash/crc32"
"io"
"io/ioutil"
"log"
@@ -321,6 +322,31 @@ func ExampleWriter_Write() {
wc.ACL = []storage.ACLRule{{storage.AllUsers, storage.RoleReader}}
if _, err := wc.Write([]byte("hello world")); err != nil {
// TODO: handle error.
// Note that Write may return nil in some error situations,
// so always check the error from Close.
}
if err := wc.Close(); err != nil {
// TODO: handle error.
}
fmt.Println("updated object:", wc.Attrs())
}
// To make sure the data you write is uncorrupted, use an MD5 or CRC32c
// checksum. This example illustrates CRC32c.
func ExampleWriter_Write_checksum() {
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
// TODO: handle error.
}
data := []byte("verify me")
wc := client.Bucket("bucketname").Object("filename1").NewWriter(ctx)
wc.CRC32C = crc32.Checksum(data, crc32.MakeTable(crc32.Castagnoli))
wc.SendCRC32C = true
if _, err := wc.Write([]byte("hello world")); err != nil {
// TODO: handle error.
// Note that Write may return nil in some error situations,
// so always check the error from Close.
}
if err := wc.Close(); err != nil {
// TODO: handle error.
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.10
package storage
import "google.golang.org/api/googleapi"
func shouldRetry(err error) bool {
switch e := err.(type) {
case *googleapi.Error:
// Retry on 429 and 5xx, according to
// https://cloud.google.com/storage/docs/exponential-backoff.
return e.Code == 429 || (e.Code >= 500 && e.Code < 600)
default:
return false
}
}
+156 -11
View File
@@ -29,7 +29,6 @@ import (
"math/rand"
"net/http"
"os"
"reflect"
"sort"
"strconv"
"strings"
@@ -46,7 +45,7 @@ import (
"google.golang.org/api/option"
)
const testPrefix = "-go-cloud-storage-test"
const testPrefix = "-go-test"
// suffix is a timestamp-based suffix which is added to all buckets created by
// tests. This reduces flakiness when the tests are run in parallel and allows
@@ -125,7 +124,7 @@ func TestBucketMethods(t *testing.T) {
b := client.Bucket(newBucket)
// Test Create and Delete.
if err := b.Create(ctx, projectID, nil); err != nil {
t.Errorf("Bucket(%v).Create(%v, %v) failed: %v", newBucket, projectID, nil, err)
t.Fatalf("Bucket(%v).Create(%v, %v) failed: %v", newBucket, projectID, nil, err)
}
attrs, err := b.Attrs(ctx)
if err != nil {
@@ -154,9 +153,35 @@ func TestBucketMethods(t *testing.T) {
StorageClass: "NEARLINE",
VersioningEnabled: true,
Labels: labels,
Lifecycle: Lifecycle{
Rules: []LifecycleRule{{
Action: LifecycleAction{
Type: SetStorageClassAction,
StorageClass: "NEARLINE",
},
Condition: LifecycleCondition{
AgeInDays: 10,
Liveness: Archived,
CreatedBefore: time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC),
MatchesStorageClasses: []string{"MULTI_REGIONAL", "STANDARD"},
NumNewerVersions: 3,
},
}, {
Action: LifecycleAction{
Type: DeleteAction,
},
Condition: LifecycleCondition{
AgeInDays: 30,
Liveness: Live,
CreatedBefore: time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC),
MatchesStorageClasses: []string{"NEARLINE"},
NumNewerVersions: 10,
},
}},
},
}
if err := client.Bucket(newBucket).Create(ctx, projectID, attrs); err != nil {
t.Errorf("Bucket(%v).Create(%v, %v) failed: %v", newBucket, projectID, attrs, err)
t.Fatalf("Bucket(%v).Create(%v, %+v) failed: %v", newBucket, projectID, attrs, err)
}
attrs, err = b.Attrs(ctx)
if err != nil {
@@ -171,7 +196,7 @@ func TestBucketMethods(t *testing.T) {
if !attrs.VersioningEnabled {
t.Error("got versioning disabled, wanted it enabled")
}
if got, want := attrs.Labels, labels; !reflect.DeepEqual(got, want) {
if got, want := attrs.Labels, labels; !testutil.Equal(got, want) {
t.Errorf("labels: got %v, want %v", got, want)
}
}
@@ -224,7 +249,7 @@ func TestIntegration_BucketUpdate(t *testing.T) {
"l1": "v1",
"empty": "",
}
if !reflect.DeepEqual(attrs.Labels, wantLabels) {
if !testutil.Equal(attrs.Labels, wantLabels) {
t.Fatalf("got %v, want %v", attrs.Labels, wantLabels)
}
@@ -245,7 +270,7 @@ func TestIntegration_BucketUpdate(t *testing.T) {
"l1": "v2",
"new": "new",
}
if !reflect.DeepEqual(attrs.Labels, wantLabels) {
if !testutil.Equal(attrs.Labels, wantLabels) {
t.Fatalf("got %v, want %v", attrs.Labels, wantLabels)
}
}
@@ -339,6 +364,9 @@ func TestObjects(t *testing.T) {
if got, want := rc.ContentType(), "text/plain"; got != want {
t.Errorf("ContentType (%q) = %q; want %q", obj, got, want)
}
if got, want := rc.CacheControl(), "public, max-age=60"; got != want {
t.Errorf("CacheControl (%q) = %q; want %q", obj, got, want)
}
rc.Close()
// Check early close.
@@ -543,7 +571,7 @@ func TestObjects(t *testing.T) {
if got, want := updated.ContentLanguage, "en"; got != want {
t.Errorf("updated.ContentLanguage == %q; want %q", updated.ContentLanguage, want)
}
if got, want := updated.Metadata, metadata; !reflect.DeepEqual(got, want) {
if got, want := updated.Metadata, metadata; !testutil.Equal(got, want) {
t.Errorf("updated.Metadata == %+v; want %+v", updated.Metadata, want)
}
if got, want := updated.Created, created; got != want {
@@ -912,7 +940,7 @@ func TestZeroSizedObject(t *testing.T) {
func TestIntegration_Encryption(t *testing.T) {
// This function tests customer-supplied encryption keys for all operations
// involving objects. Bucket and ACL operations aren't tested because they
// aren't affected customer encryption.
// aren't affected by customer encryption. Neither is deletion.
ctx := context.Background()
client, bucket := testConfig(ctx, t)
defer client.Close()
@@ -1157,7 +1185,7 @@ func TestIntegration_BucketInCopyAttrs(t *testing.T) {
}
copier := obj.CopierFrom(obj)
rawObject := copier.ObjectAttrs.toRawObject(bucket)
_, err := copier.callRewrite(ctx, obj, rawObject)
_, err := copier.callRewrite(ctx, rawObject)
if err == nil {
t.Errorf("got nil, want error")
}
@@ -1299,14 +1327,131 @@ func TestIntegration_BucketIAM(t *testing.T) {
}
sort.Strings(perms)
sort.Strings(got)
if !reflect.DeepEqual(got, perms) {
if !testutil.Equal(got, perms) {
t.Errorf("got %v, want %v", got, perms)
}
}
func TestIntegration_RequesterPays(t *testing.T) {
ctx := context.Background()
client, bucketName := testConfig(ctx, t)
defer client.Close()
b := client.Bucket(bucketName + "-rp")
projID := testutil.ProjID()
// Extract the error code from err if it's a googleapi.Error.
errCode := func(err error) int {
if err == nil {
return 0
}
if err, ok := err.(*googleapi.Error); ok {
return err.Code
}
return -1
}
// Call f twice on b, first without and then with a user project.
call := func(msg string, f func(b *BucketHandle) error) {
if err := f(b); err == nil {
if got, want := errCode(err), 400; got != want {
t.Errorf("%s: got error code %d, want %d", msg, got, want)
}
}
if err := f(b.UserProject(projID)); err != nil {
t.Errorf("%s: got %v, want nil", msg, err)
}
}
// Create a requester-pays bucket.
err := b.Create(ctx, projID, &BucketAttrs{RequesterPays: true})
if err != nil {
t.Fatal(err)
}
// Getting its attributes requires a user project.
var attrs *BucketAttrs
call("Bucket attrs", func(b *BucketHandle) (err error) {
attrs, err = b.Attrs(ctx)
return err
})
if got, want := attrs.RequesterPays, true; got != want {
t.Fatalf("attr.RequesterPays = %b, want %b", got, want)
}
// Object operations.
call("write object", func(b *BucketHandle) error {
return writeObject(ctx, b.Object("foo"), "text/plain", []byte("hello"))
})
// // TODO(jba): add read test when XML API has requester-pays support.
// callObject("object attrs", o, func(o *ObjectHandle) error {
// _, err := o.Attrs(ctx)
// return err
// })
call("update object", func(b *BucketHandle) error {
_, err := b.Object("foo").Update(ctx, ObjectAttrsToUpdate{ContentLanguage: "en"})
return err
})
// ACL operations.
entity := ACLEntity("domain-google.com")
call("bucket acl set", func(b *BucketHandle) error {
return b.ACL().Set(ctx, entity, RoleReader)
})
call("bucket acl list", func(b *BucketHandle) error {
_, err := b.ACL().List(ctx)
return err
})
call("bucket acl delete", func(b *BucketHandle) error {
return b.ACL().Delete(ctx, entity)
})
call("default object acl set", func(b *BucketHandle) error {
return b.DefaultObjectACL().Set(ctx, entity, RoleReader)
})
call("default object acl list", func(b *BucketHandle) error {
_, err := b.DefaultObjectACL().List(ctx)
return err
})
call("default object acl delete", func(b *BucketHandle) error {
return b.DefaultObjectACL().Delete(ctx, entity)
})
call("object acl set", func(b *BucketHandle) error {
return b.Object("foo").ACL().Set(ctx, entity, RoleReader)
})
call("object acl list", func(b *BucketHandle) error {
_, err := b.Object("foo").ACL().List(ctx)
return err
})
call("object acl delete", func(b *BucketHandle) error {
return b.Object("foo").ACL().Delete(ctx, entity)
})
// Copy and compose.
call("copy", func(b *BucketHandle) error {
_, err := b.Object("copy").CopierFrom(b.Object("foo")).Run(ctx)
return err
})
call("compose", func(b *BucketHandle) error {
_, err := b.Object("compose").ComposerFrom(b.Object("foo"), b.Object("copy")).Run(ctx)
return err
})
// Deletion.
call("delete object", func(b *BucketHandle) error {
return b.Object("foo").Delete(ctx)
})
for _, obj := range []string{"copy", "compose"} {
if err := b.UserProject(projID).Object(obj).Delete(ctx); err != nil {
t.Fatalf("could not delete %q: %v", obj, err)
}
}
call("delete bucket", func(b *BucketHandle) error {
return b.Delete(ctx)
})
}
func writeObject(ctx context.Context, obj *ObjectHandle, contentType string, contents []byte) error {
w := obj.NewWriter(ctx)
w.ContentType = contentType
w.CacheControl = "public, max-age=60"
if contents != nil {
if _, err := w.Write(contents); err != nil {
_ = w.Close()
+1 -8
View File
@@ -18,7 +18,6 @@ import (
"cloud.google.com/go/internal"
gax "github.com/googleapis/gax-go"
"golang.org/x/net/context"
"google.golang.org/api/googleapi"
)
// runWithRetry calls the function until it returns nil or a non-retryable error, or
@@ -29,13 +28,7 @@ func runWithRetry(ctx context.Context, call func() error) error {
if err == nil {
return true, nil
}
e, ok := err.(*googleapi.Error)
if !ok {
return true, err
}
// Retry on 429 and 5xx, according to
// https://cloud.google.com/storage/docs/exponential-backoff.
if e.Code == 429 || (e.Code >= 500 && e.Code < 600) {
if shouldRetry(err) {
return false, nil
}
return true, err
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !go1.10
package storage
import (
"net/url"
"strings"
"google.golang.org/api/googleapi"
)
func shouldRetry(err error) bool {
switch e := err.(type) {
case *googleapi.Error:
// Retry on 429 and 5xx, according to
// https://cloud.google.com/storage/docs/exponential-backoff.
return e.Code == 429 || (e.Code >= 500 && e.Code < 600)
case *url.Error:
// Retry on REFUSED_STREAM.
// Unfortunately the error type is unexported, so we resort to string
// matching.
return strings.Contains(e.Error(), "REFUSED_STREAM")
default:
return false
}
}
+6
View File
@@ -28,6 +28,7 @@ type Reader struct {
body io.ReadCloser
remain, size int64
contentType string
cacheControl string
checkCRC bool // should we check the CRC?
wantCRC uint32 // the CRC32c value the server sent in the header
gotCRC uint32 // running crc
@@ -72,3 +73,8 @@ func (r *Reader) Remain() int64 {
func (r *Reader) ContentType() string {
return r.contentType
}
// CacheControl returns the cache control of the object.
func (r *Reader) CacheControl() string {
return r.cacheControl
}
+63 -33
View File
@@ -36,7 +36,7 @@ import (
"unicode/utf8"
"google.golang.org/api/option"
"google.golang.org/api/transport"
htransport "google.golang.org/api/transport/http"
"cloud.google.com/go/internal/optional"
"cloud.google.com/go/internal/version"
@@ -89,7 +89,7 @@ func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error
option.WithUserAgent(userAgent),
}
opts = append(o, opts...)
hc, ep, err := transport.NewHTTPClient(ctx, opts...)
hc, ep, err := htransport.NewClient(ctx, opts...)
if err != nil {
return nil, fmt.Errorf("dialing: %v", err)
}
@@ -110,7 +110,10 @@ func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error
//
// Close need not be called at program exit.
func (c *Client) Close() error {
// Set fields to nil so that subsequent uses
// will panic.
c.hc = nil
c.raw = nil
return nil
}
@@ -262,6 +265,7 @@ type ObjectHandle struct {
gen int64 // a negative value indicates latest
conds *Conditions
encryptionKey []byte // AES-256 key
userProject string // for requester-pays buckets
}
// ACL provides access to the object's access control list.
@@ -314,6 +318,9 @@ func (o *ObjectHandle) Attrs(ctx context.Context) (*ObjectAttrs, error) {
if err := applyConds("Attrs", o.gen, o.conds, call); err != nil {
return nil, err
}
if o.userProject != "" {
call.UserProject(o.userProject)
}
if err := setEncryptionHeaders(call.Header(), o.encryptionKey, false); err != nil {
return nil, err
}
@@ -342,11 +349,17 @@ func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (
var forceSendFields, nullFields []string
if uattrs.ContentType != nil {
attrs.ContentType = optional.ToString(uattrs.ContentType)
forceSendFields = append(forceSendFields, "ContentType")
// For ContentType, sending the empty string is a no-op.
// Instead we send a null.
if attrs.ContentType == "" {
nullFields = append(nullFields, "ContentType")
} else {
forceSendFields = append(forceSendFields, "ContentType")
}
}
if uattrs.ContentLanguage != nil {
attrs.ContentLanguage = optional.ToString(uattrs.ContentLanguage)
// For ContentLanguage It's an error to send the empty string.
// For ContentLanguage it's an error to send the empty string.
// Instead we send a null.
if attrs.ContentLanguage == "" {
nullFields = append(nullFields, "ContentLanguage")
@@ -356,7 +369,7 @@ func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (
}
if uattrs.ContentEncoding != nil {
attrs.ContentEncoding = optional.ToString(uattrs.ContentEncoding)
forceSendFields = append(forceSendFields, "ContentType")
forceSendFields = append(forceSendFields, "ContentEncoding")
}
if uattrs.ContentDisposition != nil {
attrs.ContentDisposition = optional.ToString(uattrs.ContentDisposition)
@@ -388,6 +401,9 @@ func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (
if err := applyConds("Update", o.gen, o.conds, call); err != nil {
return nil, err
}
if o.userProject != "" {
call.UserProject(o.userProject)
}
if err := setEncryptionHeaders(call.Header(), o.encryptionKey, false); err != nil {
return nil, err
}
@@ -434,6 +450,10 @@ func (o *ObjectHandle) Delete(ctx context.Context) error {
if err := applyConds("Delete", o.gen, o.conds, call); err != nil {
return err
}
if o.userProject != "" {
call.UserProject(o.userProject)
}
// Encryption doesn't apply to Delete.
setClientHeader(call.Header())
err := runWithRetry(ctx, func() error { return call.Do() })
switch e := err.(type) {
@@ -491,6 +511,9 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64)
} else if length > 0 {
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
}
if o.userProject != "" {
req.Header.Set("X-Goog-User-Project", o.userProject)
}
if err := setEncryptionHeaders(req.Header, o.encryptionKey, false); err != nil {
return nil, err
}
@@ -553,12 +576,13 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64)
crc, checkCRC = parseCRC32c(res)
}
return &Reader{
body: body,
size: size,
remain: remain,
contentType: res.Header.Get("Content-Type"),
wantCRC: crc,
checkCRC: checkCRC,
body: body,
size: size,
remain: remain,
contentType: res.Header.Get("Content-Type"),
cacheControl: res.Header.Get("Cache-Control"),
wantCRC: crc,
checkCRC: checkCRC,
}, nil
}
@@ -706,11 +730,16 @@ type ObjectAttrs struct {
// sent in the response headers.
ContentDisposition string
// MD5 is the MD5 hash of the object's content. This field is read-only.
// MD5 is the MD5 hash of the object's content. This field is read-only,
// except when used from a Writer. If set on a Writer, the uploaded
// data is rejected if its MD5 hash does not match this field.
MD5 []byte
// CRC32C is the CRC32 checksum of the object's content using
// the Castagnoli93 polynomial. This field is read-only.
// the Castagnoli93 polynomial. This field is read-only, except when
// used from a Writer. If set on a Writer and Writer.SendCRC32C
// is true, the uploaded data is rejected if its CRC32c hash does not
// match this field.
CRC32C uint32
// MediaLink is an URL to the object's content. This field is read-only.
@@ -798,26 +827,27 @@ func newObject(o *raw.Object) *ObjectAttrs {
sha256 = o.CustomerEncryption.KeySha256
}
return &ObjectAttrs{
Bucket: o.Bucket,
Name: o.Name,
ContentType: o.ContentType,
ContentLanguage: o.ContentLanguage,
CacheControl: o.CacheControl,
ACL: acl,
Owner: owner,
ContentEncoding: o.ContentEncoding,
Size: int64(o.Size),
MD5: md5,
CRC32C: crc32c,
MediaLink: o.MediaLink,
Metadata: o.Metadata,
Generation: o.Generation,
Metageneration: o.Metageneration,
StorageClass: o.StorageClass,
CustomerKeySHA256: sha256,
Created: convertTime(o.TimeCreated),
Deleted: convertTime(o.TimeDeleted),
Updated: convertTime(o.Updated),
Bucket: o.Bucket,
Name: o.Name,
ContentType: o.ContentType,
ContentLanguage: o.ContentLanguage,
CacheControl: o.CacheControl,
ACL: acl,
Owner: owner,
ContentEncoding: o.ContentEncoding,
ContentDisposition: o.ContentDisposition,
Size: int64(o.Size),
MD5: md5,
CRC32C: crc32c,
MediaLink: o.MediaLink,
Metadata: o.Metadata,
Generation: o.Generation,
Metageneration: o.Metageneration,
StorageClass: o.StorageClass,
CustomerKeySHA256: sha256,
Created: convertTime(o.TimeCreated),
Deleted: convertTime(o.TimeDeleted),
Updated: convertTime(o.Updated),
}
}
+86 -2
View File
@@ -24,12 +24,14 @@ import (
"net"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"time"
"cloud.google.com/go/internal/testutil"
"golang.org/x/net/context"
"google.golang.org/api/googleapi"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
raw "google.golang.org/api/storage/v1"
@@ -620,7 +622,7 @@ func TestObjectCompose(t *testing.T) {
if err := json.Unmarshal(body, &req); err != nil {
t.Errorf("%s: json.Unmarshal %v (body %s)", tt.desc, err, body)
}
if !reflect.DeepEqual(req, tt.wantReq) {
if !testutil.Equal(req, tt.wantReq) {
// Print to JSON.
wantReq, _ := json.Marshal(tt.wantReq)
t.Errorf("%s: request body\ngot %s\nwant %s", tt.desc, body, wantReq)
@@ -685,6 +687,88 @@ func TestCodecUint32(t *testing.T) {
}
}
func TestBucketAttrs(t *testing.T) {
for _, c := range []struct {
attrs BucketAttrs
raw raw.Bucket
}{{
attrs: BucketAttrs{
Lifecycle: Lifecycle{
Rules: []LifecycleRule{{
Action: LifecycleAction{
Type: SetStorageClassAction,
StorageClass: "NEARLINE",
},
Condition: LifecycleCondition{
AgeInDays: 10,
Liveness: Live,
CreatedBefore: time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC),
MatchesStorageClasses: []string{"MULTI_REGIONAL", "REGIONAL", "STANDARD"},
NumNewerVersions: 3,
},
}, {
Action: LifecycleAction{
Type: DeleteAction,
},
Condition: LifecycleCondition{
AgeInDays: 30,
Liveness: Live,
CreatedBefore: time.Date(2017, 1, 2, 3, 4, 5, 6, time.UTC),
MatchesStorageClasses: []string{"NEARLINE"},
NumNewerVersions: 10,
},
}, {
Action: LifecycleAction{
Type: DeleteAction,
},
Condition: LifecycleCondition{
Liveness: Archived,
},
}},
},
},
raw: raw.Bucket{
Lifecycle: &raw.BucketLifecycle{
Rule: []*raw.BucketLifecycleRule{{
Action: &raw.BucketLifecycleRuleAction{
Type: SetStorageClassAction,
StorageClass: "NEARLINE",
},
Condition: &raw.BucketLifecycleRuleCondition{
Age: 10,
IsLive: googleapi.Bool(true),
CreatedBefore: "2017-01-02",
MatchesStorageClass: []string{"MULTI_REGIONAL", "REGIONAL", "STANDARD"},
NumNewerVersions: 3,
},
}, {
Action: &raw.BucketLifecycleRuleAction{
Type: DeleteAction,
},
Condition: &raw.BucketLifecycleRuleCondition{
Age: 30,
IsLive: googleapi.Bool(true),
CreatedBefore: "2017-01-02",
MatchesStorageClass: []string{"NEARLINE"},
NumNewerVersions: 10,
},
}, {
Action: &raw.BucketLifecycleRuleAction{
Type: DeleteAction,
},
Condition: &raw.BucketLifecycleRuleCondition{
IsLive: googleapi.Bool(false),
},
}},
},
},
}} {
if got := c.attrs.toRawBucket(); !testutil.Equal(*got, c.raw) {
t.Errorf("toRawBucket: got %v, want %v", *got, c.raw)
}
}
}
func newTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*http.Client, func()) {
ts := httptest.NewTLSServer(http.HandlerFunc(handler))
tlsConf := &tls.Config{InsecureSkipVerify: true}
+21 -9
View File
@@ -36,6 +36,8 @@ type Writer struct {
// SendCRC specifies whether to transmit a CRC32C field. It should be set
// to true in addition to setting the Writer's CRC32C field, because zero
// is a valid CRC and normally a zero would not be transmitted.
// If a CRC32C is sent, and the data written does not match the checksum,
// the write will be rejected.
SendCRC32C bool
// ChunkSize controls the maximum number of bytes of the object that the
@@ -85,7 +87,7 @@ func (w *Writer) open() error {
w.opened = true
if w.ChunkSize < 0 {
return errors.New("storage: Writer.ChunkSize must non-negative")
return errors.New("storage: Writer.ChunkSize must be non-negative")
}
mediaOpts := []googleapi.MediaOption{
googleapi.ChunkSize(w.ChunkSize),
@@ -119,15 +121,25 @@ func (w *Writer) open() error {
var resp *raw.Object
err := applyConds("NewWriter", w.o.gen, w.o.conds, call)
if err == nil {
if w.o.userProject != "" {
call.UserProject(w.o.userProject)
}
setClientHeader(call.Header())
// We will only retry here if the initial POST, which obtains a URI for
// the resumable upload, fails with a retryable error. The upload itself
// has its own retry logic.
err = runWithRetry(w.ctx, func() error {
var err2 error
resp, err2 = call.Do()
return err2
})
// If the chunk size is zero, then no chunking is done on the Reader,
// which means we cannot retry: the first call will read the data, and if
// it fails, there is no way to re-read.
if w.ChunkSize == 0 {
resp, err = call.Do()
} else {
// We will only retry here if the initial POST, which obtains a URI for
// the resumable upload, fails with a retryable error. The upload itself
// has its own retry logic.
err = runWithRetry(w.ctx, func() error {
var err2 error
resp, err2 = call.Do()
return err2
})
}
}
if err != nil {
w.err = err
+19 -4
View File
@@ -21,10 +21,11 @@ import (
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"cloud.google.com/go/internal/testutil"
"golang.org/x/net/context"
"google.golang.org/api/option"
@@ -32,6 +33,7 @@ import (
type fakeTransport struct {
gotReq *http.Request
gotBody []byte
results []transportResult
}
@@ -46,6 +48,14 @@ func (t *fakeTransport) addResult(res *http.Response, err error) {
func (t *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
t.gotReq = req
t.gotBody = nil
if req.Body != nil {
bytes, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
t.gotBody = bytes
}
if len(t.results) == 0 {
return nil, fmt.Errorf("error handling request")
}
@@ -57,6 +67,7 @@ func (t *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
func TestErrorOnObjectsInsertCall(t *testing.T) {
t.Parallel()
ctx := context.Background()
const contents = "hello world"
doWrite := func(hc *http.Client) *Writer {
client, err := NewClient(ctx, option.WithHTTPClient(hc))
@@ -68,7 +79,7 @@ func TestErrorOnObjectsInsertCall(t *testing.T) {
// We can't check that the Write fails, since it depends on the write to the
// underling fakeTransport failing which is racy.
wc.Write([]byte("hello world"))
wc.Write([]byte(contents))
return wc
}
@@ -93,6 +104,10 @@ func TestErrorOnObjectsInsertCall(t *testing.T) {
if err := wc.Close(); err != nil {
t.Errorf("got %v, want nil", err)
}
got := string(ft.gotBody)
if !strings.Contains(got, contents) {
t.Errorf("got body %q, which does not contain %q", got, contents)
}
}
func TestEncryption(t *testing.T) {
@@ -117,7 +132,7 @@ func TestEncryption(t *testing.T) {
if err != nil {
t.Fatalf("decoding key: %v", err)
}
if !reflect.DeepEqual(gotKey, key) {
if !testutil.Equal(gotKey, key) {
t.Errorf("key: got %v, want %v", gotKey, key)
}
wantHash := sha256.Sum256(key)
@@ -125,7 +140,7 @@ func TestEncryption(t *testing.T) {
if err != nil {
t.Fatalf("decoding hash: %v", err)
}
if !reflect.DeepEqual(gotHash, wantHash[:]) { // wantHash is an array
if !testutil.Equal(gotHash, wantHash[:]) { // wantHash is an array
t.Errorf("hash: got\n%v, want\n%v", gotHash, wantHash)
}
}