vendor: update all dependencies to latest versions
This commit is contained in:
+53
-42
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user