Move to vendor
Signed-off-by: Olivier Gambier <olivier@docker.com>
This commit is contained in:
parent
c8d8e7e357
commit
77e69b9cf3
1268 changed files with 34 additions and 24 deletions
176
vendor/google.golang.org/cloud/storage/acl.go
generated
vendored
Normal file
176
vendor/google.golang.org/cloud/storage/acl.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// ACLRole is the the access permission for the entity.
|
||||
type ACLRole string
|
||||
|
||||
const (
|
||||
RoleOwner ACLRole = "OWNER"
|
||||
RoleReader ACLRole = "READER"
|
||||
)
|
||||
|
||||
// ACLEntity is an entity holding an ACL permission.
|
||||
//
|
||||
// It could be in the form of:
|
||||
// "user-<userId>", "user-<email>","group-<groupId>", "group-<email>",
|
||||
// "domain-<domain>" and "project-team-<projectId>".
|
||||
//
|
||||
// Or one of the predefined constants: AllUsers, AllAuthenticatedUsers.
|
||||
type ACLEntity string
|
||||
|
||||
const (
|
||||
AllUsers ACLEntity = "allUsers"
|
||||
AllAuthenticatedUsers ACLEntity = "allAuthenticatedUsers"
|
||||
)
|
||||
|
||||
// ACLRule represents an access control list rule entry for a Google Cloud Storage object or bucket.
|
||||
// A bucket is a Google Cloud Storage container whose name is globally unique and contains zero or
|
||||
// more objects. An object is a blob of data that is stored in a bucket.
|
||||
type ACLRule struct {
|
||||
// Entity identifies the entity holding the current rule's permissions.
|
||||
Entity ACLEntity
|
||||
|
||||
// Role is the the access permission for the entity.
|
||||
Role ACLRole
|
||||
}
|
||||
|
||||
// DefaultACL returns the default object ACL entries for the named bucket.
|
||||
func DefaultACL(ctx context.Context, bucket string) ([]ACLRule, error) {
|
||||
acls, err := rawService(ctx).DefaultObjectAccessControls.List(bucket).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: error listing default object ACL for bucket %q: %v", bucket, err)
|
||||
}
|
||||
r := make([]ACLRule, 0, len(acls.Items))
|
||||
for _, v := range acls.Items {
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
entity, ok1 := m["entity"].(string)
|
||||
role, ok2 := m["role"].(string)
|
||||
if ok1 && ok2 {
|
||||
r = append(r, ACLRule{Entity: ACLEntity(entity), Role: ACLRole(role)})
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// PutDefaultACLRule saves the named default object ACL entity with the provided role for the named bucket.
|
||||
func PutDefaultACLRule(ctx context.Context, bucket string, entity ACLEntity, role ACLRole) error {
|
||||
acl := &raw.ObjectAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
_, err := rawService(ctx).DefaultObjectAccessControls.Update(bucket, string(entity), acl).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error updating default ACL rule for bucket %q, entity %q: %v", bucket, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDefaultACLRule deletes the named default ACL entity for the named bucket.
|
||||
func DeleteDefaultACLRule(ctx context.Context, bucket string, entity ACLEntity) error {
|
||||
err := rawService(ctx).DefaultObjectAccessControls.Delete(bucket, string(entity)).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error deleting default ACL rule for bucket %q, entity %q: %v", bucket, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BucketACL returns the ACL entries for the named bucket.
|
||||
func BucketACL(ctx context.Context, bucket string) ([]ACLRule, error) {
|
||||
acls, err := rawService(ctx).BucketAccessControls.List(bucket).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: error listing bucket ACL for bucket %q: %v", bucket, err)
|
||||
}
|
||||
r := make([]ACLRule, len(acls.Items))
|
||||
for i, v := range acls.Items {
|
||||
r[i].Entity = ACLEntity(v.Entity)
|
||||
r[i].Role = ACLRole(v.Role)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// PutBucketACLRule saves the named ACL entity with the provided role for the named bucket.
|
||||
func PutBucketACLRule(ctx context.Context, bucket string, entity ACLEntity, role ACLRole) error {
|
||||
acl := &raw.BucketAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
_, err := rawService(ctx).BucketAccessControls.Update(bucket, string(entity), acl).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error updating bucket ACL rule for bucket %q, entity %q: %v", bucket, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteBucketACLRule deletes the named ACL entity for the named bucket.
|
||||
func DeleteBucketACLRule(ctx context.Context, bucket string, entity ACLEntity) error {
|
||||
err := rawService(ctx).BucketAccessControls.Delete(bucket, string(entity)).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error deleting bucket ACL rule for bucket %q, entity %q: %v", bucket, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ACL returns the ACL entries for the named object.
|
||||
func ACL(ctx context.Context, bucket, object string) ([]ACLRule, error) {
|
||||
acls, err := rawService(ctx).ObjectAccessControls.List(bucket, object).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: error listing object ACL for bucket %q, file %q: %v", bucket, object, err)
|
||||
}
|
||||
r := make([]ACLRule, 0, len(acls.Items))
|
||||
for _, v := range acls.Items {
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
entity, ok1 := m["entity"].(string)
|
||||
role, ok2 := m["role"].(string)
|
||||
if ok1 && ok2 {
|
||||
r = append(r, ACLRule{Entity: ACLEntity(entity), Role: ACLRole(role)})
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// PutACLRule saves the named ACL entity with the provided role for the named object.
|
||||
func PutACLRule(ctx context.Context, bucket, object string, entity ACLEntity, role ACLRole) error {
|
||||
acl := &raw.ObjectAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(entity),
|
||||
Role: string(role),
|
||||
}
|
||||
_, err := rawService(ctx).ObjectAccessControls.Update(bucket, object, string(entity), acl).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error updating object ACL rule for bucket %q, file %q, entity %q: %v", bucket, object, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteACLRule deletes the named ACL entity for the named object.
|
||||
func DeleteACLRule(ctx context.Context, bucket, object string, entity ACLEntity) error {
|
||||
err := rawService(ctx).ObjectAccessControls.Delete(bucket, object, string(entity)).Context(ctx).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error deleting object ACL rule for bucket %q, file %q, entity %q: %v", bucket, object, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
150
vendor/google.golang.org/cloud/storage/example_test.go
generated
vendored
Normal file
150
vendor/google.golang.org/cloud/storage/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/cloud"
|
||||
"google.golang.org/cloud/storage"
|
||||
)
|
||||
|
||||
// TODO(jbd): Remove after Go 1.4.
|
||||
// Related to https://codereview.appspot.com/107320046
|
||||
func TestA(t *testing.T) {}
|
||||
|
||||
func Example_auth() context.Context {
|
||||
// Initialize an authorized context with Google Developers Console
|
||||
// JSON key. Read the google package examples to learn more about
|
||||
// different authorization flows you can use.
|
||||
// http://godoc.org/golang.org/x/oauth2/google
|
||||
jsonKey, err := ioutil.ReadFile("/path/to/json/keyfile.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
conf, err := google.JWTConfigFromJSON(
|
||||
jsonKey,
|
||||
storage.ScopeFullControl,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ctx := cloud.NewContext("project-id", conf.Client(oauth2.NoContext))
|
||||
// Use the context (see other examples)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func ExampleListObjects() {
|
||||
ctx := Example_auth()
|
||||
|
||||
var query *storage.Query
|
||||
for {
|
||||
// If you are using this package on App Engine Managed VMs runtime,
|
||||
// you can init a bucket client with your app's default bucket name.
|
||||
// See http://godoc.org/google.golang.org/appengine/file#DefaultBucketName.
|
||||
objects, err := storage.ListObjects(ctx, "bucketname", query)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, obj := range objects.Results {
|
||||
log.Printf("object name: %s, size: %v", obj.Name, obj.Size)
|
||||
}
|
||||
// if there are more results, objects.Next
|
||||
// will be non-nil.
|
||||
query = objects.Next
|
||||
if query == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("paginated through all object items in the bucket you specified.")
|
||||
}
|
||||
|
||||
func ExampleNewReader() {
|
||||
ctx := Example_auth()
|
||||
|
||||
rc, err := storage.NewReader(ctx, "bucketname", "filename1")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
slurp, err := ioutil.ReadAll(rc)
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("file contents:", slurp)
|
||||
}
|
||||
|
||||
func ExampleNewWriter() {
|
||||
ctx := Example_auth()
|
||||
|
||||
wc := storage.NewWriter(ctx, "bucketname", "filename1")
|
||||
wc.ContentType = "text/plain"
|
||||
wc.ACL = []storage.ACLRule{{storage.AllUsers, storage.RoleReader}}
|
||||
if _, err := wc.Write([]byte("hello world")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := wc.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("updated object:", wc.Object())
|
||||
}
|
||||
|
||||
func ExampleCopyObject() {
|
||||
ctx := Example_auth()
|
||||
|
||||
o, err := storage.CopyObject(ctx, "bucketname", "file1", "another-bucketname", "file2", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("copied file:", o)
|
||||
}
|
||||
|
||||
func ExampleDeleteObject() {
|
||||
// To delete multiple objects in a bucket, first ListObjects then delete them.
|
||||
ctx := Example_auth()
|
||||
|
||||
// If you are using this package on App Engine Managed VMs runtime,
|
||||
// you can init a bucket client with your app's default bucket name.
|
||||
// See http://godoc.org/google.golang.org/appengine/file#DefaultBucketName.
|
||||
const bucket = "bucketname"
|
||||
|
||||
var query *storage.Query // Set up query as desired.
|
||||
for {
|
||||
objects, err := storage.ListObjects(ctx, bucket, query)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, obj := range objects.Results {
|
||||
log.Printf("deleting object name: %q, size: %v", obj.Name, obj.Size)
|
||||
if err := storage.DeleteObject(ctx, bucket, obj.Name); err != nil {
|
||||
log.Fatalf("unable to delete %q: %v", obj.Name, err)
|
||||
}
|
||||
}
|
||||
// if there are more results, objects.Next will be non-nil.
|
||||
query = objects.Next
|
||||
if query == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("deleted all object items in the bucket you specified.")
|
||||
}
|
327
vendor/google.golang.org/cloud/storage/integration_test.go
generated
vendored
Normal file
327
vendor/google.golang.org/cloud/storage/integration_test.go
generated
vendored
Normal file
|
@ -0,0 +1,327 @@
|
|||
// Copyright 2014 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 integration
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/cloud/internal"
|
||||
"google.golang.org/cloud/internal/testutil"
|
||||
)
|
||||
|
||||
var (
|
||||
bucket string
|
||||
contents = make(map[string][]byte)
|
||||
objects = []string{"obj1", "obj2", "obj/with/slashes"}
|
||||
aclObjects = []string{"acl1", "acl2"}
|
||||
copyObj = "copy-object"
|
||||
)
|
||||
|
||||
const envBucket = "GCLOUD_TESTS_GOLANG_PROJECT_ID"
|
||||
|
||||
func TestObjects(t *testing.T) {
|
||||
ctx := testutil.Context(ScopeFullControl)
|
||||
bucket = os.Getenv(envBucket)
|
||||
|
||||
// Cleanup.
|
||||
cleanup(t, "obj")
|
||||
|
||||
const defaultType = "text/plain"
|
||||
|
||||
// Test Writer.
|
||||
for _, obj := range objects {
|
||||
t.Logf("Writing %v", obj)
|
||||
wc := NewWriter(ctx, bucket, obj)
|
||||
wc.ContentType = defaultType
|
||||
c := randomContents()
|
||||
if _, err := wc.Write(c); err != nil {
|
||||
t.Errorf("Write for %v failed with %v", obj, err)
|
||||
}
|
||||
if err := wc.Close(); err != nil {
|
||||
t.Errorf("Close for %v failed with %v", obj, err)
|
||||
}
|
||||
contents[obj] = c
|
||||
}
|
||||
|
||||
// Test Reader.
|
||||
for _, obj := range objects {
|
||||
t.Logf("Creating a reader to read %v", obj)
|
||||
rc, err := NewReader(ctx, bucket, obj)
|
||||
if err != nil {
|
||||
t.Errorf("Can't create a reader for %v, errored with %v", obj, err)
|
||||
}
|
||||
slurp, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
t.Errorf("Can't ReadAll object %v, errored with %v", obj, err)
|
||||
}
|
||||
if got, want := slurp, contents[obj]; !bytes.Equal(got, want) {
|
||||
t.Errorf("Contents (%v) = %q; want %q", obj, got, want)
|
||||
}
|
||||
rc.Close()
|
||||
|
||||
// Test SignedURL
|
||||
opts := &SignedURLOptions{
|
||||
GoogleAccessID: "xxx@clientid",
|
||||
PrivateKey: dummyKey("rsa"),
|
||||
Method: "GET",
|
||||
MD5: []byte("202cb962ac59075b964b07152d234b70"),
|
||||
Expires: time.Date(2020, time.October, 2, 10, 0, 0, 0, time.UTC),
|
||||
ContentType: "application/json",
|
||||
Headers: []string{"x-header1", "x-header2"},
|
||||
}
|
||||
u, err := SignedURL(bucket, obj, opts)
|
||||
if err != nil {
|
||||
t.Fatalf("SignedURL(%q, %q) errored with %v", bucket, obj, err)
|
||||
}
|
||||
hc := internal.HTTPClient(ctx)
|
||||
res, err := hc.Get(u)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't get URL %q: %v", u, err)
|
||||
}
|
||||
slurp, err = ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't ReadAll signed object %v, errored with %v", obj, err)
|
||||
}
|
||||
if got, want := slurp, contents[obj]; !bytes.Equal(got, want) {
|
||||
t.Errorf("Contents (%v) = %q; want %q", obj, got, want)
|
||||
}
|
||||
res.Body.Close()
|
||||
}
|
||||
|
||||
// Test NotFound.
|
||||
_, err := NewReader(ctx, bucket, "obj-not-exists")
|
||||
if err != ErrObjectNotExist {
|
||||
t.Errorf("Object should not exist, err found to be %v", err)
|
||||
}
|
||||
|
||||
name := objects[0]
|
||||
|
||||
// Test StatObject.
|
||||
o, err := StatObject(ctx, bucket, name)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got, want := o.Name, name; got != want {
|
||||
t.Errorf("Name (%v) = %q; want %q", name, got, want)
|
||||
}
|
||||
if got, want := o.ContentType, defaultType; got != want {
|
||||
t.Errorf("ContentType (%v) = %q; want %q", name, got, want)
|
||||
}
|
||||
|
||||
// Test object copy.
|
||||
copy, err := CopyObject(ctx, bucket, name, bucket, copyObj, nil)
|
||||
if err != nil {
|
||||
t.Errorf("CopyObject failed with %v", err)
|
||||
}
|
||||
if copy.Name != copyObj {
|
||||
t.Errorf("Copy object's name = %q; want %q", copy.Name, copyObj)
|
||||
}
|
||||
if copy.Bucket != bucket {
|
||||
t.Errorf("Copy object's bucket = %q; want %q", copy.Bucket, bucket)
|
||||
}
|
||||
|
||||
// Test UpdateAttrs.
|
||||
updated, err := UpdateAttrs(ctx, bucket, name, ObjectAttrs{
|
||||
ContentType: "text/html",
|
||||
ACL: []ACLRule{{Entity: "domain-google.com", Role: RoleReader}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("UpdateAttrs failed with %v", err)
|
||||
}
|
||||
if want := "text/html"; updated.ContentType != want {
|
||||
t.Errorf("updated.ContentType == %q; want %q", updated.ContentType, want)
|
||||
}
|
||||
|
||||
// Test checksums.
|
||||
checksumCases := []struct {
|
||||
name string
|
||||
contents [][]byte
|
||||
size int64
|
||||
md5 string
|
||||
crc32c uint32
|
||||
}{
|
||||
{
|
||||
name: "checksum-object",
|
||||
contents: [][]byte{[]byte("hello"), []byte("world")},
|
||||
size: 10,
|
||||
md5: "fc5e038d38a57032085441e7fe7010b0",
|
||||
crc32c: 1456190592,
|
||||
},
|
||||
{
|
||||
name: "zero-object",
|
||||
contents: [][]byte{},
|
||||
size: 0,
|
||||
md5: "d41d8cd98f00b204e9800998ecf8427e",
|
||||
crc32c: 0,
|
||||
},
|
||||
}
|
||||
for _, c := range checksumCases {
|
||||
wc := NewWriter(ctx, bucket, c.name)
|
||||
for _, data := range c.contents {
|
||||
if _, err := wc.Write(data); err != nil {
|
||||
t.Errorf("Write(%q) failed with %q", data, err)
|
||||
}
|
||||
}
|
||||
if err = wc.Close(); err != nil {
|
||||
t.Errorf("%q: close failed with %q", c.name, err)
|
||||
}
|
||||
obj := wc.Object()
|
||||
if got, want := obj.Size, c.size; got != want {
|
||||
t.Errorf("Object (%q) Size = %v; want %v", c.name, got, want)
|
||||
}
|
||||
if got, want := fmt.Sprintf("%x", obj.MD5), c.md5; got != want {
|
||||
t.Errorf("Object (%q) MD5 = %q; want %q", c.name, got, want)
|
||||
}
|
||||
if got, want := obj.CRC32C, c.crc32c; got != want {
|
||||
t.Errorf("Object (%q) CRC32C = %v; want %v", c.name, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Test public ACL.
|
||||
publicObj := objects[0]
|
||||
if err = PutACLRule(ctx, bucket, publicObj, AllUsers, RoleReader); err != nil {
|
||||
t.Errorf("PutACLRule failed with %v", err)
|
||||
}
|
||||
publicCtx := testutil.NoAuthContext()
|
||||
rc, err := NewReader(publicCtx, bucket, publicObj)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
slurp, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
t.Errorf("ReadAll failed with %v", err)
|
||||
}
|
||||
if string(slurp) != string(contents[publicObj]) {
|
||||
t.Errorf("Public object's content is expected to be %s, found %s", contents[publicObj], slurp)
|
||||
}
|
||||
rc.Close()
|
||||
|
||||
// Test writer error handling.
|
||||
wc := NewWriter(publicCtx, bucket, publicObj)
|
||||
if _, err := wc.Write([]byte("hello")); err != nil {
|
||||
t.Errorf("Write unexpectedly failed with %v", err)
|
||||
}
|
||||
if err = wc.Close(); err == nil {
|
||||
t.Error("Close expected an error, found none")
|
||||
}
|
||||
|
||||
// DeleteObject object.
|
||||
// The rest of the other object will be deleted during
|
||||
// the initial cleanup. This tests exists, so we still can cover
|
||||
// deletion if there are no objects on the bucket to clean.
|
||||
if err := DeleteObject(ctx, bucket, copyObj); err != nil {
|
||||
t.Errorf("Deletion of %v failed with %v", copyObj, err)
|
||||
}
|
||||
_, err = StatObject(ctx, bucket, copyObj)
|
||||
if err != ErrObjectNotExist {
|
||||
t.Errorf("Copy is expected to be deleted, stat errored with %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestACL(t *testing.T) {
|
||||
ctx := testutil.Context(ScopeFullControl)
|
||||
cleanup(t, "acl")
|
||||
entity := ACLEntity("domain-google.com")
|
||||
if err := PutDefaultACLRule(ctx, bucket, entity, RoleReader); err != nil {
|
||||
t.Errorf("Can't put default ACL rule for the bucket, errored with %v", err)
|
||||
}
|
||||
for _, obj := range aclObjects {
|
||||
t.Logf("Writing %v", obj)
|
||||
wc := NewWriter(ctx, bucket, obj)
|
||||
c := randomContents()
|
||||
if _, err := wc.Write(c); err != nil {
|
||||
t.Errorf("Write for %v failed with %v", obj, err)
|
||||
}
|
||||
if err := wc.Close(); err != nil {
|
||||
t.Errorf("Close for %v failed with %v", obj, err)
|
||||
}
|
||||
}
|
||||
name := aclObjects[0]
|
||||
acl, err := ACL(ctx, bucket, name)
|
||||
if err != nil {
|
||||
t.Errorf("Can't retrieve ACL of %v", name)
|
||||
}
|
||||
aclFound := false
|
||||
for _, rule := range acl {
|
||||
if rule.Entity == entity && rule.Role == RoleReader {
|
||||
aclFound = true
|
||||
}
|
||||
}
|
||||
if !aclFound {
|
||||
t.Error("Expected to find an ACL rule for google.com domain users, but not found")
|
||||
}
|
||||
if err := DeleteACLRule(ctx, bucket, name, entity); err != nil {
|
||||
t.Errorf("Can't delete the ACL rule for the entity: %v", entity)
|
||||
}
|
||||
|
||||
if err := PutBucketACLRule(ctx, bucket, "user-jbd@google.com", RoleReader); err != nil {
|
||||
t.Errorf("Error while putting bucket ACL rule: %v", err)
|
||||
}
|
||||
bACL, err := BucketACL(ctx, bucket)
|
||||
if err != nil {
|
||||
t.Errorf("Error while getting the ACL of the bucket: %v", err)
|
||||
}
|
||||
bACLFound := false
|
||||
for _, rule := range bACL {
|
||||
if rule.Entity == "user-jbd@google.com" && rule.Role == RoleReader {
|
||||
bACLFound = true
|
||||
}
|
||||
}
|
||||
if !bACLFound {
|
||||
t.Error("Expected to find an ACL rule for jbd@google.com user, but not found")
|
||||
}
|
||||
if err := DeleteBucketACLRule(ctx, bucket, "user-jbd@google.com"); err != nil {
|
||||
t.Errorf("Error while deleting bucket ACL rule: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup(t *testing.T, prefix string) {
|
||||
ctx := testutil.Context(ScopeFullControl)
|
||||
var q *Query = &Query{
|
||||
Prefix: prefix,
|
||||
}
|
||||
for {
|
||||
o, err := ListObjects(ctx, bucket, q)
|
||||
if err != nil {
|
||||
t.Fatalf("Cleanup List for bucket %v failed with error: %v", bucket, err)
|
||||
}
|
||||
for _, obj := range o.Results {
|
||||
t.Logf("Cleanup deletion of %v", obj.Name)
|
||||
if err = DeleteObject(ctx, bucket, obj.Name); err != nil {
|
||||
t.Fatalf("Cleanup Delete for object %v failed with %v", obj.Name, err)
|
||||
}
|
||||
}
|
||||
if o.Next == nil {
|
||||
break
|
||||
}
|
||||
q = o.Next
|
||||
}
|
||||
}
|
||||
|
||||
func randomContents() []byte {
|
||||
h := md5.New()
|
||||
io.WriteString(h, fmt.Sprintf("hello world%d", rand.Intn(100000)))
|
||||
return h.Sum(nil)
|
||||
}
|
350
vendor/google.golang.org/cloud/storage/storage.go
generated
vendored
Normal file
350
vendor/google.golang.org/cloud/storage/storage.go
generated
vendored
Normal file
|
@ -0,0 +1,350 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
// Package storage contains a Google Cloud Storage client.
|
||||
//
|
||||
// This package is experimental and may make backwards-incompatible changes.
|
||||
package storage // import "google.golang.org/cloud/storage"
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/cloud/internal"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/googleapi"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBucketNotExist = errors.New("storage: bucket doesn't exist")
|
||||
ErrObjectNotExist = errors.New("storage: object doesn't exist")
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeFullControl grants permissions to manage your
|
||||
// data and permissions in Google Cloud Storage.
|
||||
ScopeFullControl = raw.DevstorageFullControlScope
|
||||
|
||||
// ScopeReadOnly grants permissions to
|
||||
// view your data in Google Cloud Storage.
|
||||
ScopeReadOnly = raw.DevstorageReadOnlyScope
|
||||
|
||||
// ScopeReadWrite grants permissions to manage your
|
||||
// data in Google Cloud Storage.
|
||||
ScopeReadWrite = raw.DevstorageReadWriteScope
|
||||
)
|
||||
|
||||
// TODO(jbd): Add storage.buckets.list.
|
||||
// TODO(jbd): Add storage.buckets.insert.
|
||||
// TODO(jbd): Add storage.buckets.update.
|
||||
// TODO(jbd): Add storage.buckets.delete.
|
||||
|
||||
// TODO(jbd): Add storage.objects.watch.
|
||||
|
||||
// BucketInfo returns the metadata for the specified bucket.
|
||||
func BucketInfo(ctx context.Context, name string) (*Bucket, error) {
|
||||
resp, err := rawService(ctx).Buckets.Get(name).Projection("full").Context(ctx).Do()
|
||||
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
|
||||
return nil, ErrBucketNotExist
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newBucket(resp), nil
|
||||
}
|
||||
|
||||
// ListObjects lists objects from the bucket. You can specify a query
|
||||
// to filter the results. If q is nil, no filtering is applied.
|
||||
func ListObjects(ctx context.Context, bucket string, q *Query) (*Objects, error) {
|
||||
c := rawService(ctx).Objects.List(bucket)
|
||||
c.Projection("full")
|
||||
if q != nil {
|
||||
c.Delimiter(q.Delimiter)
|
||||
c.Prefix(q.Prefix)
|
||||
c.Versions(q.Versions)
|
||||
c.PageToken(q.Cursor)
|
||||
if q.MaxResults > 0 {
|
||||
c.MaxResults(int64(q.MaxResults))
|
||||
}
|
||||
}
|
||||
resp, err := c.Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objects := &Objects{
|
||||
Results: make([]*Object, len(resp.Items)),
|
||||
Prefixes: make([]string, len(resp.Prefixes)),
|
||||
}
|
||||
for i, item := range resp.Items {
|
||||
objects.Results[i] = newObject(item)
|
||||
}
|
||||
for i, prefix := range resp.Prefixes {
|
||||
objects.Prefixes[i] = prefix
|
||||
}
|
||||
if resp.NextPageToken != "" {
|
||||
next := Query{}
|
||||
if q != nil {
|
||||
// keep the other filtering
|
||||
// criteria if there is a query
|
||||
next = *q
|
||||
}
|
||||
next.Cursor = resp.NextPageToken
|
||||
objects.Next = &next
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// SignedURLOptions allows you to restrict the access to the signed URL.
|
||||
type SignedURLOptions struct {
|
||||
// GoogleAccessID represents the authorizer of the signed URL generation.
|
||||
// It is typically the Google service account client email address from
|
||||
// the Google Developers Console in the form of "xxx@developer.gserviceaccount.com".
|
||||
// Required.
|
||||
GoogleAccessID string
|
||||
|
||||
// PrivateKey is the Google service account private key. It is obtainable
|
||||
// from the Google Developers Console.
|
||||
// At https://console.developers.google.com/project/<your-project-id>/apiui/credential,
|
||||
// create a service account client ID or reuse one of your existing service account
|
||||
// credentials. Click on the "Generate new P12 key" to generate and download
|
||||
// a new private key. Once you download the P12 file, use the following command
|
||||
// to convert it into a PEM file.
|
||||
//
|
||||
// $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes
|
||||
//
|
||||
// Provide the contents of the PEM file as a byte slice.
|
||||
// Required.
|
||||
PrivateKey []byte
|
||||
|
||||
// Method is the HTTP method to be used with the signed URL.
|
||||
// Signed URLs can be used with GET, HEAD, PUT, and DELETE requests.
|
||||
// Required.
|
||||
Method string
|
||||
|
||||
// Expires is the expiration time on the signed URL. It must be
|
||||
// a datetime in the future.
|
||||
// Required.
|
||||
Expires time.Time
|
||||
|
||||
// ContentType is the content type header the client must provide
|
||||
// to use the generated signed URL.
|
||||
// Optional.
|
||||
ContentType string
|
||||
|
||||
// Headers is a list of extention headers the client must provide
|
||||
// in order to use the generated signed URL.
|
||||
// Optional.
|
||||
Headers []string
|
||||
|
||||
// MD5 is the base64 encoded MD5 checksum of the file.
|
||||
// If provided, the client should provide the exact value on the request
|
||||
// header in order to use the signed URL.
|
||||
// Optional.
|
||||
MD5 []byte
|
||||
}
|
||||
|
||||
// SignedURL returns a URL for the specified object. Signed URLs allow
|
||||
// the users access to a restricted resource for a limited time without having a
|
||||
// Google account or signing in. For more information about the signed
|
||||
// URLs, see https://cloud.google.com/storage/docs/accesscontrol#Signed-URLs.
|
||||
func SignedURL(bucket, name string, opts *SignedURLOptions) (string, error) {
|
||||
if opts == nil {
|
||||
return "", errors.New("storage: missing required SignedURLOptions")
|
||||
}
|
||||
if opts.GoogleAccessID == "" || opts.PrivateKey == nil {
|
||||
return "", errors.New("storage: missing required credentials to generate a signed URL")
|
||||
}
|
||||
if opts.Method == "" {
|
||||
return "", errors.New("storage: missing required method option")
|
||||
}
|
||||
if opts.Expires.IsZero() {
|
||||
return "", errors.New("storage: missing required expires option")
|
||||
}
|
||||
key, err := parseKey(opts.PrivateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h := sha256.New()
|
||||
fmt.Fprintf(h, "%s\n", opts.Method)
|
||||
fmt.Fprintf(h, "%s\n", opts.MD5)
|
||||
fmt.Fprintf(h, "%s\n", opts.ContentType)
|
||||
fmt.Fprintf(h, "%d\n", opts.Expires.Unix())
|
||||
fmt.Fprintf(h, "%s", strings.Join(opts.Headers, "\n"))
|
||||
fmt.Fprintf(h, "/%s/%s", bucket, name)
|
||||
b, err := rsa.SignPKCS1v15(
|
||||
rand.Reader,
|
||||
key,
|
||||
crypto.SHA256,
|
||||
h.Sum(nil),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encoded := base64.StdEncoding.EncodeToString(b)
|
||||
u := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "storage.googleapis.com",
|
||||
Path: fmt.Sprintf("/%s/%s", bucket, name),
|
||||
}
|
||||
q := u.Query()
|
||||
q.Set("GoogleAccessId", opts.GoogleAccessID)
|
||||
q.Set("Expires", fmt.Sprintf("%d", opts.Expires.Unix()))
|
||||
q.Set("Signature", string(encoded))
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// StatObject returns meta information about the specified object.
|
||||
func StatObject(ctx context.Context, bucket, name string) (*Object, error) {
|
||||
o, err := rawService(ctx).Objects.Get(bucket, name).Projection("full").Context(ctx).Do()
|
||||
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
|
||||
return nil, ErrObjectNotExist
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newObject(o), nil
|
||||
}
|
||||
|
||||
// UpdateAttrs updates an object with the provided attributes.
|
||||
// All zero-value attributes are ignored.
|
||||
func UpdateAttrs(ctx context.Context, bucket, name string, attrs ObjectAttrs) (*Object, error) {
|
||||
o, err := rawService(ctx).Objects.Patch(bucket, name, attrs.toRawObject(bucket)).Projection("full").Context(ctx).Do()
|
||||
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
|
||||
return nil, ErrObjectNotExist
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newObject(o), nil
|
||||
}
|
||||
|
||||
// DeleteObject deletes the single specified object.
|
||||
func DeleteObject(ctx context.Context, bucket, name string) error {
|
||||
return rawService(ctx).Objects.Delete(bucket, name).Context(ctx).Do()
|
||||
}
|
||||
|
||||
// CopyObject copies the source object to the destination.
|
||||
// The copied object's attributes are overwritten by attrs if non-nil.
|
||||
func CopyObject(ctx context.Context, srcBucket, srcName string, destBucket, destName string, attrs *ObjectAttrs) (*Object, error) {
|
||||
if srcBucket == "" || destBucket == "" {
|
||||
return nil, errors.New("storage: srcBucket and destBucket must both be non-empty")
|
||||
}
|
||||
if srcName == "" || destName == "" {
|
||||
return nil, errors.New("storage: srcName and destName must be non-empty")
|
||||
}
|
||||
var rawObject *raw.Object
|
||||
if attrs != nil {
|
||||
attrs.Name = destName
|
||||
if attrs.ContentType == "" {
|
||||
return nil, errors.New("storage: attrs.ContentType must be non-empty")
|
||||
}
|
||||
rawObject = attrs.toRawObject(destBucket)
|
||||
}
|
||||
o, err := rawService(ctx).Objects.Copy(
|
||||
srcBucket, srcName, destBucket, destName, rawObject).Projection("full").Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newObject(o), nil
|
||||
}
|
||||
|
||||
// NewReader creates a new io.ReadCloser to read the contents
|
||||
// of the object.
|
||||
func NewReader(ctx context.Context, bucket, name string) (io.ReadCloser, error) {
|
||||
hc := internal.HTTPClient(ctx)
|
||||
u := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "storage.googleapis.com",
|
||||
Path: fmt.Sprintf("/%s/%s", bucket, name),
|
||||
}
|
||||
res, err := hc.Get(u.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
res.Body.Close()
|
||||
return nil, ErrObjectNotExist
|
||||
}
|
||||
if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||
res.Body.Close()
|
||||
return res.Body, fmt.Errorf("storage: can't read object %v/%v, status code: %v", bucket, name, res.Status)
|
||||
}
|
||||
return res.Body, nil
|
||||
}
|
||||
|
||||
// NewWriter returns a storage Writer that writes to the GCS object
|
||||
// identified by the specified name.
|
||||
// If such an object doesn't exist, it creates one.
|
||||
// Attributes can be set on the object by modifying the returned Writer's
|
||||
// ObjectAttrs field before the first call to Write. The name parameter to this
|
||||
// function is ignored if the Name field of the ObjectAttrs field is set to a
|
||||
// non-empty string.
|
||||
//
|
||||
// It is the caller's responsibility to call Close when writing is done.
|
||||
//
|
||||
// The object is not available and any previous object with the same
|
||||
// name is not replaced on Cloud Storage until Close is called.
|
||||
func NewWriter(ctx context.Context, bucket, name string) *Writer {
|
||||
return &Writer{
|
||||
ctx: ctx,
|
||||
bucket: bucket,
|
||||
name: name,
|
||||
donec: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func rawService(ctx context.Context) *raw.Service {
|
||||
return internal.Service(ctx, "storage", func(hc *http.Client) interface{} {
|
||||
svc, _ := raw.New(hc)
|
||||
return svc
|
||||
}).(*raw.Service)
|
||||
}
|
||||
|
||||
// parseKey converts the binary contents of a private key file
|
||||
// to an *rsa.PrivateKey. It detects whether the private key is in a
|
||||
// PEM container or not. If so, it extracts the the private key
|
||||
// from PEM container before conversion. It only supports PEM
|
||||
// containers with no passphrase.
|
||||
func parseKey(key []byte) (*rsa.PrivateKey, error) {
|
||||
if block, _ := pem.Decode(key); block != nil {
|
||||
key = block.Bytes
|
||||
}
|
||||
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
parsed, ok := parsedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("oauth2: private key is invalid")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
238
vendor/google.golang.org/cloud/storage/storage_test.go
generated
vendored
Normal file
238
vendor/google.golang.org/cloud/storage/storage_test.go
generated
vendored
Normal file
|
@ -0,0 +1,238 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestSignedURL(t *testing.T) {
|
||||
expires, _ := time.Parse(time.RFC3339, "2002-10-02T10:00:00-05:00")
|
||||
url, err := SignedURL("bucket-name", "object-name", &SignedURLOptions{
|
||||
GoogleAccessID: "xxx@clientid",
|
||||
PrivateKey: dummyKey("rsa"),
|
||||
Method: "GET",
|
||||
MD5: []byte("202cb962ac59075b964b07152d234b70"),
|
||||
Expires: expires,
|
||||
ContentType: "application/json",
|
||||
Headers: []string{"x-header1", "x-header2"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
want := "https://storage.googleapis.com/bucket-name/object-name?" +
|
||||
"Expires=1033570800&GoogleAccessId=xxx%40clientid&Signature=" +
|
||||
"ITqNWQHr7ayIj%2B0Ds5%2FzUT2cWMQQouuFmu6L11Zd3kfNKvm3sjyGIzO" +
|
||||
"gZsSUoter1SxP7BcrCzgqIZ9fQmgQnuIpqqLL4kcGmTbKsQS6hTknpJM%2F" +
|
||||
"2lS4NY6UH1VXBgm2Tce28kz8rnmqG6svcGvtWuOgJsETeSIl1R9nAEIDCEq" +
|
||||
"ZJzoOiru%2BODkHHkpoFjHWAwHugFHX%2B9EX4SxaytiN3oEy48HpYGWV0I" +
|
||||
"h8NvU1hmeWzcLr41GnTADeCn7Eg%2Fb5H2GCNO70Cz%2Bw2fn%2BofLCUeR" +
|
||||
"YQd%2FhES8oocv5kpHZkstc8s8uz3aKMsMauzZ9MOmGy%2F6VULBgIVvi6a" +
|
||||
"AwEBIYOw%3D%3D"
|
||||
if url != want {
|
||||
t.Fatalf("Unexpected signed URL; found %v", url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedURL_PEMPrivateKey(t *testing.T) {
|
||||
expires, _ := time.Parse(time.RFC3339, "2002-10-02T10:00:00-05:00")
|
||||
url, err := SignedURL("bucket-name", "object-name", &SignedURLOptions{
|
||||
GoogleAccessID: "xxx@clientid",
|
||||
PrivateKey: dummyKey("pem"),
|
||||
Method: "GET",
|
||||
MD5: []byte("202cb962ac59075b964b07152d234b70"),
|
||||
Expires: expires,
|
||||
ContentType: "application/json",
|
||||
Headers: []string{"x-header1", "x-header2"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
want := "https://storage.googleapis.com/bucket-name/object-name?" +
|
||||
"Expires=1033570800&GoogleAccessId=xxx%40clientid&Signature=" +
|
||||
"B7XkS4dfmVDoe%2FoDeXZkWlYmg8u2kI0SizTrzL5%2B9RmKnb5j7Kf34DZ" +
|
||||
"JL8Hcjr1MdPFLNg2QV4lEH86Gqgqt%2Fv3jFOTRl4wlzcRU%2FvV5c5HU8M" +
|
||||
"qW0FZ0IDbqod2RdsMONLEO6yQWV2HWFrMLKl2yMFlWCJ47et%2BFaHe6v4Z" +
|
||||
"EBc0%3D"
|
||||
if url != want {
|
||||
t.Fatalf("Unexpected signed URL; found %v", url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedURL_MissingOptions(t *testing.T) {
|
||||
pk := dummyKey("rsa")
|
||||
var tests = []struct {
|
||||
opts *SignedURLOptions
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
&SignedURLOptions{},
|
||||
"missing required credentials",
|
||||
},
|
||||
{
|
||||
&SignedURLOptions{GoogleAccessID: "access_id"},
|
||||
"missing required credentials",
|
||||
},
|
||||
{
|
||||
&SignedURLOptions{
|
||||
GoogleAccessID: "access_id",
|
||||
PrivateKey: pk,
|
||||
},
|
||||
"missing required method",
|
||||
},
|
||||
{
|
||||
&SignedURLOptions{
|
||||
GoogleAccessID: "access_id",
|
||||
PrivateKey: pk,
|
||||
Method: "PUT",
|
||||
},
|
||||
"missing required expires",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
_, err := SignedURL("bucket", "name", test.opts)
|
||||
if !strings.Contains(err.Error(), test.errMsg) {
|
||||
t.Errorf("expected err: %v, found: %v", test.errMsg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dummyKey(kind string) []byte {
|
||||
slurp, err := ioutil.ReadFile(fmt.Sprintf("./testdata/dummy_%s", kind))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return slurp
|
||||
}
|
||||
|
||||
func TestCopyObjectMissingFields(t *testing.T) {
|
||||
var tests = []struct {
|
||||
srcBucket, srcName, destBucket, destName string
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
"mybucket", "", "mybucket", "destname",
|
||||
"srcName and destName must be non-empty",
|
||||
},
|
||||
{
|
||||
"mybucket", "srcname", "mybucket", "",
|
||||
"srcName and destName must be non-empty",
|
||||
},
|
||||
{
|
||||
"", "srcfile", "mybucket", "destname",
|
||||
"srcBucket and destBucket must both be non-empty",
|
||||
},
|
||||
{
|
||||
"mybucket", "srcfile", "", "destname",
|
||||
"srcBucket and destBucket must both be non-empty",
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
_, err := CopyObject(context.TODO(), test.srcBucket, test.srcName, test.destBucket, test.destName, nil)
|
||||
if !strings.Contains(err.Error(), test.errMsg) {
|
||||
t.Errorf("CopyObject test #%v: err = %v, want %v", i, err, test.errMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectNames(t *testing.T) {
|
||||
// Naming requirements: https://cloud.google.com/storage/docs/bucket-naming
|
||||
const maxLegalLength = 1024
|
||||
|
||||
type testT struct {
|
||||
name, want string
|
||||
}
|
||||
tests := []testT{
|
||||
// Embedded characters important in URLs.
|
||||
{"foo % bar", "foo%20%25%20bar"},
|
||||
{"foo ? bar", "foo%20%3F%20bar"},
|
||||
{"foo / bar", "foo%20/%20bar"},
|
||||
{"foo %?/ bar", "foo%20%25%3F/%20bar"},
|
||||
|
||||
// Non-Roman scripts
|
||||
{"타코", "%ED%83%80%EC%BD%94"},
|
||||
{"世界", "%E4%B8%96%E7%95%8C"},
|
||||
|
||||
// Longest legal name
|
||||
{strings.Repeat("a", maxLegalLength), strings.Repeat("a", maxLegalLength)},
|
||||
|
||||
// Line terminators besides CR and LF: https://en.wikipedia.org/wiki/Newline#Unicode
|
||||
{"foo \u000b bar", "foo%20%0B%20bar"},
|
||||
{"foo \u000c bar", "foo%20%0C%20bar"},
|
||||
{"foo \u0085 bar", "foo%20%C2%85%20bar"},
|
||||
{"foo \u2028 bar", "foo%20%E2%80%A8%20bar"},
|
||||
{"foo \u2029 bar", "foo%20%E2%80%A9%20bar"},
|
||||
|
||||
// Null byte.
|
||||
{"foo \u0000 bar", "foo%20%00%20bar"},
|
||||
|
||||
// Non-control characters that are discouraged, but not forbidden, according to the documentation.
|
||||
{"foo # bar", "foo%20%23%20bar"},
|
||||
{"foo []*? bar", "foo%20%5B%5D%2A%3F%20bar"},
|
||||
|
||||
// Angstrom symbol singleton and normalized forms: http://unicode.org/reports/tr15/
|
||||
{"foo \u212b bar", "foo%20%E2%84%AB%20bar"},
|
||||
{"foo \u0041\u030a bar", "foo%20A%CC%8A%20bar"},
|
||||
{"foo \u00c5 bar", "foo%20%C3%85%20bar"},
|
||||
|
||||
// Hangul separating jamo: http://www.unicode.org/versions/Unicode7.0.0/ch18.pdf (Table 18-10)
|
||||
{"foo \u3131\u314f bar", "foo%20%E3%84%B1%E3%85%8F%20bar"},
|
||||
{"foo \u1100\u1161 bar", "foo%20%E1%84%80%E1%85%A1%20bar"},
|
||||
{"foo \uac00 bar", "foo%20%EA%B0%80%20bar"},
|
||||
}
|
||||
|
||||
// C0 control characters not forbidden by the docs.
|
||||
var runes []rune
|
||||
for r := rune(0x01); r <= rune(0x1f); r++ {
|
||||
if r != '\u000a' && r != '\u000d' {
|
||||
runes = append(runes, r)
|
||||
}
|
||||
}
|
||||
tests = append(tests, testT{fmt.Sprintf("foo %s bar", string(runes)), "foo%20%01%02%03%04%05%06%07%08%09%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20bar"})
|
||||
|
||||
// C1 control characters, plus DEL.
|
||||
runes = nil
|
||||
for r := rune(0x7f); r <= rune(0x9f); r++ {
|
||||
runes = append(runes, r)
|
||||
}
|
||||
tests = append(tests, testT{fmt.Sprintf("foo %s bar", string(runes)), "foo%20%7F%C2%80%C2%81%C2%82%C2%83%C2%84%C2%85%C2%86%C2%87%C2%88%C2%89%C2%8A%C2%8B%C2%8C%C2%8D%C2%8E%C2%8F%C2%90%C2%91%C2%92%C2%93%C2%94%C2%95%C2%96%C2%97%C2%98%C2%99%C2%9A%C2%9B%C2%9C%C2%9D%C2%9E%C2%9F%20bar"})
|
||||
|
||||
opts := &SignedURLOptions{
|
||||
GoogleAccessID: "xxx@clientid",
|
||||
PrivateKey: dummyKey("rsa"),
|
||||
Method: "GET",
|
||||
MD5: []byte("202cb962ac59075b964b07152d234b70"),
|
||||
Expires: time.Date(2002, time.October, 2, 10, 0, 0, 0, time.UTC),
|
||||
ContentType: "application/json",
|
||||
Headers: []string{"x-header1", "x-header2"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
g, err := SignedURL("bucket-name", test.name, opts)
|
||||
if err != nil {
|
||||
t.Errorf("SignedURL(%q) err=%v, want nil", test.name, err)
|
||||
}
|
||||
if w := "/bucket-name/" + test.want; !strings.Contains(g, w) {
|
||||
t.Errorf("SignedURL(%q)=%q, want substring %q", test.name, g, w)
|
||||
}
|
||||
}
|
||||
}
|
39
vendor/google.golang.org/cloud/storage/testdata/dummy_pem
generated
vendored
Normal file
39
vendor/google.golang.org/cloud/storage/testdata/dummy_pem
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
Bag Attributes
|
||||
friendlyName: privatekey
|
||||
localKeyID: 54 69 6D 65 20 31 34 31 36 38 35 32 30 30 34 37 37 32
|
||||
Key Attributes: <No Attributes>
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQCtCWMoJ2Bok2QoGFyU7A6IlGprO9QfUTT0jNrLkIbM5OWNIuDx
|
||||
64+PEaTS5g5m+2Hz/lmd5jJKanAH4dY9LZzsaYAPq1K17Gcmg1hEisYeKsgOcjYY
|
||||
kwRkV+natCTsC+tfWmS0voRh0jA1rI1J4MikceoHtgWdEuoHrrptRVpWKwIDAQAB
|
||||
AoGAKp3uQvx3vSnX+BwP6Um+RpsvHpwMoW3xue1bEdnVqW8SrlERz+NxZw40ZxDs
|
||||
KSbuuBZD4iTI7BUM5JQVnNm4FQY1YrPlWZLyI73Bj8RKTXrPdJheM/0r7xjiIXbQ
|
||||
7w4cUSM9rVugnI/rxF2kPIQTGYI+EG/6+P+k6VvgPmC0T/ECQQDUPskiS18WaY+i
|
||||
Koalbrb3GakaBoHrC1b4ln4CAv7fq7H4WvFvqi/2rxLhHYq31iwxYy8s7J7Sba1+
|
||||
5vwJ2TxZAkEA0LVfs3Q2VWZ+cM3bv0aYTalMXg6wT+LoNvk9HnOb0zQYajF3qm4G
|
||||
ZFdfEqvOkje0zQ4fcihARKyda/VY84UGIwJBAIZa0FvjNmgrnn7bSKzEbxHwrnkJ
|
||||
EYjGfuGR8mY3mzvfpiM+/oLfSslvfhX+62cALq18yco4ZzlxsFgaxAU//NECQDcS
|
||||
NN94YcHlGqYPW9W7/gI4EwOaoqFhwV6II71+SfbP/0U+KlJZV+xwNZEKrqZcdqPI
|
||||
/zkzL8ovNha/laokRrsCQQCyoPHGcBWj+VFbNoyQnX4tghc6rOY7n4pmpgQvU825
|
||||
TAM9vnYtSkKK/V56kEDNBO5LwiRsir95IUNclqqMKR1C
|
||||
-----END RSA PRIVATE KEY-----
|
||||
Bag Attributes
|
||||
friendlyName: privatekey
|
||||
localKeyID: 54 69 6D 65 20 31 34 31 36 38 35 32 30 30 34 37 37 32
|
||||
subject=/CN=1079432350659-nvog0vmn9s6pqr3kr4v2avbc7nkhoa11.apps.googleusercontent.com
|
||||
issuer=/CN=1079432350659-nvog0vmn9s6pqr3kr4v2avbc7nkhoa11.apps.googleusercontent.com
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICXTCCAcagAwIBAgIIHxTMQUVJRZ0wDQYJKoZIhvcNAQEFBQAwVDFSMFAGA1UE
|
||||
AxNJMTA3OTQzMjM1MDY1OS1udm9nMHZtbjlzNnBxcjNrcjR2MmF2YmM3bmtob2Ex
|
||||
MS5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbTAeFw0xNDExMjQxODAwMDRaFw0y
|
||||
NDExMjExODAwMDRaMFQxUjBQBgNVBAMTSTEwNzk0MzIzNTA2NTktbnZvZzB2bW45
|
||||
czZwcXIza3I0djJhdmJjN25raG9hMTEuYXBwcy5nb29nbGV1c2VyY29udGVudC5j
|
||||
b20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAK0JYygnYGiTZCgYXJTsDoiU
|
||||
ams71B9RNPSM2suQhszk5Y0i4PHrj48RpNLmDmb7YfP+WZ3mMkpqcAfh1j0tnOxp
|
||||
gA+rUrXsZyaDWESKxh4qyA5yNhiTBGRX6dq0JOwL619aZLS+hGHSMDWsjUngyKRx
|
||||
6ge2BZ0S6geuum1FWlYrAgMBAAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/
|
||||
BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4GB
|
||||
ACVvKkZkomHq3uffOQwdZ4VJYuxrvDGnZu/ExW9WngO2teEsjxABL41TNnRYHN5T
|
||||
lMC19poFA2tR/DySDLJ2XNs/hSvyQUL6HHCncVdR4Srpie88j48peY1MZSMP51Jv
|
||||
qagbbP5K5DSEu02/zZaV0kaCvLEN0KAtj/noDuOOnQU2
|
||||
-----END CERTIFICATE-----
|
27
vendor/google.golang.org/cloud/storage/testdata/dummy_rsa
generated
vendored
Normal file
27
vendor/google.golang.org/cloud/storage/testdata/dummy_rsa
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAx4fm7dngEmOULNmAs1IGZ9Apfzh+BkaQ1dzkmbUgpcoghucE
|
||||
DZRnAGd2aPyB6skGMXUytWQvNYav0WTR00wFtX1ohWTfv68HGXJ8QXCpyoSKSSFY
|
||||
fuP9X36wBSkSX9J5DVgiuzD5VBdzUISSmapjKm+DcbRALjz6OUIPEWi1Tjl6p5RK
|
||||
1w41qdbmt7E5/kGhKLDuT7+M83g4VWhgIvaAXtnhklDAggilPPa8ZJ1IFe31lNlr
|
||||
k4DRk38nc6sEutdf3RL7QoH7FBusI7uXV03DC6dwN1kP4GE7bjJhcRb/7jYt7CQ9
|
||||
/E9Exz3c0yAp0yrTg0Fwh+qxfH9dKwN52S7SBwIDAQABAoIBAQCaCs26K07WY5Jt
|
||||
3a2Cw3y2gPrIgTCqX6hJs7O5ByEhXZ8nBwsWANBUe4vrGaajQHdLj5OKfsIDrOvn
|
||||
2NI1MqflqeAbu/kR32q3tq8/Rl+PPiwUsW3E6Pcf1orGMSNCXxeducF2iySySzh3
|
||||
nSIhCG5uwJDWI7a4+9KiieFgK1pt/Iv30q1SQS8IEntTfXYwANQrfKUVMmVF9aIK
|
||||
6/WZE2yd5+q3wVVIJ6jsmTzoDCX6QQkkJICIYwCkglmVy5AeTckOVwcXL0jqw5Kf
|
||||
5/soZJQwLEyBoQq7Kbpa26QHq+CJONetPP8Ssy8MJJXBT+u/bSseMb3Zsr5cr43e
|
||||
DJOhwsThAoGBAPY6rPKl2NT/K7XfRCGm1sbWjUQyDShscwuWJ5+kD0yudnT/ZEJ1
|
||||
M3+KS/iOOAoHDdEDi9crRvMl0UfNa8MAcDKHflzxg2jg/QI+fTBjPP5GOX0lkZ9g
|
||||
z6VePoVoQw2gpPFVNPPTxKfk27tEzbaffvOLGBEih0Kb7HTINkW8rIlzAoGBAM9y
|
||||
1yr+jvfS1cGFtNU+Gotoihw2eMKtIqR03Yn3n0PK1nVCDKqwdUqCypz4+ml6cxRK
|
||||
J8+Pfdh7D+ZJd4LEG6Y4QRDLuv5OA700tUoSHxMSNn3q9As4+T3MUyYxWKvTeu3U
|
||||
f2NWP9ePU0lV8ttk7YlpVRaPQmc1qwooBA/z/8AdAoGAW9x0HWqmRICWTBnpjyxx
|
||||
QGlW9rQ9mHEtUotIaRSJ6K/F3cxSGUEkX1a3FRnp6kPLcckC6NlqdNgNBd6rb2rA
|
||||
cPl/uSkZP42Als+9YMoFPU/xrrDPbUhu72EDrj3Bllnyb168jKLa4VBOccUvggxr
|
||||
Dm08I1hgYgdN5huzs7y6GeUCgYEAj+AZJSOJ6o1aXS6rfV3mMRve9bQ9yt8jcKXw
|
||||
5HhOCEmMtaSKfnOF1Ziih34Sxsb7O2428DiX0mV/YHtBnPsAJidL0SdLWIapBzeg
|
||||
KHArByIRkwE6IvJvwpGMdaex1PIGhx5i/3VZL9qiq/ElT05PhIb+UXgoWMabCp84
|
||||
OgxDK20CgYAeaFo8BdQ7FmVX2+EEejF+8xSge6WVLtkaon8bqcn6P0O8lLypoOhd
|
||||
mJAYH8WU+UAy9pecUnDZj14LAGNVmYcse8HFX71MoshnvCTFEPVo4rZxIAGwMpeJ
|
||||
5jgQ3slYLpqrGlcbLgUXBUgzEO684Wk/UV9DFPlHALVqCfXQ9dpJPg==
|
||||
-----END RSA PRIVATE KEY-----
|
417
vendor/google.golang.org/cloud/storage/types.go
generated
vendored
Normal file
417
vendor/google.golang.org/cloud/storage/types.go
generated
vendored
Normal file
|
@ -0,0 +1,417 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// Bucket represents a Google Cloud Storage bucket.
|
||||
type Bucket struct {
|
||||
// Name is the name of the bucket.
|
||||
Name string
|
||||
|
||||
// ACL is the list of access control rules on the bucket.
|
||||
ACL []ACLRule
|
||||
|
||||
// DefaultObjectACL is the list of access controls to
|
||||
// apply to new objects when no object ACL is provided.
|
||||
DefaultObjectACL []ACLRule
|
||||
|
||||
// Location is the location of the bucket. It defaults to "US".
|
||||
Location string
|
||||
|
||||
// Metageneration is the metadata generation of the bucket.
|
||||
// Read-only.
|
||||
Metageneration int64
|
||||
|
||||
// StorageClass is the storage class of the bucket. This defines
|
||||
// how objects in the bucket are stored and determines the SLA
|
||||
// and the cost of storage. Typical values are "STANDARD" and
|
||||
// "DURABLE_REDUCED_AVAILABILITY". Defaults to "STANDARD".
|
||||
StorageClass string
|
||||
|
||||
// Created is the creation time of the bucket.
|
||||
// Read-only.
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
func newBucket(b *raw.Bucket) *Bucket {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
bucket := &Bucket{
|
||||
Name: b.Name,
|
||||
Location: b.Location,
|
||||
Metageneration: b.Metageneration,
|
||||
StorageClass: b.StorageClass,
|
||||
Created: convertTime(b.TimeCreated),
|
||||
}
|
||||
acl := make([]ACLRule, len(b.Acl))
|
||||
for i, rule := range b.Acl {
|
||||
acl[i] = ACLRule{
|
||||
Entity: ACLEntity(rule.Entity),
|
||||
Role: ACLRole(rule.Role),
|
||||
}
|
||||
}
|
||||
bucket.ACL = acl
|
||||
objACL := make([]ACLRule, len(b.DefaultObjectAcl))
|
||||
for i, rule := range b.DefaultObjectAcl {
|
||||
objACL[i] = ACLRule{
|
||||
Entity: ACLEntity(rule.Entity),
|
||||
Role: ACLRole(rule.Role),
|
||||
}
|
||||
}
|
||||
bucket.DefaultObjectACL = objACL
|
||||
return bucket
|
||||
}
|
||||
|
||||
// ObjectAttrs is the user-editable object attributes.
|
||||
type ObjectAttrs struct {
|
||||
// Name is the name of the object.
|
||||
Name string
|
||||
|
||||
// ContentType is the MIME type of the object's content.
|
||||
// Optional.
|
||||
ContentType string
|
||||
|
||||
// ContentLanguage is the optional RFC 1766 Content-Language of
|
||||
// the object's content sent in response headers.
|
||||
ContentLanguage string
|
||||
|
||||
// ContentEncoding is the optional Content-Encoding of the object
|
||||
// sent it the response headers.
|
||||
ContentEncoding string
|
||||
|
||||
// CacheControl is the optional Cache-Control header of the object
|
||||
// sent in the response headers.
|
||||
CacheControl string
|
||||
|
||||
// ContentDisposition is the optional Content-Disposition header of the object
|
||||
// sent in the response headers.
|
||||
ContentDisposition string
|
||||
|
||||
// ACL is the list of access control rules for the object.
|
||||
// Optional. If nil or empty, existing ACL rules are preserved.
|
||||
ACL []ACLRule
|
||||
|
||||
// Metadata represents user-provided metadata, in key/value pairs.
|
||||
// It can be nil if the current metadata values needs to preserved.
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
func (o ObjectAttrs) toRawObject(bucket string) *raw.Object {
|
||||
var acl []*raw.ObjectAccessControl
|
||||
if len(o.ACL) > 0 {
|
||||
acl = make([]*raw.ObjectAccessControl, len(o.ACL))
|
||||
for i, rule := range o.ACL {
|
||||
acl[i] = &raw.ObjectAccessControl{
|
||||
Entity: string(rule.Entity),
|
||||
Role: string(rule.Role),
|
||||
}
|
||||
}
|
||||
}
|
||||
return &raw.Object{
|
||||
Bucket: bucket,
|
||||
Name: o.Name,
|
||||
ContentType: o.ContentType,
|
||||
ContentEncoding: o.ContentEncoding,
|
||||
ContentLanguage: o.ContentLanguage,
|
||||
CacheControl: o.CacheControl,
|
||||
ContentDisposition: o.ContentDisposition,
|
||||
Acl: acl,
|
||||
Metadata: o.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
// Object represents a Google Cloud Storage (GCS) object.
|
||||
type Object struct {
|
||||
// Bucket is the name of the bucket containing this GCS object.
|
||||
Bucket string
|
||||
|
||||
// Name is the name of the object within the bucket.
|
||||
Name string
|
||||
|
||||
// ContentType is the MIME type of the object's content.
|
||||
ContentType string
|
||||
|
||||
// ContentLanguage is the content language of the object's content.
|
||||
ContentLanguage string
|
||||
|
||||
// CacheControl is the Cache-Control header to be sent in the response
|
||||
// headers when serving the object data.
|
||||
CacheControl string
|
||||
|
||||
// ACL is the list of access control rules for the object.
|
||||
ACL []ACLRule
|
||||
|
||||
// Owner is the owner of the object.
|
||||
//
|
||||
// If non-zero, it is in the form of "user-<userId>".
|
||||
Owner string
|
||||
|
||||
// Size is the length of the object's content.
|
||||
Size int64
|
||||
|
||||
// ContentEncoding is the encoding of the object's content.
|
||||
ContentEncoding string
|
||||
|
||||
// MD5 is the MD5 hash of the object's content.
|
||||
MD5 []byte
|
||||
|
||||
// CRC32C is the CRC32 checksum of the object's content using
|
||||
// the Castagnoli93 polynomial.
|
||||
CRC32C uint32
|
||||
|
||||
// MediaLink is an URL to the object's content.
|
||||
MediaLink string
|
||||
|
||||
// Metadata represents user-provided metadata, in key/value pairs.
|
||||
// It can be nil if no metadata is provided.
|
||||
Metadata map[string]string
|
||||
|
||||
// Generation is the generation number of the object's content.
|
||||
Generation int64
|
||||
|
||||
// MetaGeneration is the version of the metadata for this
|
||||
// object at this generation. This field is used for preconditions
|
||||
// and for detecting changes in metadata. A metageneration number
|
||||
// is only meaningful in the context of a particular generation
|
||||
// of a particular object.
|
||||
MetaGeneration int64
|
||||
|
||||
// StorageClass is the storage class of the bucket.
|
||||
// This value defines how objects in the bucket are stored and
|
||||
// determines the SLA and the cost of storage. Typical values are
|
||||
// "STANDARD" and "DURABLE_REDUCED_AVAILABILITY".
|
||||
// It defaults to "STANDARD".
|
||||
StorageClass string
|
||||
|
||||
// Deleted is the time the object was deleted.
|
||||
// If not deleted, it is the zero value.
|
||||
Deleted time.Time
|
||||
|
||||
// Updated is the creation or modification time of the object.
|
||||
// For buckets with versioning enabled, changing an object's
|
||||
// metadata does not change this property.
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
// convertTime converts a time in RFC3339 format to time.Time.
|
||||
// If any error occurs in parsing, the zero-value time.Time is silently returned.
|
||||
func convertTime(t string) time.Time {
|
||||
var r time.Time
|
||||
if t != "" {
|
||||
r, _ = time.Parse(time.RFC3339, t)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func newObject(o *raw.Object) *Object {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
acl := make([]ACLRule, len(o.Acl))
|
||||
for i, rule := range o.Acl {
|
||||
acl[i] = ACLRule{
|
||||
Entity: ACLEntity(rule.Entity),
|
||||
Role: ACLRole(rule.Role),
|
||||
}
|
||||
}
|
||||
owner := ""
|
||||
if o.Owner != nil {
|
||||
owner = o.Owner.Entity
|
||||
}
|
||||
md5, _ := base64.StdEncoding.DecodeString(o.Md5Hash)
|
||||
var crc32c uint32
|
||||
d, err := base64.StdEncoding.DecodeString(o.Crc32c)
|
||||
if err == nil && len(d) == 4 {
|
||||
crc32c = uint32(d[0])<<24 + uint32(d[1])<<16 + uint32(d[2])<<8 + uint32(d[3])
|
||||
}
|
||||
return &Object{
|
||||
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,
|
||||
Deleted: convertTime(o.TimeDeleted),
|
||||
Updated: convertTime(o.Updated),
|
||||
}
|
||||
}
|
||||
|
||||
// Query represents a query to filter objects from a bucket.
|
||||
type Query struct {
|
||||
// Delimiter returns results in a directory-like fashion.
|
||||
// Results will contain only objects whose names, aside from the
|
||||
// prefix, do not contain delimiter. Objects whose names,
|
||||
// aside from the prefix, contain delimiter will have their name,
|
||||
// truncated after the delimiter, returned in prefixes.
|
||||
// Duplicate prefixes are omitted.
|
||||
// Optional.
|
||||
Delimiter string
|
||||
|
||||
// Prefix is the prefix filter to query objects
|
||||
// whose names begin with this prefix.
|
||||
// Optional.
|
||||
Prefix string
|
||||
|
||||
// Versions indicates whether multiple versions of the same
|
||||
// object will be included in the results.
|
||||
Versions bool
|
||||
|
||||
// Cursor is a previously-returned page token
|
||||
// representing part of the larger set of results to view.
|
||||
// Optional.
|
||||
Cursor string
|
||||
|
||||
// MaxResults is the maximum number of items plus prefixes
|
||||
// to return. As duplicate prefixes are omitted,
|
||||
// fewer total results may be returned than requested.
|
||||
// The default page limit is used if it is negative or zero.
|
||||
MaxResults int
|
||||
}
|
||||
|
||||
// Objects represents a list of objects returned from
|
||||
// a bucket look-p request and a query to retrieve more
|
||||
// objects from the next pages.
|
||||
type Objects struct {
|
||||
// Results represent a list of object results.
|
||||
Results []*Object
|
||||
|
||||
// Next is the continuation query to retrieve more
|
||||
// results with the same filtering criteria. If there
|
||||
// are no more results to retrieve, it is nil.
|
||||
Next *Query
|
||||
|
||||
// Prefixes represents prefixes of objects
|
||||
// matching-but-not-listed up to and including
|
||||
// the requested delimiter.
|
||||
Prefixes []string
|
||||
}
|
||||
|
||||
// contentTyper implements ContentTyper to enable an
|
||||
// io.ReadCloser to specify its MIME type.
|
||||
type contentTyper struct {
|
||||
io.Reader
|
||||
t string
|
||||
}
|
||||
|
||||
func (c *contentTyper) ContentType() string {
|
||||
return c.t
|
||||
}
|
||||
|
||||
// A Writer writes a Cloud Storage object.
|
||||
type Writer struct {
|
||||
// ObjectAttrs are optional attributes to set on the object. Any attributes
|
||||
// must be initialized before the first Write call. Nil or zero-valued
|
||||
// attributes are ignored.
|
||||
ObjectAttrs
|
||||
|
||||
ctx context.Context
|
||||
bucket string
|
||||
name string
|
||||
|
||||
once sync.Once
|
||||
|
||||
opened bool
|
||||
r io.Reader
|
||||
pw *io.PipeWriter
|
||||
|
||||
donec chan struct{} // closed after err and obj are set.
|
||||
err error
|
||||
obj *Object
|
||||
}
|
||||
|
||||
func (w *Writer) open() {
|
||||
attrs := w.ObjectAttrs
|
||||
// Always set the name, otherwise the backend
|
||||
// rejects the request and responds with an HTTP 400.
|
||||
if attrs.Name == "" {
|
||||
attrs.Name = w.name
|
||||
}
|
||||
pr, pw := io.Pipe()
|
||||
w.r = &contentTyper{pr, attrs.ContentType}
|
||||
w.pw = pw
|
||||
w.opened = true
|
||||
|
||||
go func() {
|
||||
resp, err := rawService(w.ctx).Objects.Insert(
|
||||
w.bucket, attrs.toRawObject(w.bucket)).Media(w.r).Projection("full").Context(w.ctx).Do()
|
||||
w.err = err
|
||||
if err == nil {
|
||||
w.obj = newObject(resp)
|
||||
} else {
|
||||
pr.CloseWithError(w.err)
|
||||
}
|
||||
close(w.donec)
|
||||
}()
|
||||
}
|
||||
|
||||
// Write appends to w.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
}
|
||||
if !w.opened {
|
||||
w.open()
|
||||
}
|
||||
return w.pw.Write(p)
|
||||
}
|
||||
|
||||
// Close completes the write operation and flushes any buffered data.
|
||||
// If Close doesn't return an error, metadata about the written object
|
||||
// can be retrieved by calling Object.
|
||||
func (w *Writer) Close() error {
|
||||
if !w.opened {
|
||||
w.open()
|
||||
}
|
||||
if err := w.pw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
<-w.donec
|
||||
return w.err
|
||||
}
|
||||
|
||||
// CloseWithError aborts the write operation with the provided error.
|
||||
// CloseWithError always returns nil.
|
||||
func (w *Writer) CloseWithError(err error) error {
|
||||
if !w.opened {
|
||||
return nil
|
||||
}
|
||||
return w.pw.CloseWithError(err)
|
||||
}
|
||||
|
||||
// Object returns metadata about a successfully-written object.
|
||||
// It's only valid to call it after Close returns nil.
|
||||
func (w *Writer) Object() *Object {
|
||||
return w.obj
|
||||
}
|
42
vendor/google.golang.org/cloud/storage/types_test.go
generated
vendored
Normal file
42
vendor/google.golang.org/cloud/storage/types_test.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/cloud"
|
||||
)
|
||||
|
||||
type fakeTransport struct{}
|
||||
|
||||
func (t *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return nil, fmt.Errorf("error handling request")
|
||||
}
|
||||
|
||||
func TestErrorOnObjectsInsertCall(t *testing.T) {
|
||||
ctx := cloud.NewContext("project-id", &http.Client{
|
||||
Transport: &fakeTransport{}})
|
||||
wc := NewWriter(ctx, "bucketname", "filename1")
|
||||
wc.ContentType = "text/plain"
|
||||
if _, err := wc.Write([]byte("hello world")); err == nil {
|
||||
t.Errorf("expected error on write, got nil")
|
||||
}
|
||||
if err := wc.Close(); err == nil {
|
||||
t.Errorf("expected error on close, got nil")
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue